为什么会粘包
TCP(Transmission Control Protocol)传输控制协议,是一种面向连接的、可靠的、基于字节流的传输层通信协议。
其中跟粘包关系最大的就是基于字节流这个特点。字节流可以理解为一个双向的通道里流淌的数据,这个数据其实就是我们常说的二进制数据,简单来说就是一大堆 01 串。这些 01 串之间没有任何边界。
发送端原因
发送端在组装消息的时候,就把几个小包合成一包了,这样接收端自然无法解析出小包。
接收端原因
接收端接收到消息以后,应用层总是不能立即取走数据,总是会有接收缓冲区的存在。如果两条独立的消息进入缓冲区的间隔太小,应用层不能在两次消息中间取走上一条消息,那么下次读取的时候,就势必会把两包消息同时读出来,这也会导致粘包。
如何解决
通过自定义协议(如http协议),确定包头(分割符),包长度等方式,来处理粘包。
自定义协议
涉及到内部机密,不方便表出,大概如下:
- FA为分割符
- 协议包含数据长度,CRC检验,时间戳,数据等
上代码
// CheckBuffer 检查长度,处理TCP粘包,分割出每一帧数据
func CheckBuffer(bufferByte []byte, chBuffer chan []byte) []byte {
emptyByte := make([]byte, 0)
bufferByteLen := len(bufferByte)
i := 0
// 已经成功解析了几条协议
times := 1
for i = 0; i < bufferByteLen; i++ {
buffer := hex.EncodeToString(bufferByte[i:])
headPos := strings.Index(buffer, def.Frame) // 是否含有“FA”
if headPos == -1 {
return emptyByte
}
if headPos > 0 {
i = i + headPos
continue
}
bufferStrLen := len(buffer)
// 解析协议
// .....
// .....
chBuffer <- bufferByte[i:end]
// 下一个包的起始位置
i = end - 1
// 解析成功的次数+1
times++
}
if i >= bufferByteLen {
return emptyByte
}
// 返回剩下的数据
return bufferByte[i:]
}
// Decode 解码协议
func Decode(buffer string) *model.Request {
req := &model.Request{}
req.BufferHex = buffer
req.Code = enum.MsgOK
if str.Empty(buffer) {
req.Code = enum.MsgEmpty
req.Msg = "[数据流为空]"
return req
}
headPos := strings.Index(buffer, def.Frame)
if headPos == -1 {
req.Code = enum.MsgHeadErr
req.Msg = "[找不到帧头]"
return req
}
// 协议内容具体的解析,CRC判断等等
// ...
// ...
return req
}