このページの目次

バイナリプロトコルの設計

バイナリプロトコルはテキストプロトコルよりもコンパクトですが、フレーミング(長さプレフィックス方式 vs 区切り文字方式)、整数エンコーディング(可変長整数 vs 固定長)、拡張性(TLV によりパーサーが未知フィールドをスキップ可能)といった各設計判断は、プロトコルの帯域幅効率、パースの複雑さ、前方互換性に影響します。Protobuf のワイヤーフォーマットはこれらの判断の古典的な参照例です。

概要

バイナリプロトコルはテキストプロトコルよりもコンパクトですが、フィールドエンコーディングと拡張性を慎重に設計する必要があります。主要な設計判断:フレーミング方式(長さプレフィックス方式 vs 区切り文字方式)、整数エンコーディング(可変長整数 vs 固定長)、拡張性(TLV によりパーサーが未知フィールドをスキップ可能)。Protobuf(Google, 2008)はマイクロサービス間の通信や設定のシリアライズに広く使用されており、そのワイヤーフォーマット設計(フィールド番号 + ワイヤータイプタグ + 可変長/長さ区切りデータ)はバイナリプロトコルの優れた例です。

フレーミング

長さプレフィックス方式:
  [4バイトの長さ (ビッグエンディアン)][ペイロード]
  利点: 単純。長さを読み取り、バッファを割り当て、ペイロードを読み込む
  欠点: 長さを事前に知る必要がある

区切り文字ベース方式:
  [ペイロード][CR LF CR LF]  ← HTTP ヘッダーの境界
  [ペイロード][CR LF . CR LF] ← SMTP ボディの終了
  → ペイロード内に区切り文字が含まれる場合、エスケープが必要

TLV (Type-Length-Value)

拡張性: パーサーが type を認識しない場合 → 長さによってスキップ → 前方互換

[Type (1-2バイト)] [Length (1-4バイト)] [Value (length バイト)]

整数エンコーディング

固定長: 2/4/8バイト ビッグエンディアン (IP, TCP, DNS)

可変長整数 (protobuf, QUIC):
  各バイトの最上位ビット = 「後続データあり」
  例: 300 = 0x12C → 1010 1100 0000 0010 = AC 02 (2バイト)

ZigZag (符号付き整数用):
  sint32: 0→0, -1→1, 1→2, -2→3 → 絶対値が小さい値のエンコーディングが小さくなる

Protobuf ワイヤーフォーマット

field = (field_number << 3) | wire_type

wire_type: 0=可変長整数, 2=長さ区切り

message Person {
  string name = 1;  → tag: 0x0A (1<<3 | 2), 長さ, UTF-8 バイト
  int32  age  = 2;  → tag: 0x10 (2<<3 | 0), 可変長整数
}

バイトオーダー

ビッグエンディアン (ネットワークバイトオーダー): TCP/IP ヘッダー → htons() / htonl()
リトルエンディアン (ホスト): x86/ARM → 新プロトコルでは LE を使用 (CPU ネイティブ、変換が少ない)

参考

  • protobuf: protobuf.dev/programming-guides/encoding
  • QUIC: RFC 9000 (Appendix A に可変長整数エンコーディングあり)

キーワード: フレーミング, TLV, 可変長整数, protobuf, ビッグエンディアン, リトルエンディアン, ZigZag