不能直接用 io.ReadFull 读完就解析,因真实网络中一次读可能含多个包、半包或跨包碎片,需改用 *bytes.Buffer 累积→切分→循环解析,并配以读超时与原子写保障。

io.ReadFull 是解包的起点,但只用它两次读头读体远远不够——真实网络里一次 conn.Read 可能带回多个完整包、半个包、甚至跨包碎片。必须把解包逻辑从“单次读取→解析”改成“累积→切分→循环”。
为什么不能直接用 io.ReadFull 读完就解析
常见错误是写个固定流程:io.ReadFull(conn, header) → 解出长度 → io.ReadFull(conn, body) → 解析。这在理想单包场景下能跑通,但实际会立刻崩:
- 一次
conn.Read返回 2048 字节,可能含 3 个完整消息 + 半个新消息;你只处理第一个,剩下 1800+ 字节被丢弃或卡死 - 上一个包解析后 buffer 剩余 3 字节,下次
io.ReadFull仍从这 3 字节开始读,导致头被截成[0x00 0x00],binary.BigEndian.Uint32解出超大非法长度 -
io.ReadFull遇到连接关闭会返回io.EOF,但缓冲区里还有未消费字节,直接退出等于丢数据
必须用 *bytes.Buffer 滚动解析剩余字节
每个连接独享一个 *bytes.Buffer,所有读 goroutine 收到新数据后无条件 buffer.Write(data),解包逻辑只从这个 buffer 里切,不碰原始 conn:
- 循环条件是
buffer.Len() >= 4,先 peek 头部 4 字节 → 解出msgLen - 若
buffer.Len() ,说明包不完整,跳出等下次数据 - 合法长度要校验上限(比如 >1MB 就断连),防 OOM 或恶意构造
- 用
buffer.Next(4 + int(msgLen))拿出完整包,前 4 字节是头,后面是 payload - 切完继续循环,直到 buffer 不够一个头为止
发送端写法错一个字节序就跨语言失联
Go 和 Java/Python 通信时,binary.BigEndian.PutUint32 漏掉 BigEndian 是高频翻车点:
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
立即学习“go语言免费学习笔记(深入)”;
- 发送端必须用
binary.BigEndian.PutUint32(header[:], uint32(len(payload))),不能用binary.LittleEndian或本地序 - 写入必须原子:先
io.WriteFull(conn, header[:])成功,再io.WriteFull(conn, payload);中间失败会导致接收端永远卡在读头 - 长度字段必须是
uint32,不是int32—— 后者在 64 位系统下可能被解释为 8 字节,协议直接错乱
没设 conn.SetReadDeadline 的粘包逻辑迟早泄漏 goroutine
连接异常断开但未发 FIN,或客户端静默掉线,conn.Read 会永久阻塞,goroutine 卡住不释放:
- 在
accept后立即设置:conn.SetReadDeadline(time.Now().Add(30 * time.Second)) - 每次成功读取后要重置 deadline,否则 30 秒后整个连接被 kill
- 别依赖
SetReadDeadline来“等包齐”——它只控制单次 I/O 超时,不是业务层超时机制 - 最易被忽略的是:buffer 里剩半包时连接断了,
io.ReadFull返回io.ErrUnexpectedEOF,但没人清理 buffer 和 goroutine,残留状态越积越多
真正难的不是第一次读出完整包,而是 buffer 里永远有“剩下的那半截”。只要没把剩余字节滚进下一轮切分,粘包问题就还在。

















