TCP 輻輳制御
TCP の送信側は経路の容量を直接見られません。フィードバックから送信量を調整します。この教材は教科書型 Reno です。1 パケットから始め、スロースタートで RTT ごとに倍増し、しきい値以降は 1 ずつ増やし、損失時に減らします。ウィンドウは一定値に落ち着かず、のこぎり歯状になります。
経路は RTT ごとに 8 パケットを排出し、バッファーは 4 パケットを保持します。送信側が持つのは cwnd(送信中にできる MSS 数)と ssthresh(スロースタートのしきい値)です。3 重複 ACK は後続データが届いていることを示すため、本例では半減後も輻輳回避を続けます。タイムアウトの設定では ACK が戻らず、cwnd を 1 にしてスロースタートを再開します。
TCP 輻輳制御の仕組み
損失のない各 RTT の後で cwnd < ssthresh を調べます。
成立すればスロースタートで cwnd = min(cwnd * 2, ssthresh)。実 TCP は ACK ごとに更新します。ウィンドウを使い切り、各パケットが個別に確認される条件では、およそ RTT ごとに倍になります。遅延 ACK やバイト数による計算は成長率に影響します。本例は 1 往復の確認をまとめ、しきい値で増加を止めます。
しきい値に達すると輻輳回避へ移り、cwnd += 1。RTT ごとに 1 MSS の線形成長です。
cwnd > C + B であふれます。C は RTT ごとの排出数で、この固定 1 RTT 経路では帯域遅延積にも相当します。B はバッファー容量です。C を超えた分が待ち行列へ入り、C + B を超えた分を落とします。送信側は C を読みません。このモデルは容量計算と損失通知を分けており、同じ送信群の末尾損失から 3 重複 ACK を導出するものではありません。
- 3 重複 ACK による高速再送。
ssthresh = max(cwnd // 2, 2)、cwnd = ssthreshとして輻輳回避を続けます。実 Reno の高速リカバリーが一時的に使うssthresh + 3は省略します。 - タイムアウト。 必要な ACK の到着前に再送タイマーが切れます。しきい値は失われた送信群の窓から
max(cwnd // 2, 2)とし、cwnd = 1でスロースタートへ戻ります。このプリセットは送信群への応答がない状態を使います。
AIMD は加算的増加(RTT ごとに +1)と乗算的減少(× ½)です。1 だけ減らすと満杯に近い状態が続き、増加後にまたあふれます。半減すると次の探索の余地が生まれます。
TCP 輻輳制御のステップ
初期設定の Sawtooth は C = 8 パケット/RTT、B = 4、cwnd = 1、ssthresh = 8。容量を超えたときに損失が起きます。
- 初期化。 パイプは空で、点は
(RTT 0, cwnd 1)。ssthresh = 8は送信側の状態、C = 8は送信側から見えない値です。一致はこの設定の選択であり、同じ変数ではありません。 - スロースタート。 1 を送り 1 を確認、
cwnd 1 → 2。 - スロースタート。 2 を送り確認、
2 → 4。 - スロースタート。 4 を送り確認、
4 → 8。しきい値で止まります。 - しきい値到達。 増加規則を
×2から+1へ変更。このステップではパケットは動きません。 - 輻輳回避。 8 を送り確認。待ち行列 0、
8 → 9。 - 輻輳回避。 9 を送り、待ち行列 1、
9 → 10。 - 輻輳回避。 10 を送り、待ち行列 2、
10 → 11。 - 輻輳回避。 11 を送り、待ち行列 3、
11 → 12。 - 輻輳回避。 12 を送り、待ち行列 4/4、
12 → 13。次の送信群は収まりません。 - あふれる。 13 のうち 12 が収まり、1 が損失。3 重複 ACK を表示します。縮小は次のステップなので
cwndはまだ 13 です。 - 半減。
ssthresh = max(13 // 2, 2) = 6、cwnd = 6。輻輳回避のまま点が垂直に下がり、しきい値線も 8 から 6 へ移ります。 - 輻輳回避。 6 を送り、
6 → 7。倍増ではありません。高速リカバリーの一時的な+3を省いた動きです。 - 輻輳回避。 7 を送り、
7 → 8。 - 輻輳回避。 8 を送り、
8 → 9。帯域遅延積を再び通過します。 - 次の歯。
9 → 10 → 11 → 12 → 13、損失、6 への下降を 1 ステップで描きます。 - 完了。
cwnd = 6、ssthresh = 6。スロースタートの上昇と輻輳回避の歯が 2 個残ります。正確な容量を決めて終わるのではなく、増加、損失、半減を続けます。
No loss は B = 16 に変えます。cwnd = 14 まで損失なく増え、縮小せずに再生を終えます。
Timeout は 13 の送信群への ACK を返しません。RTO が発火して cwnd = 1。その後 1 → 2 → 4 → 6 と増やし、新しいしきい値で止め、輻輳回避へ移ります。同じ過負荷でも通知方法で減少幅が変わります。
パイプはその RTT の損失理由、グラフは各 RTT のウィンドウ履歴です。指数成長、線形成長、下降を、それぞれの更新規則と結び付けてください。
のこぎり歯になる理由
理想化した固定経路で C が分かれば、送信中の量を C にできます。しかし送信側にはその情報がありません。スロースタートで N のウィンドウに達するには Θ(log N) RTT。1, 2, 4, 8 は 4 回の送信ウィンドウです。輻輳回避は RTT ごとに 1 MSS なので、さらに N パケット増やすには Θ(N) RTT かかります。
損失後の半減は待ち行列に余裕を作ります。13 から 12 にしか減らさなければ 4/4 のままで、12 の送信成功後に 13 へ増え、再びあふれます。初期設定で歯を 2 回描くのは、同じ条件から同じ動作が繰り返されることを示すためです。
プリセットは次の特徴を示します。
- 線形成長後に半減:Reno 型の損失探索。3 重複 ACK が典型的な契機です。
- 1 へ落ちて再びスロースタート:タイムアウトや確認の遅れ。
- 待ち行列が増えてもまだ下がらない:バッファーが過負荷を吸収し、バッファーブロートによる遅延が生じ得る状態。
Cubic や BBR は別の曲線になります。ここでは Reno だけを扱います。
境界条件
- 初期しきい値は教材の設定。 実装では大きい値から始め、最初の輻輳でスロースタートを終える場合があります。本例は最初の損失前に両方の成長規則を見せるため 8 にしています。
- しきい値の下限は 2。
max(cwnd // 2, 2)は整数の半減でゼロになることを防ぎます。 - しきい値にちょうど着地。 しきい値 8 なら
4 → 8、タイムアウト後の 6 なら4 → 6で輻輳回避へ移ります。 - タイムアウトと重複 ACK は異なる。 同じ 13 の過負荷から、本例は前者で 1、後者で 6 にします。
- 損失なしでは縮小しない。 このモデルは増加を続けますが、有限のバッファーは成長が続けばいずれあふれます。長い待ち行列は無限の容量ではありません。
よくある間違い
- スロースタートは必ず最初の損失まで続くと思う。 初期しきい値が大きいときの挙動です。判定は
cwnd < ssthresh。本例は 8 で切り替わり、13 で損失が起きます。 - 3 重複 ACK 後に倍増する。 この Reno モデルは半分を残して輻輳回避を続けます。小さなウィンドウからスロースタートへ戻る方式とは異なります。
- ACK 番号の増加を輻輳制御と読む。 ハンドシェイクの
ack = seq + 1は番号の確認。cwndは送信中のデータ量の上限です。 - すべての損失をタイムアウトと呼ぶ。 重複 ACK は受信側が後続データを見ていることを示し、Reno は半分を残します。
- 単一フローの歯から無条件の公平性を導く。 AIMD の公平性は共有モデルや RTT などの条件に依存します。Chiu–Jain の図が扱う問題であり、本例は 1 フローです。
- 容量 C を発見して終わったと思う。 送信側は範囲を探索し続け、次の増加でもあふれます。
省略した内容
これは 1 フローを RTT ごとにまとめた Reno です。実 TCP は ACK ごとに更新し、初期ウィンドウは 1 より大きいことが多く、例えば 10 MSS です。高速リカバリーでは一時的に cwnd = ssthresh + 3 とします。損失時のしきい値は FlightSize から求め、送信量は受信ウィンドウにも制約されます。本例はウィンドウ全体を送る想定で cwnd を使います。Cubic、BBR、pacing、遅延 ACK、SACK、複数フローの公平性は省略しています。成長と後退の規則を示すモデルであり、完全な TCP 実装ではありません。
続けて学ぶ
同じ手法につながるレッスンや、次の概念への入口です。
鍵マークのあるレッスンの操作には有料アクセスが必要です。解説はすべて無料で読めます。
誤りや気になる点を見つけましたか? フィードバックを送る。