WEB PROTOCOL

TCPフロー制御(Flow Control)

スライディングウィンドウ、スロースタート、AIMD(加算増加・乗算減少)によるTCPの流量制御メカニズム

INTERACTIVE VISUALIZATION
cwnd
ssthresh
ロス
SS
cwnd
1
輻輳ウィンドウ
ssthresh
16
スロースタート閾値
Window
1
min(cwnd, rwnd=64)
RTT
0
初期状態
シナリオ
スロースタートで指数的にcwndが増加し、ssthreshに達したら輻輳回避に移行するフローを確認します
ステップ1 / 12
自動再生でウィンドウサイズの変化を順番に確認できます
TCP接続確立後、輻輳ウィンドウ(cwnd)は1セグメントから開始。
スロースタート閾値(ssthresh)は16に設定されている。

ここからACKを受信するたびにcwndが指数的に増加する
ウィンドウ状態
cwnd1
ssthresh16
フェーズ
スロースタート
rwnd64
解説

📌
フロー制御とは

TCPフロー制御は、送信側がネットワークと受信側の状況に応じて送信量を動的に調整する仕組みです。「ウィンドウ」と呼ばれるパラメータを使い、スロースタート・輻輳回避・パケットロス検出を組み合わせて、効率的かつ安全にデータを転送します。水道管を想像してください。蛇口を全開にすると大量の水が流れますが、受け手のバケツが小さければ溢れてしまいます。これがTCPにおける「フロー制御」の問題です。送信側はデータを高速に送れますが、受信側の処理が追いつかないとバッファがあふれ、データが失われます。TCPはこの問題を「受信ウィンドウ(rwnd)」で解決します。受信側が「今、私のバッファにはこれだけの空きがあります」と送信側に伝え、送信側はその量を超えないようにデータを送ります。もう一つの問題は道路の渋滞と同じです。あなた一人だけが車で走るなら道は空いていますが、みんなが一斉に道路に出ると大渋滞になります。ネットワークでも、多くのコンピュータが同時に大量のデータを送ると、ルーターやスイッチの処理能力を超えてパケットが廃棄されます。これが「輻輳(ネットワークの混雑)」です。TCPは「輻輳ウィンドウ(cwnd)」でこの問題に対処します。ネットワークの混雑度を推測しながら、送信量を動的に調整します。TCPが実際に送信できるデータ量は min(cwnd, rwnd) で決まります。つまり、受信側のバッファの空き(フロー制御)とネットワークの混雑度(輻輳制御)のうち、より厳しい制約が適用されます。Netflixの動画ストリーミングでも、Zoomのビデオ会議でも、裏側ではこのウィンドウ制御が1秒間に何十回も実行されており、ネットワーク環境に応じて自動的に送信速度を調整しています。上のツールで「スロースタート」シナリオを再生すると、ウィンドウサイズ(cwnd)がRTTごとにどう変化するかが確認できます。上段のグラフでcwndの増加パターンに注目してください。

送信量渋滞rwndmin(cwnd, rwnd)= 実効ウィンドウ

📌
特徴

  • 🐢スロースタート接続開始時はcwnd=1セグメント(約1.5KB)から始まり、ACKを受信するたびにcwndが倍増します。1 RTT目: cwnd=1, 2 RTT目: cwnd=2, 3 RTT目: cwnd=4, 4 RTT目: cwnd=8...と指数的に増加します。名前は「スロースタート」ですが、実は指数的な増加なので非常に速く、数RTTで数MBのデータを送信可能になります。ネットワークの容量がわからない状態で慎重に探りを入れながら速度を上げるため、「スロー」と呼ばれています。上のツールで「スロースタート」シナリオの前半(ステップ1-5)でcwndが1→2→4→8→16と倍増する様子を確認できます
  • 📈輻輳回避cwndがssthresh(スロースタート閾値)に達すると、増加モードが指数的→線形に切り替わります。1 RTTごとにcwndが1だけ増加する慎重なモードです。これは高速道路でスピードを出しすぎると事故(パケットロス)が起きるため、制限速度付近では慎重に速度を上げるのと同じです。上のツールで「スロースタート」シナリオの後半(ステップ6-10)でcwndが16→17→18→19...と1ずつ増加する様子を確認できます
  • 📉パケットロスで減速ネットワークが混雑してパケットが廃棄されると、TCPはそれを検知して即座にcwndを縮小します。検知方法は2つあります。(1) 3つの重複ACK(同じシーケンス番号のACKが3回届く)を受信した場合 → cwndを半分に(Fast Retransmit)。これは軽度の混雑と判断し、穏やかに減速します。(2) ACKが一定時間返ってこない(タイムアウト)場合 → cwndを1にリセット。これは重度の混雑と判断し、スロースタートからやり直します。上のツールで「パケットロス + Fast Retransmit」と「タイムアウト + リセット」の2つのシナリオを比較すると、減速の度合いの違いが確認できます
  • 🪟スライディングウィンドウACKが返ってくるのを毎回待っていると、ネットワークの使用効率が極めて低くなります(特にRTTが長い衛星通信やモバイル回線)。スライディングウィンドウでは、cwnd個のパケットをACKを待たずに連続送信できます。ACKが届くとウィンドウが右に「スライド」し、新しいパケットの送信が可能になります。これにより、ネットワーク帯域を最大限に活用した効率的なデータ転送が実現します。上のツール下段のスライディングウィンドウ表示で、ステップを進めるたびに送信済み(緑)→送信可能(青)のパケットが変化する様子を確認できます

