Go处理TCP粘包必须在应用层定义消息边界,推荐用4字节大端序长度前缀协议:发送端先写长度再写payload,接收端用io.ReadFull两次分别读头和体,并循环解析缓冲区剩余字节,同时设读超时防goroutine泄漏。

Go 的 net.Conn.Read 从不保证一次只读一个应用层消息——它只按内核缓冲区当前有多少字节就返回多少,所以粘包和拆包不是 bug,是 TCP 的必然行为。必须在应用层定义并解析消息边界,否则 json.Unmarshal 报 invalid character、结构体字段全零、binary.Read 卡在 EOF,都是迟早的事。
为什么不能依赖 bufio.Scanner 或 ReadString
这些工具只适合纯文本、有明确分隔符(比如 \n)的协议。一旦数据里含未转义的换行、JSON 字段带 \n、用户输入含表情或代码块,Scanner.Scan() 就会提前截断;ReadString('\n') 则可能永远阻塞——因为二进制 payload 根本不含 \n。
- 它们内部靠不断扩容 buffer 扫描,性能差,且无法表达空消息(长度为 0)
- 不解决“头被截半”或“多个包拼一起”的根本问题,只是把问题藏得更深
- 调试时看似正常,压测或长连接跑几小时后开始丢包、panic
用 binary.Write + io.ReadFull 实现长度前缀协议
最可控、跨语言兼容性最好的方式:每个消息以 4 字节大端序 uint32 开头,表示后续 payload 长度。
- 发送端必须用
binary.BigEndian,漏掉会按本地字节序写,跨平台必错 - 别用
int存长度——32 位系统下是 4 字节,64 位下是 8 字节,不一致 - 写入要原子:先
binary.Write(conn, binary.BigEndian, length),再conn.Write(payload),不能只写 header 就中断 - 接收端先
io.ReadFull(conn, header)读满 4 字节,再io.ReadFull(conn, data)读指定长度,两次都必须用ReadFull,不能只用Read
解包逻辑必须处理剩余字节和循环解析
一次 Read 可能含:1 个完整包 + 半个包,或 3 个完整包 + 半个包。不能假设“读一次就一个包”。
立即学习“go语言免费学习笔记(深入)”;
- 用
*bytes.Buffer或切片游标暂存未消费字节,别每次重开 buffer - 解包函数应返回
([]byte, []byte, error):第一个是完整包,第二个是剩余未消费字节 - 遇到非法长度(如 >1MB)立刻断连,别等
ReadFull超时,防止 OOM 或 goroutine 泄漏 - 务必在
conn.SetReadDeadline设置超时(例如 30 秒),避免单个坏连接拖垮整个连接池
最容易被忽略的是“剩余字节”处理——很多实现只顾读完一个包就丢弃缓冲区,结果粘包里的后半截永远卡在内存里;还有就是忘记设读超时,导致某个异常连接长期占着 goroutine 不释放。这两个点不上生产环境很难暴露,但一出问题就是雪崩级。


















