本页目录
二进制协议设计
二进制协议比文本协议更紧凑,但每个设计决策——framing(length-prefixed vs delimiter)、整数编码(varint vs fixed)、可扩展性(TLV 让 parser 跳过未知字段)——都影响协议的带宽效率、解析复杂度和向前兼容能力。Protobuf 的 wire format 是这些决策的经典参考。
概述
二进制协议比文本协议更紧凑,但需要仔细设计字段编码和扩展性。核心决策:framing 方式(length-prefixed vs delimiter)、整数编码(varint vs fixed-width)、可扩展性(TLV 让 parser 跳过未知字段)。Protobuf(Google, 2008)广泛用于微服务通信和配置序列化,其 wire format 设计——field number + wire type tag + varint/length-delimited data——是二进制协议的优秀范例。
Framing
Length-prefixed:
[4-byte len (big-endian)][payload]
优点: 简单, 读取 len → 分配 buffer → 读 payload
缺点: 需要预知长度
Delimiter-based:
[payload][CR LF CR LF] ← HTTP header boundary
[payload][CR LF . CR LF] ← SMTP body end
→ 需要 escaping when delimiter appears in payload
TLV (Type-Length-Value)
可扩展性: parser 不认识 type → skip by length → forward compatible
[Type (1-2B)] [Length (1-4B)] [Value (length bytes)]
整数编码
Fixed: 2/4/8B big-endian (IP, TCP, DNS)
varint (protobuf, QUIC):
每 byte 最高 bit = "后续还有"
例: 300 = 0x12C → 1010 1100 0000 0010 = AC 02 (2B)
ZigZag (signed int):
sint32: 0→0, -1→1, 1→2, -2→3 → 小绝对值的小编码
Protobuf Wire Format
field = (field_number << 3) | wire_type
wire_type: 0=varint, 2=length-delimited
message Person {
string name = 1; → tag: 0x0A (1<<3 | 2), len, UTF-8 bytes
int32 age = 2; → tag: 0x10 (2<<3 | 0), varint
}
Byte Order
Big-endian (网络字节序): TCP/IP headers → htons() / htonl()
Little-endian (主机): x86/ARM → 新协议用 LE (CPU native, 少转换)
参考
- protobuf: protobuf.dev/programming-guides/encoding
- QUIC: RFC 9000 (varint encoding in Appendix A)
Keywords: framing, TLV, varint, protobuf, big-endian, little-endian, ZigZag