📌
ユースケース

📺 Netflix(動画ストリーミング)
Netflixの動画ストリーミングでは、視聴者のネットワーク速度に応じてビットレートを自動調整する「アダプティブビットレートストリーミング」が使われています。この基盤となるのがTCPフロー制御です。スロースタートで回線速度を探り、安定したスループットが得られる帯域で最適な画質(SD/HD/4K)を選択します。ネットワークが混雑するとcwndが縮小し、自動的に低画質に切り替えて再生の途切れを防ぎます
💬 Zoom(ビデオ会議)
ビデオ会議では音声・映像のリアルタイム転送が求められます。TCPのフロー制御により、参加者のネットワーク帯域に応じて映像ビットレートを動的に調整します。パケットロスが検出されると即座にビットレートを下げ、解像度を落としてでも音声・映像の途切れを最小化します。社内Wi-Fiと外出先のモバイル回線では利用可能な帯域が大きく異なるため、この自動調整が不可欠です
🐙 GitHub(大容量push)
大規模なリポジトリ(数GB)をgit pushする場合、最初のRTTではcwnd=1でわずか1.5KBしか送信できませんが、スロースタートにより数秒後にはネットワーク帯域を最大限に活用した高速転送が可能になります。複数のTCPセグメントをウィンドウ内で効率的に送信することで、大容量のgitオブジェクトを高速にアップロードします
☁️ AWS S3(マルチパートアップロード)
S3のマルチパートアップロードでは、大容量ファイルを複数のパート(各5MB〜5GB)に分割し、並行してアップロードします。各パートのTCP接続ではフロー制御が独立に動作し、ネットワーク帯域を最大限に活用しつつ、パケットロスが発生した場合は該当パートのcwndだけが縮小され、他のパートのアップロードには影響しません

📌
用語解説

cwnd(輻輳ウィンドウ)
= ネットワーク混雑に基づく送信可能量
輻輳ウィンドウ(Congestion Window)は、送信側がネットワークの混雑状態に基づいて管理する送信可能量です。受信側が通知するrwndとは異なり、cwndは送信側が独自に計算します。接続開始時はcwnd=1(または初期値10、最近のLinuxカーネル)から始まり、ACK受信パターンに応じてスロースタート(倍増)→輻輳回避(+1/RTT)→パケットロス(半減またはリセット)と動的に変化します。ss -i コマンドで現在のcwnd値を確認できます。
peakloss
ssthresh(スロースタート閾値)
= 増加モードの切替ポイント
スロースタート閾値(Slow Start Threshold)は、cwndの増加モードを切り替える境界値です。cwnd < ssthresh ならスロースタート(指数増加)、cwnd >= ssthresh なら輻輳回避(線形増加)で制御されます。パケットロスが検出されると、ssthreshはロス時のcwndの半分(cwnd/2)に再設定されます。初期値はOS依存ですが、Linuxでは65535バイトが一般的です。ssthreshは「過去の経験に基づく安全な速度の目安」と考えるとわかりやすいです。
ssthresh指数増加線形増加
rwnd(受信ウィンドウ)
= 受信側バッファの空き容量
受信ウィンドウ(Receive Window)は、受信側がTCPヘッダーの Window フィールドで送信側に通知するバッファの空き容量です。「今、私のバッファにはこれだけの空きがあるので、この量までならデータを送ってOKです」という意味です。rwndはACKパケットに含まれて送信されるため、受信側の処理状況がリアルタイムに反映されます。受信アプリケーションがデータを読み出すとrwndが増え、処理が遅れるとrwndが減ります。rwnd=0になると、送信側はデータ送信を停止します(ゼロウィンドウ)。
rwnd空き
AIMD
= 加算増加・乗算減少
Additive Increase, Multiplicative Decrease の略で、TCPの輻輳制御の基本原理です。「増やすときは慎重に1ずつ(加算増加)、減らすときは一気に半分に(乗算減少)」というルールです。このルールにより、同じネットワークを共有する複数のTCP接続が、時間の経過とともに公平に帯域を分け合うようになります(公平性の収束)。もしすべてのTCP接続が同じAIMDルールに従うなら、理論上は各接続が帯域の1/Nを使用する均衡状態に収束します。
+1, +1, +1.../2
スロースタート
= 慎重に速度を上げるフェーズ
名前は「遅いスタート」ですが、実はcwndが指数的に増加(1→2→4→8→16→...)するため、非常に高速にネットワーク帯域を探索します。cwnd=1から始める理由は、接続開始時にネットワークの状態がわからないため、いきなり大量のデータを送ると輻輳を引き起こす可能性があるからです。Linuxカーネル3.0以降ではスロースタートの初期cwnd値が10に引き上げられ(IW10)、小さなWebページなら1 RTTでダウンロード可能になりました。Google研究者がRFC 6928で提案した変更です。
124816
Fast Retransmit
= タイムアウト前の早期再送
通常、TCPはタイムアウト(RTO: Retransmission Timeout)を待ってからロストパケットを再送しますが、RTOは通常200ms〜数秒と長く、この間ずっとデータ転送が止まります。Fast Retransmitは、3つの重複ACK(同じシーケンス番号のACKが3回連続で届く)をパケットロスの兆候として検知し、タイムアウトを待たずに即座に再送します。cwndの減少も半減(cwnd/2)に留まり、cwnd=1へのリセット(タイムアウト時)よりもはるかに穏やかです。上のツールで「パケットロス + Fast Retransmit」シナリオを再生すると、3重複ACK検出→cwnd半減→再送→回復の流れが確認できます。
DupACKDupACKDupACK再送

