go标准库net/rpc不处理粘包,因其依赖裸字节流net.conn;gob.codec通过decode()原子读取完整gob值并缓冲多余数据隐式应对,但要求独占decoder且禁用混用原始read()。

Go标准库net/rpc本身不处理粘包,必须由Codec或上层协议显式应对;直接裸用net.Conn读写必然出错。
为什么gob.Codec看似“自动”处理粘包
它不是真自动,而是靠gob.Decoder.Decode()的原子性隐式缓冲:每次调用都阻塞等待一个完整gob结构(含magic header、type table、value),半包就等,多出来的字节留在内部bufio.Reader缓冲区里,下次Decode()直接复用。
但这个机制有硬前提:
- 不能在同一个
conn上混用gob.Decoder和原始conn.Read()——buffer状态立刻错乱 - 每个连接必须独占一个
gob.Decoder实例,禁止多个goroutine共用 - 业务若需插入心跳帧、控制指令等非gob数据,
gob完全不支持——没扩展字段,得换自定义协议
自定义封包必须带长度头,且用uint32大端序
固定4字节binary.BigEndian.PutUint32(buf[:4], uint32(len(body)))打头,body长度不含头部自身。别用int——32位系统是4字节,64位下是8字节,跨平台解包直接失败。
写入和读取都必须原子:
- 写入不能分两次
conn.Write(header); conn.Write(body),得用io.MultiWriter或append(header, body...)一次性Write() - 读取必须先
io.ReadFull(conn, header[:]),再按解析出的length申请buffer,再io.ReadFull(conn, body);绝不能用conn.Read()自己拼 - 解包前必须校验
length > MaxBodySize(如10MB),超限立刻conn.Close(),防OOM
gob或jsonrpc不能绕过粘包问题
它们只是序列化格式,底层仍走net.Conn裸字节流,照样粘包拆包。区别只在封装层级:gob把粘包逻辑下沉到Decoder内部,jsonrpc则依赖HTTP分帧(实际是靠http.Server的request边界)。
但如果你用jsonrpc走TCP裸连(非HTTP),一样要自己加长度头——net/rpc/jsonrpc.NewClient默认不处理TCP粘包。
goroutine泄漏最常发生在解包I/O生命周期失控时
典型场景不是逻辑错误,而是:
- 没设
conn.SetReadDeadline(),io.ReadFull()卡死在半包上,永远不返回 - 收到非法长度(如
0xffffffff)后没主动断连,后续ReadFull()持续阻塞 - 用
bytes.Buffer暂存未消费字节时,每次新连接都新建buffer、反复append小片段,触发高频GC,拖慢整体吞吐
真正健壮的实现,必须让每个连接goroutine绑定context.WithTimeout(),并在所有I/O入口处检查ctx.Err(),而不是只靠超时被动回收。










