Skip to content

コラム: 行区切りテキストプロトコルの設計

mini_db のコマンドは POST A B\n のような 改行区切りのテキスト です。一見素朴ですが、これは Redis / SMTP / HTTP / IRC など、現役のプロトコルが採用してきた由緒正しい設計です。

このコラムでは「なぜ行区切りなのか」「どこまで通用するのか」「破綻するケース」を整理します。

1. 行区切りテキストの 3 つの長所

人間がデバッグできる : nc localhost 4343 で繋いで POST A B と打てば動く。バイナリ・プロトコルだと xxd 必須。

境界が単純 : 区切り文字が \n 1 文字。受信バッファに \n が現れたかどうかだけ見れば良い。

実装が薄い : 長さプレフィックス方式と違い、長さフィールドの読み取り → バッファリングという 2 段階を必要としない。std::string::find('\n') で 1 行。

2. 似た設計の現役プロトコル

プロトコル区切りコマンド例応答例
mini_db\nPOST A B0
Redis (Inline)\r\nSET A B+OK
SMTP\r\nMAIL FROM:<a@b.com>250 OK
HTTP/1.1 (header)\r\nGET /index.html HTTP/1.1200 OK
IRC\r\nPRIVMSG #ch :hello:srv 332 …

mini_db がほぼ唯一 \n のみで \r\n ではないのは、本実装が単純化のために採用した選択です。実プロダクトでは CRLF が事実上の標準

3. 破綻するケース 1: 区切り文字を含めたい

たとえば POST greeting "hello world\n" のように、値の中に改行を含めたい とします。素直なプロトコルではここで詰みます。

逃げ道は 3 つ:

エスケープ : \n\\n のように書き換えて送る。デコード時に逆変換。実装は地味に面倒で、テストも増える。

引用符 : POST greeting "hello\nworld" のように引用符で囲む。パーサがクォート状態を持つ必要があり、プロトコル全体の状態機械が複雑化する。

長さプレフィックス (length-prefixed) : 「次に来るバイト数」を先に送る。以下の Redis RESP がこの方式 (Bulk String):

*3\r\n            ← 3 要素の配列
$4\r\nPOST\r\n    ← 4 バイトの "POST"
$8\r\ngreeting\r\n
$11\r\nhello\nworld\r\n   ← 11 バイト固定。中身に \n が入っていてもOK

mini_db を発展させるなら length-prefixed が最も筋が良く、バイナリ値も自然に扱える という利点が出てきます。

4. 破綻するケース 2: 行が長すぎる

行が無限に届かない場合、buffers_[fd] が無制限に膨らみます。悪意あるクライアントが 'A'\n 無しで送り続ける だけで、サーバの RAM を食い潰せる (DoS)。

mini_db のスコープでは「リクエストはせいぜい 1000 文字以内」という前提があるので無視していますが、本番サーバなら:

  • バッファサイズに上限を設け、超えたら接続を切る
  • アイドル時間 (SO_RCVTIMEO / select の timeout) で切断する

ぐらいは入れます。

5. 「テキスト vs バイナリ」をどう選ぶか

観点テキストバイナリ
人間のデバッグtelnet/nc でそのまま打てるxxd か専用クライアントが必要
効率△ 数値を "12345" (5 byte) で送るint32_t (4 byte) で済む
パース難度△ トークン分割と整数変換が要るmemcpy で構造体に詰めるだけ
拡張性○ 追加コマンドは新しい行を追加するだけ○ バージョンビットを使えば後方互換しやすい

**「目で読める」**を捨てない限りはテキスト、性能要件が支配的になったらバイナリ — というのが大まかな目安です。Redis が両方サポートしている (Inline + RESP) のはこのトレードオフの妥協点です。

まとめ

  • 行区切りテキストは「人が読める / 実装が薄い」が長所。SMTP・HTTP・Redis が証明している。
  • 値に区切り文字を入れたくなった瞬間に破綻する。逃げ道は エスケープ / 引用 / 長さプレフィックス
  • 長さプレフィックス (Redis RESP 風) は技術的にいちばん筋が良いが、デバッグ性は下がる。
  • スコープが広がるほど DoS 対策・サイズ上限・タイムアウト が必須になる。