Go标准库rpc不直接解决粘包,因其依赖底层net.Conn裸字节流,未内置长度头或分隔符解析;gob.Codec通过Decode()原子读取完整gob值并缓冲多余数据来隐式应对,但要求独占Decoder且禁用混用原始Read()。
为什么Go标准库rpc不直接解决粘包
因为net/rpc本身不处理传输层边界——它依赖底层io.readwriter,而net.conn就是裸字节流。你传进去一个bufio.reader或bytes.buffer,它就按那个读;没做任何长度头解析或分隔符切分。所以一旦客户端连续调用两次client.call(),服务端server.serveconn()很可能在一次read()里拿到两个完整rpc帧,直接panic。
gob.Codec内部怎么应对半包/粘包
gob.NewEncoder()和gob.NewDecoder()本身不解决粘包,但它们的Encode()/Decode()方法是原子的:每次调用都尝试读/写一个完整gob值。关键在于——Decoder.Decode()会阻塞直到凑够一个合法gob结构(含magic header、type table、value data),如果只收到半包,它就卡在io.ReadFull()里等后续数据;如果收到两个gob,第二个会留在buffer里,下次Decode()直接复用。这相当于把粘包“下沉”到gob协议栈内部处理,但前提是:你不能自己对conn乱Read(),必须让Decoder全程掌控读取节奏。
- 别在同一个conn上混用
gob.Decoder和原始conn.Read(),buffer状态会错乱 - 每个连接必须独占一个
gob.Decoder实例,不能多个goroutine共用 - 如果业务需要自定义心跳或控制帧,gob就不适合——它没有预留扩展字段,得换自定义协议
自定义RPC协议封包时长度字段怎么设才安全
用uint32大端序(binary.BigEndian.PutUint32)打头,4字节固定长度,覆盖0~4GB范围,够绝大多数RPC场景。别用int——32位系统下是4字节,64位下是8字节,跨平台直接解包失败。
- 长度字段只表示body长度,不含头部自身,避免嵌套计算
- 解包时先检查
length > MaxBodySize(比如10MB),超限立刻conn.Close(),防止OOM - 写入必须原子:
conn.Write(header); conn.Write(body)可能被中断,要用io.MultiWriter或一次性append(header, body...)再Write() - 读取必须循环:
io.ReadFull(conn, header[:])之后,再用io.ReadFull(conn, body),不能用conn.Read()自己拼
goroutine泄漏最常发生在哪几个环节
典型泄漏点不是逻辑写错,而是I/O生命周期没管住:一个连接goroutine启动后,没设conn.SetReadDeadline(),又在io.ReadFull()卡死,或者解包时遇到非法长度(比如length=0xffffffff)没断连,导致该goroutine永久阻塞。
- 每个连接goroutine必须绑定
context.WithTimeout(),超时后强制关闭conn - 缓冲区别用
bytes.Buffer反复Reset(),改用预分配切片+游标(start/end索引),避免GC压力 - 解包函数返回签名必须是
([]byte, []byte, error):第一个是完整包,第二个是剩余未消费字节(用于处理粘包),error为解析失败原因——漏掉第二个返回值,粘包就永远积压
真正难的不是写对一个包,而是让成百上千个连接在高并发、弱网络、异常数据下都不泄漏、不卡死、不误切。长度头协议只是骨架,I/O超时、buffer管理、错误隔离才是血肉。











