TCPの輻輳制御
受信窓・輻輳窓・RTT・回線速度が転送量を制限する条件を確認します。
説明
TCP送信側は、相手の受信窓だけでなく、ネットワークへ送り込む量を輻輳ウィンドウcwndで制御します。cwndは送信側の状態であり、TCPヘッダーのwindow欄そのものではありません。
同じMSSの転送モデルで、受信窓とcwndの小さい方を上限として送信余裕を計算します。窓を増やす過程と損失後の変化、RTTと帯域遅延積による上限を順に比較します。
1一つのセグメントに入るデータ量
IPv4でMTU1500、IPヘッダー20、TCPヘッダー20、追加オプションなしとします。
mtu=経路のIPパケット上限1500; ip_header=IPv4ヘッダー20; tcp_header=TCPヘッダー20; mss=データ部分の上限mtu-ip_header-tcp_header; mss計算値この条件ではデータ1460バイトです。MSSはTCPデータの長さで、パケット全体1500バイトとは違います。実際には相手が広告したMSS、経路MTU、使用するオプションも考慮します。
2輻輳ウィンドウ・受信ウィンドウと未確認データ量
cwndを4MSS、受信窓を10000バイト、未確認の送信済み量を2MSSとします。
cwnd=輻輳ウィンドウ4*mss; rwnd=受信ウィンドウ10000; flight=未確認の送信量2*mss; (cwnd追加余裕を計算,rwnd,flight,max(0,min(cwnd,rwnd)-flight))cwnd5840が受信窓10000より小さく、飛行中2920を引いた追加余裕は2920バイトです。受信側に空きがあっても、ネットワーク側の制御で送信を抑えます。
3受信窓の方が小さい場合
cwndは同じまま受信窓を3000バイトに変えます。
rwnd=3000受信側の空きを変更; max二つの制限を適用(0,min(cwnd,rwnd)-flight)余裕は80バイトです。実装の送信方針によっては小さな送信を待つ場合もありますが、この計算では二つの上限から許される量を求めています。回線が空いているだけでは、相手の窓を超えて送れません。
4Slow StartでACKごとに窓を増やす
初期cwndを2MSSとする例です。各MSSに一つACKが返り、受信窓やアプリケーション供給量の制限がない条件で、一往復ごとに増える量を計算します。
cwnd=2*mssこの例の初期窓; history=一往復で窓と同じ量が増える条件[cwnd*(2**round_trip) for round_trip in range(4)]; history三往復後までの窓この条件では2→4→8→16MSSです。ACKごとの増加を一往復単位でまとめた結果であり、時間経過だけで無条件に倍増するわけではありません。初期窓や遅延ACK、上限、使用アルゴリズムによって実機の推移は変わります。
5損失後のしきい値とタイムアウト
飛行中が16MSSのときタイムアウトした、RFC 5681の基本的な制御を計算します。
flight=損失時の飛行中データ16*mss; ssthresh=半分と2MSSの大きい方max(flight//2,2*mss); cwnd=mssタイムアウト後の窓; (ssthresh,cwnd)しきい値と再開量しきい値は8MSSの11680、タイムアウト後の窓は1MSSの1460です。重複ACKで始まるfast recoveryと、タイムアウト後の再開は同じ推移ではありません。すべての現代的な輻輳制御方式をこの一例で表しているわけではありません。
6RTTが長いと同じ窓で速度が下がる
利用可能な窓を65536バイトとし、RTT20msと100msで窓÷RTTの上限を比べます。
window=利用可能な窓65536; [(rtt窓をRTTで割ってbit/sへ換算,round(window*8/rtt/1_000_000,4)) for rtt in (0.02,0.1)]約26.21Mbit/sと5.24Mbit/sです。これは窓だけから求める上限で、回線速度や再送、ヘッダー、受信処理による制限があればさらに低くなります。RTTが5倍になると、この条件の上限は5分の1です。
7回線を使い切るために必要な未確認データ量
100Mbit/s、RTT100msの経路の帯域遅延積をバイトで計算します。
rate=回線のbit/s100_000_000; rtt=往復秒数0.1; bdp=必要な飛行量をバイトへ換算rate*rtt/8; (bdp64KiBとの比較,bdp/window)1,250,000バイトで、64KiB窓の約19.1倍です。十分なデータを供給し続けるには、受信窓とcwndの両方が必要な飛行量を許すことが条件になります。窓を広げるだけで必ず回線速度へ届くわけではありません。
8窓と回線の両方から上限を求める
窓を64KiB、1.25MB、2.5MBへ変え、回線100Mbit/sと比較します。
[(w,min窓由来と回線由来の小さい上限(rate,w*8/rtt)/1_000_000) for w in (65536,1250000,2500000)]窓が帯域遅延積に達すると計算上の上限は100Mbit/sになり、さらに倍の窓を用意しても回線100Mbit/sは超えません。これはヘッダー等を省いた上限モデルです。実測値を説明するときは、窓、RTT、回線、損失、アプリケーション供給のどれが支配しているかを調べます。
まとめ
送信余裕は受信窓とcwndの小さい方から、飛行中の量を引いて求めます。
窓÷RTTと回線速度は上限の候補であり、実効速度の保証ではありません。
操作履歴と解説を振り返れます。
Python 3
mtu=1500; ip_header=20; tcp_header=20; mss=mtu-ip_header-tcp_header; mss1460
cwnd=4*mss; rwnd=10000; flight=2*mss; (cwnd,rwnd,flight,max(0,min(cwnd,rwnd)-flight))(5840, 10000, 2920, 2920)
rwnd=3000; max(0,min(cwnd,rwnd)-flight)80
cwnd=2*mss; history=[cwnd*(2**round_trip) for round_trip in range(4)]; history[2920, 5840, 11680, 23360]
flight=16*mss; ssthresh=max(flight//2,2*mss); cwnd=mss; (ssthresh,cwnd)(11680, 1460)
window=65536; [(rtt,round(window*8/rtt/1_000_000,4)) for rtt in (0.02,0.1)][(0.02, 26.2144), (0.1, 5.2429)]
rate=100_000_000; rtt=0.1; bdp=rate*rtt/8; (bdp,bdp/window)(1250000.0, 19.073486328125)
[(w,min(rate,w*8/rtt)/1_000_000) for w in (65536,1250000,2500000)][(65536, 5.24288), (1250000, 100.0), (2500000, 100.0)]
演習完了