一篇文章带你了解Go语言基础之网络编程

开发 前端
本次章节我们讲述了什么是TCP,什么是UDP。并且编写了代码如何实现TCP服务端,TCP客户端,UDP服务端,UDP客户端。讲述了为什么会出现粘包,该怎么解决粘包。

[[361058]]

前言

Hi,大家好呀,我是码农,星期八,我们身处21世纪,我们的世界已经在不知不觉中,就像很多网一样在互联互通。

互联网是一个统称,目前比较常用的有TCP,UDP协议。

当然,还有很多其他的协议,但是本次主要讲最常用的TCP和UDP协议。

socker编程

我们所学的TCP和UDP,统称为Socker编程,也叫做套接字编程。

多台机器要实现互相通讯,其实是一个非常复杂的过程,底层从铺设网线,网线接口,交换机,路由器,在到规定各种协议。

再到应用层QQ,微信等软件。

如果没有一套标准,每次使用都要自己去实现,可能每个程序员都不是掉头发那么简单了!

有了Socker之后,Socker会在应用层之前,将各种繁琐的的底层操作隐藏,我们可能只需要Socker.TCP就实现了TCP协议的通讯。

Go语言TCP

TCP属于稳定的,可靠的长连接,

既然要涉及通讯,必然有两个终端,最起码一个是服务端,一个是客户端,就像我们的淘宝,我们每次打开淘宝,都要去链接它,当然,淘宝可不直接是TCP。

服务端

在Go中实现服务端,并且在服务端并发很简单,只需要将每个连接让一个协程处理即可!

