TCP/IP #
「TCP/IP」という言葉は 二つの意味で使われる。1 つは TCP と IP を中心とするプロトコルスイート全体 (HTTP / DNS / TLS / SSH / ICMP / IP / Ethernet … をまとめたファミリー)、もう 1 つは そのファミリーを 4 層に整理した参照モデル。インターネット上で動いているほぼすべての通信が TCP/IP の上に乗っている ので、「インターネットを動かしているプロトコル」と言ってもほぼ言い換えになる。
別記事として IP (アドレッシングとルーティング) と OSI 参照モデル (7 層の参考枠) がある。本稿はそれらと棲み分けて、「TCP/IP モデルの俯瞰」(§1) と 「TCP 自体が IP の上で何をしているか」(§2 が記事の中心) を扱い、§3 で UDP、§4 で QUIC、§5 で実際の TCP 挙動を観察する場面に降りる。
1. 4 層モデル #
TCP/IP モデルでは、通信を 4 つの層 に分けて考える。OSI の 7 層と違って L5/L6 を独立した層に切らず、Application 層に吸収 しているのが特徴。
各層の責務を一文で:
- Application: 「届いたバイト列の意味」を決めるルール (HTTP のリクエスト/レスポンス, DNS のクエリ/応答, SSH のチャネル多重化など)
- Transport: 「どのアプリのどの接続か」を識別し、必要なら 信頼性 (TCP) や暗号化された UDP セッション (QUIC) を提供
- Internet: 「この IP のホストへ世界の何処からでも届ける」 — 経路選択は ベストエフォート
- Link: 「今この瞬間、目の前のケーブル/電波で隣のノードへ届ける」
Internet 層がベストエフォート (届くか分からない) なのが TCP/IP 設計の核。それを 必要なときだけ TCP が補う、要らないなら UDP が薄く透過する、という分離が、インターネットがあらゆる用途に伸びていけた根本理由になっている。
2. TCP — IP の上に「信頼性」を作る #
IP は「封筒に住所を書いて投函するだけ」のサービスで、届く保証も、順序の保証も、重複しない保証もしない (best-effort)。それでもインターネット上のほとんどのアプリが「投げたデータは順番通りに、欠けず、重複なく、相手に届く」と仮定して書ける理由は、間に TCP が入って IP の不確かさを覆い隠している から。
TCP が提供する 4 つの保証を、内部の仕組みと一緒に追っていく:
- コネクション = ハンドシェイクで「これから話します」「いいよ」を握る (§2.1)
- 順序保証 + 重複排除 + 損失回復 = シーケンス番号 + ACK + 再送 (§2.2)
- フロー制御 = 受信側が「これ以上送るな」と窓で伝える (§2.3)
- 輻輳制御 = 経路の混雑を推定して送信ペースを自動調整 (§2.4)
2.1 3-way handshake と 4-way teardown #
TCP の最大の特徴は コネクション指向であること。データを送る前に、両端で「これから話します」「いいですよ」という合意を 3 メッセージで作る。終わるときも対称的に「もう送りません」「了解」を双方向で交わす。
3-way handshake の本質は 「双方向のシーケンス番号を相手に確認させる」 ことにある。各方向でシーケンス番号 (32-bit) を独立に持ち、ハンドシェイクで「自分はこの番号から始めます」を SYN で宣言、相手は ACK で「それ受け取った, +1 から待ってる」と返す。これが片方向だけでは信用できないので、両方向で同じやりとりを行う = 3 メッセージになる。
4-way teardown は、TCP が 片方向ずつ独立に close できる 設計になっているため:
- クライアントが FIN を送る = 「自分はもう送らないが、まだ受け取れる」
- サーバが ACK を返す
- サーバアプリが
close()を呼んだ時点で FIN を返す - クライアントが ACK を返し、TIME_WAIT で 2 MSL (Maximum Segment Lifetime, 通常 30〜60 秒) 待機
TIME_WAIT が「データ転送が終わったあとも残る」のは、遅延した古いセグメントが新しいコネクションに紛れ込まないため。短命なセッションを大量に張る (HTTP/1.1 のキープアライブ無し時代の Web サーバ等) と TIME_WAIT が積み上がり「ポート枯渇」につながる、という古典的問題の正体がこれ。
2.2 シーケンス番号と ACK — 順序・損失・重複の解決 #
TCP のヘッダで一番大事な 2 フィールドは シーケンス番号 (32-bit) と ACK 番号 (32-bit)。
- シーケンス番号 = 「このセグメントの最初のバイトは、コネクション上で何バイト目か」
- ACK 番号 = 「次に受け取りたいバイトの位置」 (= 「ここまで来ました」)
例: サーバが Client に対し「seq=1000 の 500 byte」を送ったら、Client は「ack=1500」を返す。これだけで:
- 損失検出: ACK が来ない → 一定時間後 (RTO, Retransmission Timeout) で再送
- 順序復元: 順番通りでなく届いても、シーケンス番号で並べ直してアプリに渡す
- 重複排除: 同じシーケンス番号のセグメントは捨てる
実用的には SACK (Selective ACK, RFC 2018) を使って「1000-1500 と 2500-3000 は届いた, 1500-2500 は未着」のように 穴 (hole) を相手に伝え、必要分だけ再送できる仕組みが標準で動いている。
2.3 フロー制御 — 受信窓 (rwnd) #
送信側が受信側の処理能力を超えないようにするための仕組み。受信側は ACK のたびに「今ここから何 byte 受け取れる」(= 受信窓, Window Size) をヘッダに乗せて伝える。送信側は 「未 ACK のデータ量」が受信窓を超えないように送る。
TCP ヘッダの Window Size は 16-bit (最大 65,535) だが、現代の高速回線ではこれでは足りない。Window Scale option (RFC 7323) で 窓を最大 14 bit 左シフトし、最大 1 GB まで拡張できる。
「ダウンロードが安定 80 MB/s で頭打ち」のような症状は、しばしば受信側カーネルのバッファ不足で受信窓が広がりきれていないのが原因。
net.ipv4.tcp_rmemの調整やアプリのSO_RCVBUFで解消することがある。
2.4 輻輳制御 — 経路の混雑を推定する #
フロー制御 (§2.3) は エンドホスト同士の都合を見ているだけ。途中の経路の混雑は別問題で、それを扱うのが 輻輳制御 (congestion control)。送信側は 輻輳窓 (cwnd, congestion window) という別の窓も持ち、「実際に送れる量 = min(rwnd, cwnd)」 で送信を絞る。
cwnd の動き方を決めるのが 輻輳制御アルゴリズム:
| アルゴリズム | 仕組み (要点) | 採用 |
|---|---|---|
| Reno (1990) | 損失=輻輳と仮定 → cwnd を半減して再開 | 古い標準 |
| CUBIC (2008) | 3 次曲線で cwnd を回復, 高 BDP に強い | Linux のデフォルト (2.6.19〜) |
| BBR (2016) | 損失でなく 帯域 × RTT の推定 を直接モデル化 | Google が広く投入 (YouTube, GCP など) |
Slow Start で cwnd を倍々に増やし、しきい値で Congestion Avoidance に移行、損失で減らす — という指数→線形→減速の 3 段サイクル が古典的だが、BBR ではこの「損失=ペース下げ」の仮定自体を捨てている。「Wi-Fi で再送が多い経路」「衛星回線のような長 RTT」「データセンター内の超低 RTT」など状況ごとに性能差が大きく出るため、最近の OS では sysctl で切替可能 (net.ipv4.tcp_congestion_control) になっている。
2.5 接続の状態 — ss で実際に見るとき #
TCP の状態遷移は約 11 状態あるが、運用で 1 度は気にしたほうがいい状態は限られている:
| 状態 | 意味 | 気にすべき場面 |
|---|---|---|
LISTEN |
サーバがアクセプト待ち | サービスが上がっていることの確認 |
ESTABLISHED |
データ転送可能 | アクティブな接続。多すぎないか |
SYN_SENT / SYN_RECV |
ハンドシェイク途中 | 普通すぐ消える / SYN_RECV 大量 = SYN flood の兆候 |
FIN_WAIT_1 / FIN_WAIT_2 |
自分が FIN を送った後 | 通常すぐ消える |
CLOSE_WAIT |
相手が FIN を送ってきたが自分はまだ close() を呼んでいない | 長時間残る = アプリのバグ (close 忘れ) |
TIME_WAIT |
自分が active close した側 / 2 MSL 待機 | 大量に出ても通常は正常 |
# 状態別カウントを 1 行で
ss -tan | awk 'NR>1 {print $1}' | sort | uniq -c
# 特定 port の ESTABLISHED 一覧
ss -tan state established sport = :443
CLOSE_WAIT が積み上がるサーバはほぼ間違いなくアプリの close() 漏れで、アプリ修正以外で根治しない。TIME_WAIT の大量発生は正常で、無理に潰すための tcp_tw_recycle (Linux 4.12 で削除) のような設定はかつてのアンチパターンとして覚えておくと安全。
3. UDP — 信頼性が「邪魔」なとき #
TCP のすべての仕組み (§2.1〜§2.4) は 「届く / 順序 / 重複なし」 を作るためのコストを払っている。リアルタイム音声 / 映像 / ゲームのように 「古いデータが順番待ちで遅れて届くより、欠けたまま新しいフレームを表示したい」 用途では、TCP の保証がむしろ邪魔になる。
UDP (User Datagram Protocol, RFC 768) は TCP と対極の設計:
- コネクションレス — ハンドシェイクなし、いきなり投げる
- ヘッダは 8 byte のみ (送信元 port / 宛先 port / 長さ / チェックサム)
- 再送なし / 順序保証なし / フロー制御なし / 輻輳制御なし
- 必要な保証はアプリが自分で作る
UDP が選ばれる代表例:
- DNS クエリ — 1 往復で済むのに TCP のハンドシェイクは過剰
- VoIP / RTP / WebRTC — 0.5 秒前の音声を再送してもらっても意味がない
- ゲーム — 古い座標より新しい入力を優先したい
- DHCP / NTP / TFTP — ブートストラップ用途
「信頼性が必要 → TCP, リアルタイム性優先 → UDP」という対比だったが、現代では 次節の QUIC がこの綺麗な対比を一段ずらしている。
4. QUIC — UDP の上に TCP+TLS+HTTP を建て直したもの #
TCP は 信頼性・輻輳制御を OS カーネルに持っている プロトコル。これは長らく利点だったが、進化が遅い (新しいアルゴリズムを足すには OS 更新が要る) という弱点になった。さらに TCP と TLS のハンドシェイクが直列に積み重なる (TCP 1 RTT + TLS 1 RTT = 2 RTT) のも、現代の Web には重い。
QUIC (RFC 9000, 2021) は これをユーザ空間で UDP の上に作り直した プロトコル:
- TCP+TLS+HTTP の機能を 1 つに統合 — TLS 1.3 が組み込まれており、初接続でも 1 RTT、再接続なら 0 RTT
- マルチストリーム — 1 コネクション内に独立したストリームを多重化、TCP の head-of-line blocking を解消
- コネクション ID — IP / port が変わっても (Wi-Fi → 4G ハンドオフなど) 同じコネクションで継続
- アプリ空間で実装 されるため、新しい輻輳制御アルゴリズムの投入が早い
HTTP/3 は 「HTTP over QUIC」 で、Google / Cloudflare / Meta / Akamai が広く採用、2026 年時点で新しいインフラの標準的な選択肢になりつつある。「UDP は信頼性なし」という単純な対比は、QUIC を境に古い理解になった。
5. パケットを実際に見る #
ここまでの仕組みを tcpdump と ss でその場で観察 できる。
# 3-way handshake / FIN を最小フィルタで観察
sudo tcpdump -nn -i any 'tcp port 443 and (tcp[tcpflags] & (tcp-syn|tcp-fin) != 0)'
# あるサーバへの ESTABLISHED 接続だけを追う
ss -tan dst :443
# 状態の内訳と接続数 (運用ダッシュボードに置きたい数字)
ss -tan | awk 'NR>1 {s[$1]++} END {for(k in s) print k, s[k]}'
# RTT / cwnd / 再送数まで含めた詳細 (Linux)
ss -tin
ss -tin の出力に出てくる cwnd:N, rtt:M/V, retrans:R/T は、§2.4 で語った 輻輳窓・往復遅延・再送数 がそのまま見えている。「Web ページが遅い」「特定サーバ間だけ転送が落ち込む」 という症状に対して、どの層で詰まっているか を切り分ける最初の手は、ほぼ常にここに戻ってくる。
TCP/IP は「インターネットを動かしている運び屋」であり、その中心の TCP は「ベストエフォートの IP の上で、信頼性を作るための一連の工夫」 (ハンドシェイク + シーケンス番号 + 受信窓 + 輻輳窓) として理解するのが最も近道になる。UDP はその工夫を全部捨てた素朴さを、QUIC はその工夫をユーザ空間で再設計したアジリティを、それぞれ別の方向で示している。仕組みを 1 度押さえておくと、ss -tin の見方も、HTTP/3 採用判断も、輻輳制御アルゴリズム選びも、自分の判断軸で扱えるようになる。