- TCP(Transmission Control Protocol) は、不安定なネットワーク環境でも 信頼性があり順序が保証されたデータ転送 を可能にする、インターネットの中核プロトコル
- IP がホスト間のデータ配送だけを担うのに対し、TCP は ポートベースのプロセス間通信 と エラー回復・再送・順序制御 を行う
- フロー制御(flow control) と 輻輳制御(congestion control) により、受信バッファやネットワーク帯域幅の限界を超えないよう調整
- C 言語で実装した シンプルな TCP サーバーと HTTP サーバーの例 を通じて、ソケット生成、バインド、リッスン、接続受け入れ、送受信の過程が具体的に説明される
- TCP の シーケンス番号・ACK 番号、ウィンドウ、チェックサム、フラグ(SYN/ACK/FIN/RST) などの内部構造は、今日のインターネットの安定動作を支える重要な基盤
TCP の必要性と役割
- IP はホスト間のパケット配送だけを担い、プロセス間通信 のためには TCP/UDP のような トランスポート層 が必要
- IP アドレスは「建物」、ポートは「アパート」にたとえられ、各アプリケーションはポートにバインドされて通信する
- TCP は パケット損失・重複・順序の入れ替わり などのネットワークの不安定さを隠蔽し、再送・チェックサム などで信頼性を保証
- ルーターはシンプルに保たれ、信頼性は通信の両端で処理 されることで、ネットワークインフラの複雑さを減らす
- この構造のおかげで、HTTP、SMTP、SSH など主要なインターネットサービスが安定して動作する
フロー制御と輻輳制御
- 受信側は カーネルの受信バッファ(receive buffer) を通じてデータを一時保存し、バッファサイズは
net.ipv4.tcp_rmem で設定
- 送信側は、受信側が許容可能なデータ量を ウィンドウ(window) フィールドで受け取り、送信量を調整
- ネットワーク全体の帯域差による 輻輳(congestion) を防ぐため、TCP は 輻輳制御アルゴリズム を導入
- 1986 年に発生した 輻輳崩壊(congestion collapse) をきっかけに、「バックオフ(back-off)」メカニズムが追加された
TCP サーバーおよび HTTP サーバーの例
- C 言語で書かれた 基本的な TCP エコーサーバー は、クライアントの入力を受けて「you sent:」を付けて再送する
socket(), bind(), listen(), accept(), send(), recv() などの Berkeley ソケット API を使用
- サーバーが
sleep() 中のとき、クライアントのデータは 受信バッファで待機 し、その後順次処理される
- シンプルな HTTP/1.1 サーバーの例 では、TCP 接続を通じてリクエストを受け付け、
HTTP/1.1 200 OK ヘッダーと本文を送信
- リクエスト回数を
i でカウントし、curl localhost:8080 を実行すると「[1] Yo, I am a legit web server」形式の応答が出力される
TCP セグメント構造と主要フィールド
- TCP セグメントは 送信元・宛先ポート、シーケンス番号、ACK 番号、ウィンドウサイズ、チェックサム、フラグ などで構成
- ポートは 16 ビットずつ割り当てられ、最大 64K ポート を利用可能
- 接続は
(プロトコル, 送信元 IP, 送信元ポート, 宛先 IP, 宛先ポート) の 5 タプル で識別
- シーケンス番号 は送信されたバイト範囲を、ACK 番号 は受信完了したバイトを示す
- 欠落データがある場合、ACK はその地点で止まり、再送後に累積 ACK として更新される
- フラグビット は接続状態を制御
SYN/ACK は 3-way ハンドシェイク によって接続を確立
FIN は 4-way ハンドシェイク で接続を終了
RST は異常終了やエラー時に即座に接続を解除
- ウィンドウフィールド は受信可能なデータ量を示し、
ss コマンドでバッファ状態(rb131072, tb16384)を確認可能
- チェックサム(checksum) は、セグメント内の 16 ビット単位の合算によってエラー検出を行う
結論
- TCP は 信頼性・順序・完全性 を保証し、不安定なインターネット環境でもアプリケーションが正常に動作するよう支援
- 数十年前は数 KB の転送も難しかったが、今日では 4K ストリーミング が日常化するほど発展
- このような安定した通信を可能にした TCP の設計と実装の精緻さ が、インターネットの継続的な成長を支える基盤となっている
1件のコメント
Hacker Newsの意見
信頼性のない データグラム層 の上に信頼できるデータストリームを作ろうとすると、結局ほぼTCPと同じ形になる
TCPの初期の限界は、小さな ウィンドウサイズ、損失パケット処理の不十分さ、そして単一ストリームしか扱えない点だった
こうした問題を解決するために SCTP や QUIC が登場した
輻輳制御アルゴリズムはプロトコルの一部ではなく、各接続の両端で動作するコードである
初期のアルゴリズム(Reno、Vegasなど)は単純だったが十分に効果的で、その後も大容量バッファ・長いRTT・公平性などを扱う研究が続いている
以前、JavaScriptで複数ダウンロードを1本のストリーム上で 優先順位付けとキャンセル機能 によって制御できるライブラリを作ったことがある
GreaseMonkeyスクリプトで出会い系サイトのサムネイルをバックグラウンドで先読みさせ、スクロール位置に応じてプリロードするようにした
結果としてサーバー負荷を減らしつつユーザー体験を改善できた
面白いことに、そのスクリプトをあるマッチ相手に共有したのだが、その人とは今でも一緒にいる — ほとんど Tinder以前のTinder だったわけだ
TCPはパケット交換網の上で仮想回線を提供する構造であり、信頼性を 再送 によって実現するという概念はフランスの Cylades ネットワークに由来する
攻撃者はネットワーク上のどこからでもデータを 注入(inject) したり、RSTパケット で接続を切断したりできる
ファイアウォールでRSTを遮断すれば安定性は高まるが、偽造されたシーケンス番号による 非同期化攻撃 は依然として可能だ
そのため、すべてのアプリケーションは別個の接続として resume機能 を実装しなければならず、TCPのスロースタート(slow start)の問題も抱えることになる
また、アドレスとポートを分離した概念そのものも非効率だと思う
たとえば DNS over TLS(DoT) では、1本のTCP接続で複数のクエリを同時に送り、応答を 順不同で 受け取れる
これは複数接続を開くより効率的で行儀のよい方法だ
QUICのほうが速いかどうかは分からないが、サーバー対応はまだ限定的だ
HTTP/1.1パイプライニング でも似たことはできるが、応答は順番通りに返ってくる
しかし多くの大学の講義ではこの点が強調されないため、TCPには単一のアルゴリズムしかないと誤解されがちだ
SCTP に愛着がある人はいるだろうか
SCTPはUDPのメッセージ指向性とTCPの信頼性を組み合わせたプロトコルで、マルチストリーミング と マルチホーミング をサポートする
複数の独立したストリームを並列送信できるため、Webページのテキストと画像を同時に送れる
詳しくは Wikipedia: Stream Control Transmission Protocol を参照
結局、最良の答えは UDP上の信頼性層、つまり QUIC だ
IPだけで直接パケットを送れるのか気になっていた
中間ルーターがTCPやUDP以外のパケットを拒否しそうに思えた
IPv4なら IANAプロトコル番号一覧 で0〜255のいずれかを指定すればよい
コアルーターはこのフィールドを検査しないが、NATやISPの機器は検査する場合がある
2台のLinuxサーバー間であれば、実験用番号(253、254)でも通信可能だ
IPsec、GRE、L2TPのようなプロトコルもTCP/UDPではない
企業ネットワークのファイアウォールやNAT環境では、任意のプロトコルが遮断されることがある
NATは end-to-end原則 を壊し、その結果、人々はTCPやUDPの上に、あるいは HTTPの上に 何でも載せるようになった
ただしECMPハッシュのエントロピーが減る程度の影響はある
結局のところ、相手がそのプロトコルを理解できるかどうかが肝心だ
ポート番号は単なる ノード内部のサービス識別子 にすぎない
RUDP(Plan9) はTCPとUDPの間にある優れた折衷案だった
Reliable User Datagram Protocol 参照
TCPがデフォルトになったおかげで、信頼性や順序保証が不要な場合であっても無条件で使われてきた
いまは HTTP/3(QUICベース) が広がることで状況が改善する可能性がある
ただしQUICははるかに複雑で、その強力さが役立つのは一部の人だけだ
UDP + WireGuardスタイルの単純な暗号化層 のほうがよりよい代案かもしれない
TCPは人類の偉大な発明の1つだが、半接続型ネットワーク(NATベース) が支配的になることは予想していなかった
当時のエンジニアたちは、なぜわざわざそんなに複雑にするのかと聞いたはずだ
結局、今の 非対称リンク構造 やクライアント–サーバーの区別は、こうした発想から生まれた
TCPの 輻輳制御アルゴリズム には、開発者があまり知らない興味深い効果がある
新しい接続でデータを送ると、初期送信は遅く、速度上昇は 遅延(latency) によって決まる
データセンターでは、RTTを数マイクロ秒縮めるだけでも大きな速度向上が得られる
ほとんどのTCPスタックはバイト単位ではなく セグメント単位 で速度上昇を計算するため、ジャンボフレーム を使うと6倍速く上がることがある
AWSはこのため 低スイッチング遅延 と ジャンボフレーム対応 に多大な労力を注いでいる
専門家はこうしたチューニングを行うが、多くの人はなぜ10Gbpsリンクで10Gbps出ないのか不思議に思っている
IP上に独自プロトコルを作るのは 非常に簡単なこと だった
15年前なら、Pythonで 直接パケットを組み立てて 実験できた