代码

  1. package main 
  2.  
  3. import ( 
  4.     "bufio" 
  5.     "fmt" 
  6.     "net" 
  7.  
  8. func process(conn net.Conn) { 
  9.     defer conn.Close() 
  10.     for { 
  11.         reader := bufio.NewReader(conn) 
  12.         buf := make([]byte, 128) 
  13.         n, err := reader.Read(buf) 
  14.         if err != nil { 
  15.             fmt.Println("数据读取失败", err) 
  16.             return 
  17.         } 
  18.         recvStr := string(buf[:n]) 
  19.         fmt.Println("客户端发送过来的值:", recvStr) 
  20.  
  21. func main() { 
  22.     lister, err := net.Listen("tcp", "0.0.0.0:8008"
  23.     if err != nil { 
  24.         fmt.Println("连接失败", err) 
  25.     for { 
  26.         fmt.Println("等待建立连接,此时会阻塞住"
  27.         conn, err := lister.Accept() //等待建立连接 
  28.         fmt.Println("连接建立成功,继续"
  29.         if err != nil { 
  30.             fmt.Println("建立连接失败", err) 
  31.             //继续监听下次链接 
  32.             continue 
  33.         } 
  34.         go process(conn) 

客户端

客户端就很简单了,相对来说是不需要并发的,只需要连接就行。

代码

  1. package main 
  2.  
  3. import ( 
  4.     "bufio" 
  5.     "fmt" 
  6.     "net" 
  7.     "os" 
  8.  
  9. //客户端 
  10. func main() { 
  11.     conn, err := net.Dial("tcp", "192.168.10.148:8008"
  12.     if err != nil { 
  13.         fmt.Println("连接服务器失败",err) 
  14.     defer conn.Close() 
  15.     inputReader:=bufio.NewReader(os.Stdin) 
  16.     for
  17.         fmt.Println(":"
  18.         input,_:=inputReader.ReadString('\n'
  19.         _, err = conn.Write([]byte(input)) 
  20.         if err != nil { 
  21.             fmt.Println("发送成功"
  22.         } 

执行结果

就这样,我们实现了服务端并发的处理所有客户端的请求。


粘包

我们先看一下什么是粘包。

服务端

  1. package main 
  2.  
  3. import ( 
  4.     "bufio" 
  5.     "fmt" 
  6.     "io" 
  7.     "net" 
  8.  
  9. func process(conn net.Conn) { 
  10.     defer conn.Close() 
  11.     reader := bufio.NewReader(conn) 
  12.     buf := make([]byte, 1024) 
  13.     for { 
  14.         n, err := reader.Read(buf) 
  15.         //读完了 
  16.         if err == io.EOF { 
  17.             fmt.Println("读完了"
  18.             break 
  19.         } 
  20.         //读错了 
  21.         if err != nil { 
  22.             fmt.Println("数据读取失败", err) 
  23.             return 
  24.         } 
  25.         recvStr := string(buf[:n]) 
  26.         fmt.Println("客户端发送过来的值:", recvStr) 
  27.  
  28. func main() { 
  29.     lister, err := net.Listen("tcp", "0.0.0.0:8008"
  30.     if err != nil { 
  31.         fmt.Println("连接失败", err) 
  32.         return 
  33.     defer lister.Close() 
  34.     for { 
  35.         fmt.Println("等待建立连接,此时会阻塞住"
  36.         conn, err := lister.Accept() //等待建立连接 
  37.         fmt.Println("连接建立成功,继续"
  38.         if err != nil { 
  39.             fmt.Println("建立连接失败", err) 
  40.             //继续监听下次链接 
  41.             continue 
  42.         } 
  43.         go process(conn) 

客户端

  1. package main 
  2.  
  3. import ( 
  4.     "fmt" 
  5.     "net" 
  6.  
  7. //客户端 
  8. func main() { 
  9.     conn, err := net.Dial("tcp", "192.168.10.148:8008"
  10.     if err != nil { 
  11.         fmt.Println("连接服务器失败", err) 
  12.     defer conn.Close() 
  13.     for i := 0; i < 10; i++ { 
  14.         sendStr := "hello world ads asdf asd fads fadsf ads ads asd asd ads " 
  15.         conn.Write([]byte(sendStr)) 
  16.         time.Sleep(time.Second

注意:18行代码睡眠了1s

执行结果


如果我注释了第18行代码


执行结果


直接都淦到一行了,what?这是啥情况,不应该跟原来一样吗???

每发送一个值,那边就接收一下,这怎么整到一块了!!!

原因

主要原因是因为我们是应用层软件,是跑在操作系统之上的软件,当我们向服务器发送一个数据时,是调用操作系统的相关接口发送的,操作系统再经过各种复杂的操作,发送到对方机器

但是操作系统有一个发送数据缓冲区,默认情况如果缓冲区是有大小的,如果缓冲区没满,是不会发送数据的,所以上述客户端在发送数据时,系统的缓冲区都没满,一直压在了操作系统的缓冲区中,最后发现没数据了,才一次都发送到服务端

但是为什么sleep(1)又管用了呢?这是因为缓冲区不止一个程序在用,1s的时间足够其他程序将缓冲区打满,然后各自发各自的数据,这也是为什么第一次操作没问题,第二次有问题,因为第二次全部都是我们客户端打满的


解决粘包

工具函数

我们将解包封包的函数封装一下

  1. socker_sitck/stick.go 

 

  1. package socker_stick 
  2.  
  3. import ( 
  4.     "bufio" 
  5.     "bytes" 
  6.     "encoding/binary" 
  7.     "fmt" 
  8.  
  9. //Encode 将消息编码 
  10. func Encode(message string) ([]byte, error) { 
  11.     length := int32(len(message)) 
  12.     var pkg = new(bytes.Buffer) 
  13.     //写入消息头 
  14.     err := binary.Write(pkg, binary.LittleEndian, length) 
  15.     if err != nil { 
  16.         fmt.Println("写入消息头失败", err) 
  17.         return nil, err 
  18.     //写入消息实体 
  19.     err = binary.Write(pkg, binary.LittleEndian, []byte(message)) 
  20.     if err != nil { 
  21.         fmt.Println("写入消息实体失败", err) 
  22.         return nil, err 
  23.     return pkg.Bytes(), nil 
  24.  
  25. //Decode解码消息 
  26. func Decode(reader *bufio.Reader) (string, error) { 
  27.     //读取信息长度 
  28.     lengthByte, _ := reader.Peek(4) 
  29.     lengthBuff := bytes.NewBuffer(lengthByte) 
  30.     var length int32 
  31.     err := binary.Read(lengthBuff, binary.LittleEndian, &length) 
  32.     if err != nil { 
  33.         return "", err 
  34.     //BuffRead 返回缓冲区现有的可读的字节数 
  35.     if int32(reader.Buffered()) < length+4 { 
  36.         return "", err 
  37.     pack := make([]byte, int(4+length)) 
  38.     _, err = reader.Read(pack) 
  39.     if err != nil { 
  40.         return "", err 
  41.     return string(pack[4:]), nil 

服务端

  1. package main 
  2.  
  3. import ( 
  4.     "a3_course/socker_stick" 
  5.     "bufio" 
  6.     "fmt" 
  7.     "io" 
  8.     "net" 
  9.  
  10. func process(conn net.Conn) { 
  11.     defer conn.Close() 
  12.     reader := bufio.NewReader(conn) 
  13.  
  14.     for { 
  15.         msg, err := socker_stick.Decode(reader) 
  16.         //读完了 
  17.         if err == io.EOF { 
  18.             fmt.Println("读完了"
  19.             break 
  20.         } 
  21.         //读错了 
  22.         if err != nil { 
  23.             fmt.Println("数据读取失败", err) 
  24.             return 
  25.         } 
  26.  
  27.         fmt.Println("客户端发送过来的值:", msg) 
  28.  
  29. func main() { 
  30.     lister, err := net.Listen("tcp", "0.0.0.0:8008"
  31.     if err != nil { 
  32.         fmt.Println("连接失败", err) 
  33.         return 
  34.     defer lister.Close() 
  35.     for { 
  36.         fmt.Println("等待建立连接,此时会阻塞住"
  37.         conn, err := lister.Accept() //等待建立连接 
  38.         fmt.Println("连接建立成功,继续"
  39.         if err != nil { 
  40.             fmt.Println("建立连接失败", err) 
  41.             //继续监听下次链接 
  42.             continue 
  43.         } 
  44.         go process(conn) 

客户端

  1. package main 
  2.  
  3. import ( 
  4.     "a3_course/socker_stick" 
  5.     "fmt" 
  6.     "net" 
  7.  
  8. //客户端 
  9. func main() { 
  10.     conn, err := net.Dial("tcp", "192.168.10.148:8008"
  11.     if err != nil { 
  12.         fmt.Println("连接服务器失败", err) 
  13.     defer conn.Close() 
  14.     for i := 0; i < 10; i++ { 
  15.         sendStr := "hello world ads asdf asd fads fadsf ads ads asd asd ads " 
  16.         data, err := socker_stick.Encode(sendStr) 
  17.         if err != nil { 
  18.             fmt.Println("编码失败",err) 
  19.             return 
  20.         } 
  21.         conn.Write(data) 
  22.         //time.Sleep(time.Second

执行结果


这次真的不管执行几次,都是这样的结果

对了,只有TCP才有粘包

Go语言UDP

UDP是一个无连接协议,客户端不会在乎服务端有没有问题,客户端只管发,通常用于实时性比较高的领域

例如直播行业

服务端

  1. package main 
  2.  
  3. import ( 
  4.     "fmt" 
  5.     "net" 
  6.  
  7. func main() { 
  8.     listen, err := net.ListenUDP("udp", &net.UDPAddr{ 
  9.         IP:   net.IPv4(0, 0, 0, 0), 
  10.         Port: 8009, 
  11. }) 
  12.     if err != nil { 
  13.         panic(fmt.Sprintf("udp启动失败,err:%v", err)) 
  14.     defer listen.Close() 
  15.     for
  16.         var data = make([]byte,1024) 
  17.         n, addr, err := listen.ReadFromUDP(data) 
  18.         if err != nil { 
  19.             panic(fmt.Sprintf("读取数据失败,err:%v", err)) 
  20.         } 
  21.         fmt.Println(string(data[:n]),addr,n) 

客户端

  1. package main 
  2.  
  3. import ( 
  4.     "fmt" 
  5.     "net" 
  6.  
  7. func main() { 
  8.     socker, err := net.DialUDP("udp", nil, &net.UDPAddr{ 
  9.         IP:   net.IPv4(0, 0, 0, 0), 
  10.         Port: 8009, 
  11. }) 
  12.     if err != nil { 
  13.         panic(fmt.Sprintf("连接服务器失败,err:%v", err)) 
  14.     defer socker.Close() 
  15.     sendStr:="你好呀" 
  16.     _, err = socker.Write([]byte(sendStr)) 
  17.     if err != nil { 
  18.         panic(fmt.Sprintf("数据发送失败,err:%v", err)) 

执行结果


总结

本次章节我们讲述了什么是TCP,什么是UDP。

并且编写了代码如何实现TCP服务端,TCP客户端,UDP服务端,UDP客户端。

讲述了为什么会出现粘包,该怎么解决粘包。

逆水行舟,不进则退!

 

责任编辑:姜华 来源: Go语言进阶学习
相关推荐

2020-11-05 09:58:16

Go语言Map

2022-02-16 10:03:06

对象接口代码

2020-10-22 08:33:22

Go语言

2020-11-11 10:52:54

Go语言C语言

2020-12-09 09:59:32

Go语言技术

2020-10-25 07:33:13

Go语言

2022-04-27 10:01:43

切片Go封装

2020-10-23 08:38:19

Go语言

2020-12-27 10:15:44

Go语言channel管道

2021-10-09 07:10:31

Go语言基础

2021-10-30 10:43:04

语言Go函数

2021-11-03 10:02:07

Go基础函数

2020-12-07 05:59:02

语言Go接口

2021-09-29 10:00:07

Go语言基础

2021-10-13 10:00:52

Go语言基础

2020-10-22 11:15:47

Go语言变量

2021-10-16 10:17:51

Go语言数据类型

2020-12-23 08:39:11

Go语言基础技术

2021-02-20 10:06:14

语言文件操作

2021-01-13 08:40:04

Go语言文件操作
点赞
收藏

51CTO技术栈公众号