net/rpc 的 ServerCodec 接口不仅替换序列化格式,还需完整实现帧读写、Header/Body分离解析、错误传播及TCP粘包处理,否则会导致连接断开或响应错乱。

直接说结论:net/rpc 的 ServerCodec 接口不是“换序列化格式”那么简单,它强制你接管整个帧读写逻辑,包括消息边界、Header 解析、错误传播,漏掉任一环都会导致连接静默断开或响应乱序。
为什么实现 ServerCodec 后 RPC 调用直接卡住或报 invalid frame size
因为 ServerCodec 不是只负责 json.Marshal/Unmarshal,它必须严格模拟 RPC 协议的两段式结构:先读/写 RequestHeader(含方法名、序列号、错误标记),再读/写 RequestBody(参数)或 ResponseBody(返回值)。标准 gob Codec 里这些字段是隐式编码的,换成自定义实现时,你得手动拆解并确保字节顺序完全一致。
- 常见错误:在
WriteRequest中直接json.Marshal(req)一整坨发出去 → 缺少 Header 分界,服务端ReadRequestHeader读不到方法名,直接丢弃连接 - 正确做法:Header 和 Body 必须分离序列化。例如用
map[string]interface{}先写{"method":"Calc.Add","seq":123},再单独写参数字节;接收端也得分两次读、两次解 - Header 字段名不能随意改:
method、seq、error是 net/rpc 内部约定,改了客户端就无法识别调用上下文
ReadRequestHeader 怎么安全处理 TCP 粘包
不能对底层 conn.Read() 做裸调用,必须配合长度前缀和 io.ReadFull。否则一次 Read 可能只拿到半个 Header,下一次又读到拼在一起的两条完整消息。
- 先读固定长度头(如 4 字节大端长度)→
io.ReadFull(conn, lengthBuf[:]),失败即断连 - 再按该长度读完整帧 →
io.ReadFull(conn, frameBuf),失败同样返回 error - 然后从
frameBuf中解析 Header:如果用 JSON,需json.Unmarshal(frameBuf, &header);如果用 Protobuf,需proto.Unmarshal(frameBuf, &pbHeader) - 切忌用
bufio.Scanner或bytes.Split处理二进制帧——它们面向文本,遇到 \x00 就截断
序列化选 json 还是 protobuf?关键看这三点
别被“JSON 可读”带偏,线上 RPC 对序列化的要求远不止“能看清”。真正影响稳定性的,是字段兼容性、类型精度、以及 Header/Body 是否共用同一套 schema。
立即学习“go语言免费学习笔记(深入)”;
-
json:适合调试期或跨语言简单调用,但必须加json:",string"处理 int64 时间戳,且nilmap/slice 默认不输出,容易和服务端字段缺失逻辑冲突 -
protobuf:推荐用于生产,但别直接用proto.Message当 RPC 消息体——Header 和 Body 应分属不同 proto message,避免service_method字段被误序列化进参数体 - 统一用
msgpack?可以,但它不自带 schema,字段增删必须靠版本号+默认值兜底,否则老客户端读新服务端消息会 panic
WriteResponse 返回 error 时,为什么客户端收不到异常信息
因为 ServerCodec.WriteResponse 的 error 参数不是传给客户端的业务错误,而是 Codec 层自身的 I/O 错误(比如网络断开、压缩失败)。真正的业务 error 必须塞进 ResponseHeader 的 error 字段,并且要确保该字段在序列化后仍能被客户端 ReadResponseHeader 正确提取。
- 错误写法:在
WriteResponse里直接return fmt.Errorf("write failed")→ 客户端看到的是rpc: server cannot decode request,完全不知错在哪 - 正确写法:构造
responseHeader := &rpc.Response{ServiceMethod: ..., Seq: ..., Error: "user not found"},然后序列化该 header 并写入连接 - 注意:Error 字符串不能含换行或控制字符,否则 JSON 解析失败;protobuf 则建议用
google.rpc.Status替代原始字符串
最易被忽略的一点:所有自定义 ServerCodec 实现,都必须保证 ReadRequestHeader 和 WriteResponse 对 seq 字段的处理完全对称。哪怕只是多一个空格、少一个引号,客户端的请求-响应匹配就会失效,表现为超时或返回上一次调用的结果。


















