Go 的 rpc.Codec 接口必须同时实现 ReadRequestHeader 和 WriteRequestHeader,因为 RPC 调用严格依赖“头-体”分离的二阶段流程:先通过 header 获取服务名、方法名等元信息,再据此解析 body;缺一则在 server.ServeCodec 或 client.Go 中触发 panic。
为什么 Go 的 rpc.Codec 接口必须同时实现 ReadRequestHeader 和 WriteRequestHeader
因为 rpc 协议栈在一次调用中严格依赖“头-体”分离的二阶段解析:先读/写请求头(含服务名、方法名、序列化类型等元信息),再根据头里的指示去读/写具体参数体。如果只实现其中一个,server.servecodec 或 client.go 会在内部调用时 panic,错误通常是 nil pointer dereference 或 invalid memory address —— 实际是未实现方法返回了 nil 或未初始化的 error。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
-
ReadRequestHeader必须从连接中准确读取固定长度或带长度前缀的 header 字节,并反序列化到*rpc.Request;不要在这里读 body -
WriteRequestHeader要确保写入的 header 格式与对端ReadRequestHeader解析逻辑完全一致,包括字节序、字段顺序、空字符串/零值编码方式 - 两个方法共用的底层
io.ReadWriter需要加锁(如用sync.Mutex包裹),否则并发调用时会出现读写错位
如何让自定义 Codec 支持非 JSON 序列化(比如 Protocol Buffers)
Go 标准 rpc 包不绑定序列化格式,关键在于把 body 的编解码逻辑从 header 处理中剥离,并在 ReadRequestBody/WriteRequestBody 中交由 protobuf 的 proto.Marshal/proto.Unmarshal 处理。
常见错误现象:客户端发的是 proto.Message,但服务端收到后 panic 报 cannot unmarshal into *struct{} —— 这是因为你没在 ReadRequestBody 里显式构造目标类型的指针实例。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
-
ReadRequestBody的参数body interface{}是一个地址(如&MyReq{}),需用reflect.New(reflect.TypeOf(body).Elem().Elem()).Interface()动态创建对应 proto struct 的指针(注意双重Elem():因传入的是*T,而T本身是proto.Message) - 避免在
Codec内部硬编码 proto 文件路径或proto.Register,应由业务层提前注册proto.Message类型 - 若需兼容老 JSON RPC 客户端,可在 header 中增加
Content-Type字段(如"application/x-protobuf"),并在ReadRequestBody中分支处理
ServerCodec 和 ClientCodec 能复用同一套实现吗
可以,但必须满足:读写逻辑完全对称,且 header/body 的边界划分在两端一致。标准库中的 jsonrpc.NewClientCodec 和 jsonrpc.NewServerCodec 就是同一结构体的不同构造函数。
容易踩的坑:
- 误以为只要实现了
rpc.Codec接口就能直接传给rpc.NewClient—— 实际上rpc.NewClient只接受rpc.ClientCodec,它额外要求实现Close()方法;同理服务端需要rpc.ServerCodec(含Close()和Ready()) - 在
WriteRequestHeader中写了 header 却忘了 flush(尤其用bufio.Writer时),导致客户端卡在ReadRequestHeader死等 - 同一个
Codec实例被多个 goroutine 复用时,内部缓冲区(如bytes.Buffer)未重置,造成 body 数据污染
为什么 ReadRequestBody 有时读不到完整数据,但 WriteRequestBody 却正常
根本原因在于 TCP 是流式协议,而你的 ReadRequestBody 没做长度帧界定。标准 jsonrpc 用换行符分隔,gob 自带长度前缀,但自定义 codec 若直接 io.ReadFull(conn, buf),就会因网络延迟或分包导致读不满。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 在 header 中预留 4 字节 body length 字段(
binary.BigEndian.PutUint32),ReadRequestBody先读这 4 字节,再循环io.ReadFull直到读满指定长度 - 避免用
bufio.Scanner处理 body,它默认 64KB 缓冲上限,大消息直接报scanner over maximum capacity - 测试时用
net.Pipe()替代真实 TCP 连接,可精确控制写入节奏,快速复现分包问题
header 的设计粒度和 body 的帧定界方式,才是自定义 Codec 稳定性的真正分水岭,不是序列化格式本身。

















