主题
Day 1 — 消息编解码(Codec)
学习目标
理解 RPC 协议设计的基本思路,实现序列化/反序列化层,为后续的 Server/Client 通信奠定基础。
核心概念
为什么需要自定义协议?
RPC 框架需要在网络上传输「调用哪个方法」和「参数是什么」两部分信息。我们设计了一个简单但完整的二层消息结构:
- Header:携带元信息(调用方法名、序列号、错误信息)
- Body:携带实际的参数/返回值数据
连接建立时,客户端先发送一个 JSON 编码的 Option,其中包含 MagicNumber(用于校验协议合法性)和 CodecType(协商使用哪种编解码方式)。
Codec 接口
go
type Codec interface {
io.Closer
ReadHeader(*Header) error
ReadBody(interface{}) error
Write(*Header, interface{}) error
}通过接口抽象,我们可以轻松切换不同的编解码实现,目前提供三种:
- GobCodec:使用 Go 原生的
encoding/gob,性能最优,但仅限 Go 语言间通信 - JsonCodec:使用
encoding/json,通用性好,便于调试,但性能较低 - ProtobufCodec:使用
google.golang.org/protobuf,跨语言生产首选,Schema 驱动
三种 Codec 对比
| 维度 | GobCodec | JsonCodec | ProtobufCodec |
|---|---|---|---|
| 编码格式 | Go 私有二进制 | 文本 JSON | 二进制 Protobuf |
| 跨语言 | 仅 Go | 通用 | 通用 |
| 可读性 | 不可读 | 人类可读 | 不可读 |
| 性能 | ~653K ops/s | ~727K ops/s | ~215K ops/s |
| 消息体积 | 中 | 大 | 小 |
| Schema | 无需 | 无需 | .proto 文件 |
| 适用场景 | Go 内部通信 | 调试/跨语言简易 | 生产级跨语言 |
ProtobufCodec 采用长度前缀帧(Header 用 JSON,Body 用 proto.Marshal), 若 Body 类型未实现
proto.Message接口则自动降级为 JSON。
bufio.Writer 的作用
Gob/Json 编码时使用 bufio.Writer 包裹底层连接,将多次小写入合并为一次系统调用,减少网络开销。 Protobuf 使用长度前缀帧,先序列化为完整 []byte 再一次性写入。
关键代码
| 文件 | 说明 |
|---|---|
codec/codec.go | Header、Codec 接口、Option、Type 定义 |
codec/gob.go | GobCodec 实现(流式编解码) |
codec/json.go | JsonCodec 实现(流式编解码) |
codec/protobuf.go | ProtobufCodec 实现(长度前缀帧) |
codec/codec_test.go | 单元测试 + Benchmark(三种 Codec) |
运行
bash
# 运行示例
go run ./day1-codec/example/
# 运行测试
go test -v ./codec/
# 运行 benchmark
go test -bench=. ./codec/