golang网关(4)-- 粘包解包

313 阅读2分钟

为什么会粘包

TCP(Transmission Control Protocol)传输控制协议,是一种面向连接的、可靠的、基于字节流的传输层通信协议。

其中跟粘包关系最大的就是基于字节流这个特点。字节流可以理解为一个双向的通道里流淌的数据,这个数据其实就是我们常说的二进制数据,简单来说就是一大堆 01 串。这些 01 串之间没有任何边界。

发送端原因

发送端在组装消息的时候,就把几个小包合成一包了,这样接收端自然无法解析出小包。

接收端原因

接收端接收到消息以后,应用层总是不能立即取走数据,总是会有接收缓冲区的存在。如果两条独立的消息进入缓冲区的间隔太小,应用层不能在两次消息中间取走上一条消息,那么下次读取的时候,就势必会把两包消息同时读出来,这也会导致粘包。

如何解决

通过自定义协议(如http协议),确定包头(分割符),包长度等方式,来处理粘包。

自定义协议

涉及到内部机密,不方便表出,大概如下:

  1. FA为分割符
  2. 协议包含数据长度,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
}