为什么要处理粘包

由于tcp协议是数据流传输,一次读数据不一定能得到一个完整的业务数据包,所以需要进行粘包处理,保证要处理的数据是一个或者多个完成的业务数据包。

处理粘包的方法

假设:

  • 包的格式为 四字节包头 + 包体, 包头指明包体的长度
  • 大端传输数据

处理过程:

  • 先读取四字节包头,解析出包头的长度bodyLen
  • 再读取bodyLen长度的包体,这次读取的数据就是一个完整的业务包bodyData
  • 把bodyData返回到业务层处理

处理代码如下:

const (
	MAX_CONN_TIMEOUT = 30
	PACKET_HEAD_LEN  = 4
)

func Recv(conn net.Conn) ([]byte, error) {
	//读取包头
	head := make([]byte, PACKET_HEAD_LEN)
	_ = c.Conn.SetReadDeadline(time.Now().Add(time.Second * MAX_CONN_TIMEOUT))
	_, err := io.ReadFull(conn, head)
	if nil != err {
		log.Error("io.ReadFull() failed, error[%s]", err.Error())
		return nil, err
	}
	bodyLen := binary.BigEndian.Uint32(head) 

	//读取包体
	bodyData := make([]byte, bodyLen)
	_ = c.Conn.SetReadDeadline(time.Now().Add(time.Second * MAX_CONN_TIMEOUT))
	_, err = io.ReadFull(conn, bodyData)
	if nil != err {
		log.Error("io.ReadFull() failed, error[%s]", err.Error())
		return nil, err
	}

	return bodyData, nil
}

总结

  • 每调用一次Recv函数就可以得到一个完整的业务包,返回到业务层进行处理
  • 代码中设置了超时时间,这个根据实际业务需要来设置,有些业务场景不设置也可以。如果不设置,那么读不够数据就会一直阻塞在那里。关于设置的超时时间的大小,也是根据实际业务场景,比如有的客户端与服务端之间是10秒一次心跳,那么超时时间设置就要大于10秒,比如20秒或者30秒等等。
  • Recv返回后,应当判断error是否为nil,进行错误处理,比如可能网络出错需要重新连接。