Go的net.Conn读不到完整消息是因为TCP是字节流协议,Read()不保证一次读完业务消息,需自行处理粘包/拆包;推荐用bufio.Reader+分隔符或长度前缀方案,并设超时、校验与错误恢复。
为什么 Go 的 net.Conn 读不到完整消息?
因为 tcp 是字节流协议,read() 返回的只是当前缓冲区里“恰好有的数据”,不保证一次读完一个业务消息。你发了两个 "hello" 和 "world",接收端可能一次读到 "hell",下一次才读到 "oworld" —— 这就是粘包/拆包。go 标准库不自动处理这个,得你自己定界。
用 bufio.Reader + 自定义分隔符最轻量
适合消息以固定字符结尾(比如 "\n" 或 "\r\n")的场景,比如日志推送、简单协议交互。别直接用 conn.Read() 循环拼接,容易阻塞或逻辑错乱。
实操建议:
- 用
bufio.NewReader(conn)包一层,再调ReadString('\n')或ReadBytes('\n') - 注意
ReadString()会把分隔符一起返回,记得strings.TrimSuffix(s, "\n") - 如果对方不发换行符,或消息体本身含
\n,这个方案立刻失效 —— 别硬扛,换长度前缀 - 超时必须设:在
conn.SetReadDeadline()后再调ReadString(),否则可能永久阻塞
用长度前缀(Length-Prefixed)才是通用解法
每个消息开头写 4 字节(或 2/8 字节)表示后续内容长度,接收端先读够长度字段,再读指定字节数。这是 RPC、gRPC、Protobuf 通信的基础,兼容性强、无歧义。
实操建议:
- 发送端:用
binary.Write(w, binary.BigEndian, uint32(len(data)))写长度,再写data - 接收端:先
io.ReadFull(r, header[:])读 4 字节 header,再io.ReadFull(r, buf[:n])读正文 - 别用
conn.Read()多次尝试凑够长度 ——io.ReadFull()会自动循环直到读满或出错,更安全 - 长度字段溢出要检查:
if n > 1024*1024 { return errors.New("message too large") }
封装成可复用的 PacketConn 类型要注意什么?
别直接嵌入 net.Conn,而是组合一个读写器,并暴露 ReadPacket() / WritePacket() 方法。关键点不在结构,而在错误处理和边界控制。
容易踩的坑:
- 不要在
ReadPacket()里 new 每次都分配切片 —— 预分配一个buf [4096]byte复用,避免 GC 压力 -
WritePacket()必须用io.WriteFull()而非conn.Write(),否则短写(short write)会导致长度字段和正文错位 - 连接关闭时,
ReadPacket()可能返回io.EOF,但上层业务需区分“正常断连”和“读到一半断开”,后者应视为协议错误 - 并发读写同一连接?不行。TCP 连接不是线程安全的,要么加锁,要么用
gorilla/websocket这类已封装好的库
真正难的从来不是怎么读完 4 个字节,而是当客户端突然断电、中间网络设备重置连接、或恶意发超长 length 字段时,你的代码是否还在安静地循环等待下一个字节 —— 这些地方必须有明确的超时、校验和 panic 恢复机制。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











