TCP 3-way ハンドシェイク
TCP 接続を確立するための 3 段階の手順で、SYN → SYN-ACK → ACK の順で行われる
TCP 3-way ハンドシェイクとは
TCP 3-way ハンドシェイクは、TCP 接続を確立するための 3 段階の手順で、SYN → SYN-ACK → ACK の順に進む。目的は疎通の確認だけではない。両側が初期シーケンス番号 (ISN) を伝え合い、以降のデータをどの番号から数えるかを合意する。この合意があるから、届く順序の入れ替わりや欠落を受信側が検出できる。
HTTP/1.1 と HTTP/2 は TCP の上で動くため、最初のリクエストの前に必ずこの手順が入る。一方 HTTP/3 は UDP の上の QUIC をトランスポートに使うので、TCP のハンドシェイクは発生しない。TCP 自体の仕様は 2022 年 8 月に発行された RFC 9293 が現行で、長く参照されてきた RFC 793 を置き換えている。
3 つのステップ
各ステップで何が相手に伝わり、何が確定するのかを追う。
クライアント → SYN (接続要求) → サーバー
クライアント ← SYN-ACK (要求承認) ← サーバー
クライアント → ACK (承認確認) → サーバー
→ 接続確立、データ送信開始
| ステップ | 送信者 | フラグ | 説明 |
|---|---|---|---|
| 1 | クライアント | SYN | 接続を要求し、自分の初期シーケンス番号を伝える |
| 2 | サーバー | SYN-ACK | 相手の番号を受け取ったと返し、同時に自分の初期シーケンス番号を伝える |
| 3 | クライアント | ACK | サーバーの番号を受け取ったと返す。ここで双方向の番号が確定する |
やり取りが 2 回で足りないのは、番号の確認が方向ごとに必要だからである。クライアントの番号はステップ 2 で確認され、サーバーの番号はステップ 3 で確認される。両方が揃うまで、どちらも相手が自分の番号を受け取ったと確信できない。
初期シーケンス番号は連番ではなく、外から予測できない値を選ぶ。RFC 9293 は、4 マイクロ秒刻みのタイマーの値に、接続を識別する 4 つ組 (送信元 IP / 送信元ポート / 宛先 IP / 宛先ポート) と秘密鍵を入力した擬似乱数関数の出力を加える式を推奨している。番号が推測できると、第三者が確立済みの接続へ偽のデータを差し込めるためである。
HTTPS の場合
暗号化が入ると、TCP の手順の上に TLS の手順が積み重なる。
TCP 3-way ハンドシェイク (1 RTT)
↓
TLS ハンドシェイク (TLS 1.3 は 1 RTT・TLS 1.2 は 2 RTT)
↓
HTTP リクエスト/レスポンス
→ 最初の応答までに 2〜3 RTT
TLS 1.3 で往復が 1 回減ったぶん、遠い相手との接続では差が出る。再訪時は事前共有鍵による再開を使うと、鍵交換をやり直さずに TLS の往復をさらに削れる。
レイテンシへの影響
往復の回数が同じでも、1 往復にかかる時間は距離で決まる。
東京 → 東京 (同一リージョン): RTT ≈ 1ms
→ クライアントが接続完了とみなすまで 1 RTT ≈ 1ms
東京 → バージニア (クロスリージョン): RTT ≈ 150ms
→ クライアントが接続完了とみなすまで 1 RTT ≈ 150ms
→ TLS 1.3 まで重ねると、最初のリクエストがサーバーへ届くまで 2.5 RTT ≈ 375ms
距離そのものは縮められないので、打てる手は 2 つに絞られる。往復の回数を減らすか、往復を起こさないようにするかである。前者が TLS 1.3 や HTTP/3、後者が接続の再利用と CDN での終端にあたる。
接続の再利用 (Keep-Alive)
同じ相手へ続けて要求を出すなら、いちど確立した接続を使い回すのが最も効く。
毎回接続を確立する場合:
リクエスト 1: ハンドシェイク (1.5ms) + データ (10ms)
リクエスト 2: ハンドシェイク (1.5ms) + データ (10ms)
合計: 23ms
接続を再利用する場合:
リクエスト 1: ハンドシェイク (1.5ms) + データ (10ms)
リクエスト 2: データ (10ms) ← ハンドシェイク不要
合計: 21.5ms
HTTP/1.1 では持続的接続が既定で、明示的に閉じない限り接続は維持される。HTTP/2 は 1 本の接続で複数のリクエストを多重化するため、確立の回数はさらに減る。ただし多重化は TCP からは見えないので、パケット 1 つの欠落や順序の入れ替わりが、その接続で進行中の全てのやり取りを一緒に待たせる。HTTP/3 が TCP を離れた理由の 1 つがこれである。
SYN Flood 攻撃
ハンドシェイクの途中で状態を抱える設計は、そのまま攻撃面になる。
攻撃者が送信元を偽った SYN を大量に送る
→ サーバーは SYN-ACK を返し、ACK を待つ間の状態を保持する
→ ACK は返らない → 待ち行列 (SYN backlog) が埋まる
→ 正規の接続要求が入れなくなる
根本的な対処は、ACK が返るまで状態を持たないことである。SYN cookies は、保持すべき情報を SYN-ACK のシーケンス番号自身に符号化して送り返し、待ち行列を消費しない。正規の相手なら、返ってきた ACK の確認応答番号からその情報を復元できるので、状態を確保しないままハンドシェイクを完了できる。偽の SYN は ACK が返らないため、サーバー側には何も残らない。
代償もある。シーケンス番号に詰め込める情報量には限りがあるので、一部の TCP オプションが使えなくなる。このため Linux では常時有効ではなく、待ち行列が溢れたときのフォールバックとして働く形が既定になっている (2026 年 8 月時点の tcp_syncookies の既定値は 1)。前段に置くマネージドな緩和サービスは、トランスポート層の攻撃をサーバーへ届く前に吸収する役割で、SYN cookies と置き換えの関係にはない。
接続の終了 (4-way ハンドシェイク)
終了は方向ごとに閉じるので、確立より 1 往復ぶん手順が多い。
クライアント → FIN → サーバー
クライアント ← ACK ← サーバー
クライアント ← FIN ← サーバー
クライアント → ACK → サーバー
→ 接続終了
FIN は「こちらからはもう送らない」という宣言で、片方向だけを閉じることもできる。受け取った側は残りのデータを送り終えてから自分の FIN を送るため、最短でも 4 回のやり取りになる。
能動的に閉じた側は、最後の ACK を送った後すぐ接続を消さず、TIME-WAIT 状態で 2 × MSL の時間だけ留まる。MSL はパケットが網の中に存在し得る最大時間で、RFC 9293 はこれを 2 分としている。留まる理由は 2 つある。1 つは、最後の ACK が失われて相手が FIN を再送してきたときに応答できるようにするため。もう 1 つは、同じアドレスとポートの組み合わせで新しい接続を作ったとき、前の接続の遅れて届いたパケットが混ざらないようにするためである。実装では規定より短い値を採ることが多い。
短い接続を大量に開いて閉じる側では、この TIME-WAIT が積み上がって送信元ポートを使い切ることがある。ここでも答えは同じで、接続を毎回作り直さず再利用する。
より広い文脈は TCP/IP、暗号化との積み重なりは HTTPS / TLS、番号でサービスを見分ける仕組みは ポート を参照。
この記事は役に立ちましたか?
関連用語
TCP/IP
インターネット通信の基盤となるプロトコル群で、データの分割 / 送信 / 再組立を信頼性高く行う
HTTPS / TLS
HTTP 通信を TLS で暗号化し、盗聴 / 改ざん / なりすましを防ぐプロトコル
ポート
ネットワーク通信でプロセスを識別する番号で、TCP/UDP の接続先を指定する
WebSocket
クライアントとサーバー間で双方向のリアルタイム通信を実現するプロトコル
TCP 輻輳制御
ネットワークの混雑を検知し、送信速度を動的に調整して公平で効率的なデータ転送を実現する TCP の仕組み
SSE (Server-Sent Events) とは - サーバーからのリアルタイム配信の仕組み
SSE はサーバーからクライアントへ HTTP 上で一方向のリアルタイムストリームを送る仕組み。WebSocket との違い / EventSource API の使い方 / 再接続処理を解説