自研二进制 RPC 协议(TLV + 长度前缀)
本节目标
- 设计一种简单的二进制 RPC 协议(TLV 结构)
- 手写序列化 / 反序列化
- 把前面学的"长度前缀 + 二进制解析"串成完整实战
TLV(Type-Length-Value):每个字段 = 类型(1B) + 长度(4B) + 值(NB)。自描述、易扩展、好解析。
// 运行环境:Node.js 14+
// 保存为 aj-l16.js,执行:node aj-l16.js
// 编码一个 RPC 请求:method + params
function encodeTLV(type, value) {
const vbuf = Buffer.from(value)
const head = Buffer.alloc(5)
head[0] = type
head.writeUInt32BE(vbuf.length, 1)
return Buffer.concat([head, vbuf])
}
function decodeTLV(buf) {
const type = buf[0]
const len = buf.readUInt32BE(1)
const value = buf.slice(5, 5 + len).toString()
return { type, value, rest: buf.slice(5 + len) }
}
// 组装一条"调用"消息:多个 TLV 字段顺序拼起来
function buildCall(method, params) {
return Buffer.concat([
encodeTLV(1, method), // type=1 方法名
encodeTLV(2, params) // type=2 参数
])
}
// 调用示例
const msg = buildCall('add', 'a=1&b=2')
let p = msg
const fields = {}
while (p.length) { const f = decodeTLV(p); fields[f.type] = f.value; p = f.rest }
console.log('解析出的调用:', fields) // { '1':'add', '2':'a=1&b=2' }
// 再套一层长度前缀(接 TCP 传输,呼应 aj-l4)
function frame(msg) {
const h = Buffer.alloc(4); h.writeUInt32BE(msg.length, 0)
return Buffer.concat([h, msg])
}
console.log('整帧字节数:', frame(msg).length)实战意义:真实 RPC(gRPC 的 protobuf、Thrift)都是"结构化 + 长度/分隔 + 二进制"的思路。你今天写的 TLV + 长度前缀,已具备一个最小可用私有协议骨架。
名词解释
- TLV(Type-Length-Value):自描述字段格式:类型标识含义、长度标数据字节数、值存数据。扩展新字段不破坏旧解析器。
- RPC(远程过程调用):让调用远程服务像调用本地函数一样。需约定"怎么把调用请求序列化发出、怎么把结果解析回来"——即本协议。
- 序列化 / 反序列化(Serialize / Deserialize):把内存对象转成可传输字节流(序列化),收到后再还原(反序列化)。一切网络/存储通信都绕不开它。
课后练习
- TLV 相比"固定位置字段"有什么优势?
- 答案:TLV 自描述、顺序/可选字段灵活,新增类型旧解析器可跳过不认识的 type;固定位置字段一旦改布局旧程序全崩。
- 为什么这里还要再套一层"长度前缀"帧?
- 答案:TLV 解决"单个字段自描述",但一条消息可能含多个 TLV 且要经 TCP 传输;长度前缀帧让接收方先知道"整条消息多长",解决粘包(呼应 aj-l4)。
总结
自研 RPC 协议,是把整门课所有底层积木拼成一座可用建筑的高潮。你会发现,所谓"远程调用"并没有想象中神秘——它不过是把"方法名 + 参数"按 TLV 结构化、再套一层长度前缀帧经 TCP 发出去,对面按同样的规则拆回来。TLV 的"类型-长度-值"自描述设计,让你新增字段也不用怕旧客户端崩溃,这正是工业协议(protobuf、Thrift)的通用哲学。我特别想强调"分层"思维:TLV 管"字段怎么组织",长度前缀管"消息边界在哪",两者各司其职、互不耦合——这是所有稳健网络协议的共性。走到这里,你回头看 aj-l1 的 HTTP 解析、aj-l4 的拆包、aj-l7 的二进制抠数据,会发现它们本质是同一套"定义边界 + 按结构读字节"的能力。恭喜,你已经具备手写一个最小私有协议、甚至读懂 gRPC 帧格式的底层功力。