TCP是字节流,Read()返回当前缓冲区数据,可能拆包或粘包;需先用io.ReadFull读固定长度包头解析长度,再读对应body,注意统一字节序。

为什么 net.Conn 读出来的数据总对不上协议长度?
因为 TCP 是字节流,不是消息队列。Read() 返回的只是当前内核缓冲区里“刚好有的数据”,可能一次读到半个包、两个半包,或跨多次调用才凑够一个完整包。这不是 bug,是 TCP 本来的样子。
常见错误现象:json.Unmarshal() 报 invalid character;自定义协议解析时 binary.Read() 读到一半就 EOF;服务端收包逻辑偶发 panic。
- 别在
Read()后直接解析,先攒够包头(比如前 4 字节是 payload 长度) - 用
io.ReadFull()读固定长度头,避免只读到部分头字段 - 头解析完再用
io.ReadFull()读指定长度 body,否则可能少读或多读 - 注意字节序:服务端和客户端必须统一用
binary.BigEndian或binary.LittleEndian
怎么写一个带长度头的封包函数?
最常用的是「4 字节大端长度 + 原始数据」格式,兼容性好、实现简单、调试直观。
使用场景:RPC 消息、心跳包、自定义二进制协议传输。
立即学习“go语言免费学习笔记(深入)”;
func Encode(data []byte) []byte {
length := uint32(len(data))
buf := make([]byte, 4+len(data))
binary.BigEndian.PutUint32(buf[:4], length)
copy(buf[4:], data)
return buf
}
- 别用
int存长度——32 位系统下int是 4 字节,64 位下是 8 字节,不跨平台 - 长度字段本身不加密不压缩,否则无法预判后续要读多少字节
- 如果业务允许,长度字段可设上限(如
MaxBodySize = 1 ),防止 OOM
解包逻辑卡死或 goroutine 泄漏怎么办?
典型原因是没处理粘包时的“剩余字节”,或 Read() 阻塞在不完整的包上,又没设超时。
性能影响:每次连接都新建 buffer、反复 append 小片段,会触发大量 GC。
- 用循环 buffer(如
bytes.Buffer或切片游标)暂存未消费字节,不要每次重开 - 在
conn.SetReadDeadline()设置合理超时(比如 30 秒),避免单个坏连接拖垮整个连接池 - 解包函数返回
([]byte, []byte, error):第一个是完整包,第二个是剩余未消费字节,error 是解析失败原因 - 遇到非法长度(比如 >1MB)立刻断连,别等
ReadFull()超时
用 gob 或 json 能绕过粘包问题吗?
不能。它们只是序列化格式,底层仍走 net.Conn,照样面临粘包/拆包。
但可以简化逻辑:比如 gob.Encoder/Decoder 内部自带分隔机制,只要确保每次 Encode() / Decode() 对应一个完整值即可。
-
json.Decoder的Decode()方法会自动跳过空白、等待完整 JSON 对象,适合单次小消息,但不适用于流式大数据或自定义二进制协议 -
gob要求两端结构体一致,且不兼容其他语言,调试困难,线上出问题难排查 - 真正省事的做法是封装一层
PacketConn接口,把封包/解包逻辑收拢,而不是依赖序列化库的“隐式分包”
复杂点在于边界条件:连接中断时缓冲区残留字节怎么清理、多路复用下不同请求的包如何隔离、长连接中保活包和业务包怎么共存——这些没法靠一个库自动解决,得自己想清楚状态机怎么转。