📌
ウィンドウ制御の手順

1
スロースタート開始
TCP接続が確立された直後、cwnd=1セグメント(約1,460バイト)から送信を開始します。ACKを1つ受信するたびにcwndが1ずつ増え、結果として1 RTTごとにcwndが倍増します(1→2→4→8→...)。この指数的な増加により、ネットワークの利用可能帯域を素早く探索します。
2
ssthresh到達
cwndがssthresh(スロースタート閾値)に達すると、輻輳回避フェーズに移行します。ここからは1 RTTごとにcwndが1だけ増加する線形増加に切り替わります。指数増加のままだと急激にネットワーク容量を超えてしまうリスクがあるため、閾値以降は慎重に帯域を探ります。
3
パケットロス検出
ネットワークの輻輳によりパケットが廃棄されると、TCPは2つの方法でそれを検知します。3つの重複ACKを受信した場合はcwndを半分に縮小し(Fast Retransmit)、ACKが一定時間返ってこない場合(タイムアウト)はcwndを1にリセットします。いずれの場合もssthreshはロス時のcwndの半分に再設定され、次の増加の目安として使われます。
4
回復
新しいssthreshの値に向けて、再びスロースタート(タイムアウト後)または輻輳回避(Fast Retransmit後)で増加を再開します。TCPはこのサイクルを接続が終了するまで繰り返し、ネットワークの状態変化に常に適応し続けます。
スロースタート輻輳回避ロス回復ssthresh
上のツールで「タイムアウト + リセット」シナリオを再生すると、タイムアウト後にcwndが1にリセットされてスロースタートから再開する様子が確認できます。

📌
スライディングウィンドウの動き

ACK済み送信可能(cwnd=8)未送信ACK受信でスライド →

図の中の各正方形は1つのTCPセグメント(パケット)を表しています。緑色のセグメントはACKを受信済み(送信が完了した)データ、青色のセグメントは現在のウィンドウ内で送信可能なデータ、灰色のセグメントはウィンドウ外の未送信データです。紫色の破線枠がcwndの範囲を示しています。

ACKを受信すると、ウィンドウの左端が右に移動(スライド)します。例えば、セグメント1のACKが届くと、セグメント1が「ACK済み」(緑)になり、ウィンドウが右にスライドして新しいセグメント(灰色→青)が送信可能になります。これにより、ACKを待っている間も新しいデータの送信が可能で、ネットワーク帯域を無駄なく活用できます。

もしスライディングウィンドウがなければ、1パケット送信→ACK待ち→次のパケット送信...というストップ&ウェイト方式になります。RTTが100msの場合、1秒間にたった10パケット(約15KB)しか送れません。cwnd=100なら、1 RTTで100パケット(約150KB)を送信でき、スループットが100倍になります。

ストップ&ウェイトP1waitACKP2ACK遅いスライディングウィンドウP1P2P3P4→ ACKをまとめて受信高速
上のツール下段のスライディングウィンドウ表示で、ステップを進めるたびにウィンドウがスライドする様子が確認できます。

関連コンテンツ

TCP/IP

TCP/IP

TCPとIPの役割分担、パケット分割から順序通りの組み立てまでの流れを可視化

TCP 3ウェイハンドシェイク

TCP 3ウェイハンドシェイク

SYN・SYN-ACK・ACKの3パケットによる接続確立の流れを可視化

TCPコネクション切断

TCPコネクション切断

4ウェイハンドシェイク(FIN/ACK)によるTCP接続の切断フローを可視化

HTTP通信

HTTP通信

ブラウザとサーバー間のHTTPリクエスト/レスポンスの仕組みを可視化

TLSハンドシェイク

TLSハンドシェイク

HTTPSで安全に通信するためのTLSハンドシェイクの仕組みを可視化

DNS名前解決

DNS名前解決

ドメイン名からIPアドレスへの階層的な問い合わせの流れを可視化