网络编程
位置:首页>> 网络编程>> Go语言>> Golang通过包长协议处理TCP粘包的问题解决

Golang通过包长协议处理TCP粘包的问题解决

作者:地下十一楼的森琦  发布时间:2024-04-30 10:00:11 

标签:Golang,TCP,粘包

tcp粘包产生的原因这里就不说了,因为大家能搜索TCP粘包的处理方法,想必大概对TCP粘包有了一定了解,所以我们直接从处理思路开始讲起

Golang通过包长协议处理TCP粘包的问题解决

tcp粘包现象代码重现

首先,我们来重现一下TCP粘包,然后再此基础之上解决粘包的问题,这里给出了client和server的示例代码如下

/*
   文件名:client.go
   client客户端的示例代码(未处理粘包问题)
   通过无限循环无时间间隔发送数据给server服务器
   server将会不间断的出现TCP粘包问题
*/
package main
import (
   "fmt"
   "net"
)
func main() {
   conn, err := net.Dial("tcp", ":9000")
   if err != nil {
       return
   }
   defer conn.Close()
   for {
       s := "Hello, Server!"
       n, err := conn.Write([]byte(s))
       if err != nil {
           fmt.Println("Error:", err)
           fmt.Println("Error N:", n)
           return
       }
       // 这里通过限制发送频率和时间间隔来解决TCP粘包
       // 虽然能够实现,但是频率被限制,效率也会被限制
       // time.Sleep(time.Second * 1)
   }
}
/*
   文件名:server.go
   server服务端的示例代码(未处理粘包问题)
   服务端接收到数据后立即打印
   此时将会不间断的出现TCP粘包问题
*/
package main
import (
   "fmt"
   "net"
)
func main() {
   ln, err := net.Listen("tcp", ":9000")
   if err != nil {
       return
   }
   for {
       conn, err := ln.Accept()
       if err != nil {
           continue
       }
       go handleConnection(conn)
   }
}
func handleConnection(conn net.Conn) {
   defer conn.Close()
   tmp := []byte{}
   for {
       buf := make([]byte, 1024)
       n, err := conn.Read(buf)
       if err != nil {
           fmt.Println("Read Error:", err)
           fmt.Println("Read N:", n)
           return
       }
       fmt.Println(string(buf))
   }
}

按顺序启动server.go和client.go,正常情况下每行会输出Hello, World!字样,出现TCP粘包后,将会出现类似Hello, World!Hello之类的字样,后一个包粘到前一个包了

解决TCP粘包有很多种方法,归结起来就是通过自定义通讯协议来解决,例如分隔符协议、MQTT协议、包长协议等等,而我们这里介绍的就是通过包长协议来解决问题的,当然包长协议也有很多种自定义的方法

通过演示的结果,我们可以看出来,后一个包粘到了前一个包,而且后一个包不一定是一个完整的包,也很有可能第一次收到的数据包也不是完整的数据包

tcp粘包问题处理方法

这样我们就有必要校验每次收到的数据包是否是我们期望收到的,比较直观的,客户端和服务端双方协商某种协议,例如包长协议,在客户端发送数据时,先计算一下数据的长度(假设用2字节的uint16表示),然后将计算得到的长度和实际的数据组装成一个包,最后发送给服务端;而服务端接收到数据时,先读取2字节的数据长度信息(可能不足2字节,程序需要针对这种情况设计),然后根据数据长度来读取后边的数据(可能会存在数据过剩、数据刚好、数据不足等情况,程序需要针对这些情况设计)

有了思路之后,我们就需要对发送端和接收端的数据进行处理了,因为发送端较为简单,不需要考虑其他情况,只管封装数据包发送,所以这里我们先对发送端client进行处理

/*
   文件名:client.go
   使用包长协议,封装TCP包并循环发送给server服务端
*/
package main
import (
   "encoding/binary"
   "fmt"
   "net"
)
func main() {
   conn, err := net.Dial("tcp", ":9000")
   if err != nil {
       return
   }
   defer conn.Close()
   for {
       s := "Hello, Server!"
       sbytes := make([]byte, 2+len(s))
       binary.BigEndian.PutUint16(sbytes, uint16(len(s)))
       copy(sbytes[2:], []byte(s))
       n, err := conn.Write(sbytes)
       if err != nil {
           fmt.Println("Error:", err)
           fmt.Println("Error N:", n)
           return
       }
       // time.Sleep(time.Second * 1)
   }
}

按照我们的思路,首先使用len()函数计算出待发送字符串的长度,然后使用make()函数创建一个[]byte切片作为待组装发送的数据包缓存sbyte,长度就是2字节的包头+字符串的长度,接着通过binary.BigEndian.PutUint16()函数来对数据包缓存sbyte进行操作,将字符串的长度信息写入2字节的包头中,紧接着又通过copy()完成封包组装,最后通过conn.Write()将封包发送出去,这样子发送出去的数据大概长成下面的样子

[0][14][H][e][l][l][o][,][ ][S][e][r][v][e][r][!]

其中,封包整体长16bytes,Hello, Server!则长14bytes

来源:https://blog.csdn.net/wudics/article/details/125324371

0
投稿

猜你喜欢

手机版 网络编程 asp之家 www.aspxhome.com