必须用io.ReadFull读取长度头,因其确保读满指定字节数,避免net.Conn.Read只读部分字节导致binary.BigEndian.Uint32 panic或解析错误;否则将引发unexpected EOF、超大内存分配或负长度panic。

为什么 io.ReadFull 是解析长度头的刚性要求
因为 net.Conn.Read 不保证一次读满指定字节数,只返回当前缓冲区里“刚好有的数据”。如果直接用 Read 读 4 字节头部,很可能只读到 1、2 或 3 字节就返回,后续 binary.BigEndian.Uint32 会 panic 或解析出错误长度。而 io.ReadFull 会阻塞直到填满目标 slice,或返回 io.ErrUnexpectedEOF(数据不足)或 io.EOF(连接关闭),这是唯一能安全提取固定长度头的方式。
常见错误现象:binary.Read 报 unexpected EOF;Uint32 解析出极大/极小值,导致后续分配超大内存或负长度 panic。
- 必须对头长度使用
io.ReadFull,不能用Read+ 循环补读——那是自己重造ReadFull - 头部长度统一用 4 字节
uint32,避免跨平台字长差异 - 服务端和客户端必须约定同一字节序,
binary.BigEndian是跨语言通信事实标准
如何用 bytes.Buffer 处理粘包+半包混合场景
真实网络中一次 conn.Read 可能含:0 个完整包 + 半包,或 1 个完整包 + 半包,或多个完整包 + 半包。靠单次读取 + 单次解析必然失败,必须引入应用层缓冲区持续累积、滚动消费。
推荐用 bytes.Buffer 而非裸 []byte + 游标,因它自带 Write、Len、Next、Read 等语义清晰的操作,且并发不安全这点反而是优势——每个连接独享一个 Buffer,无需锁。
- 读 goroutine 每次
conn.Read(buf)后立即buffer.Write(buf) - 解包 goroutine 循环检查:
buffer.Len() >= 4→ 提取头 →buffer.Len() >= 4 + length→ 截出完整包 →buffer.Next(4 + length) - 若不满足完整包条件,
break等下次数据到达,不阻塞、不丢弃、不假设 - 切忌在
buffer上做Read操作来取头——Read会移动内部 offset,破坏后续判断
unpack 函数必须处理的三个边界错误
解包函数不是“拿到数据就解析”,而是要主动识别并响应三种典型失败路径,否则上线后会静默丢包或 panic。
buffer.Len() :连头都不够,直接返回 <code>io.ErrUnexpectedEOF,让调用方继续等待-
int(msgLen) > buffer.Len():头说 body 要 1MB,但 buffer 当前只有 10KB,说明 body 还没到齐,此时必须buffer.UnreadByte()(或更稳妥地:不做任何读操作,保留全部原始字节),然后返回io.ErrUnexpectedEOF -
msgLen > 1024*1024(如 1MB):防恶意构造超大长度头触发 OOM,应提前拒绝,返回明确 error
注意:io.ErrUnexpectedEOF 表示“数据还没来齐”,可重试;io.EOF 表示连接已关闭,应终止循环。
为什么永远别用 bufio.Scanner 或 ReadString 做协议解析
它们本质是分隔符驱动,而业务数据几乎必然含分隔符:JSON 的 "\n"、日志里的换行、用户输入的 "[END]"、甚至 emoji 中的 UTF-8 多字节序列都可能被误判为分隔点。这不是概率问题,是确定性翻车。
典型现象:ReadString('\n') 阻塞数秒后突然返回包含 3 条消息的字符串;或把一条带换行的 JSON 拆成两段,第二段 json.Unmarshal 直接报 invalid character。
- 除非协议明确定义“每条消息以 \n 结尾且业务数据已转义”,否则禁用所有基于分隔符的读法
-
bufio.Reader.ReadBytes同样危险,它只是把ReadString的 string 换成 []byte,没解决根本问题 - 真要兼容文本协议(如 HTTP/SMTP),应先用长度头或状态机识别帧边界,再交由专用解析器处理内容
最易被忽略的一点:解包逻辑必须和 I/O 生命周期严格绑定。缓冲区不能跨连接复用,不能在 goroutine 退出时遗忘 buffer.Reset(),也不能在错误路径下跳过 buffer.Next 导致残留字节污染下一次解包——这些都不会立刻报错,但会在高并发、长连接场景下缓慢积累成偶发丢包或错包。


















