本页目录

二进制协议设计

二进制协议比文本协议更紧凑,但每个设计决策——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