フロー制御 — TCP のスライディングウィンドウ
受信側は ACK のたびに「あと何バイト受け取れるか」を広告し、送信側は ACK 待ちのバイト数をその枠内に収める。この窓の滑りがスループットの上限を決める。
速い送信側は、遅い受信側を溺れさせる
再送の仕組みが揃っても、まだ約束は完成しません。送信側のサーバが高性能で、受信側が非力なモバイル端末だったら? 届いたデータはいったん受信側の受信バッファに置かれ、アプリが recv() で読み出すのを待ちます。アプリの読み出しが送信ペースに追いつかなければバッファはあふれ、せっかく届いたデータを捨てて再送してもらう羽目になります。これでは本末転倒です。
そこで TCP は約束その 3 の前半、フロー制御を入れます。仕組みは第 3 章の ACK への相乗りです。受信側は ACK を返すたびに「確認した位置から、あとこれだけなら受け取れる」という空き容量 — ウィンドウ(rwnd) — を一緒に申告します。送信側は、送信済みでまだ ACK が返っていないバイト数(in-flight)がこの枠を超えない範囲でだけ送り進みます。
確認済みの端(ack = 20001)からウィンドウ 5000 バイト分が「送ってよい範囲」。ACK が進むと窓ごと右へ滑るので、スライディング(滑る)ウィンドウと呼ばれます。
バイト列を 4 色に塗り分ける
送信側から見ると、バイトの列はいつでも 4 つの区間に塗り分けられます。ack = 20001、ウィンドウ 5000、バイト 23000 まで送信済み、という例で見てみましょう。
ウィンドウが 0 と広告されることもあります(ゼロウィンドウ)。受信アプリがバッファを読み出していない状態で、送信側は送信を止め、ウィンドウプローブという小さな問い合わせで時々様子を聞きながら待ちます。キャプチャにゼロウィンドウが並んでいたら、疑うべきはネットワークではなく受信側アプリの処理詰まりです。
小問 4-1 — ウィンドウの端を手計算する
「どこまで送ってよいか」の線引きを、バイトの番号で正確に引けるようにします。
Q1. 受信側が ACK に載せて広告するウィンドウ(rwnd)の値が表しているものはどれですか。
rwnd は受信バッファの空き容量です。受信側は「届いたがアプリがまだ読んでいないデータ」をバッファに抱えており、その残り容量を毎回の ACK で申告します。送信側はACK 待ち(in-flight)のバイト数がこの枠を超えないように送ります。帯域や経路の混雑は rwnd には現れません — そちらの都合を扱うのが第 5 章の輻輳制御です。
Q2. 送信側は ack = 20001(20000 まで確認済み)、広告ウィンドウ 5000 バイトの ACK を受け取りました。すでにバイト 23000 まで送信済みです。いま追加で送ってよいのは何バイトですか。
送ってよい右端は「確認済みの端 + ウィンドウ」= 20000 + 5000 = 25000 です。23000 まで送信済みなので、in-flight は 20001〜23000 の 3000 バイト。残り枠は 25000 − 23000 = 2000 バイトです。ACK が進む(左端が右へ動く)たびに右端も滑って前進する — この動きがスライディングウィンドウの名前の由来です。
Q3. 受信側がウィンドウ 0 を広告してきました(ゼロウィンドウ)。送信側の正しい振る舞いはどれですか。
ゼロウィンドウは「受信バッファが満杯 — アプリがデータを読み出すまで 1 バイトも受け取れない」という申告なので、送信側は待つのが正解です。ただし黙って待つだけだと「ウィンドウが開いた」という通知が消失したとき永遠の待ちになるため、ウィンドウプローブで定期的に様子を聞きます。障害調査では、ゼロウィンドウの主犯はネットワークではなく受信側アプリの読み出しの遅さだという点が急所です(第 7 章)。
窓の大きさが速度の上限を決める — ウィンドウ ÷ RTT
フロー制御には重要な副作用があります。送信側は ACK 待ちの量をウィンドウ以下に保つので、1 RTT の間に送れるのは最大でもウィンドウ 1 杯分です。つまり、どんなに帯域が太くても
スループット上限 = ウィンドウ ÷ RTT
を超えられません。ウィンドウ 100 KB・RTT 100 ミリ秒なら、100,000 バイト ÷ 0.1 秒 = 1 MB/秒が天井です。回線を隙間なく使い切るには、帯域 × RTT(帯域遅延積、BDP)以上のウィンドウが必要になります。「近くのサーバからは速いのに、海外のサーバからだけ遅い」の多くはこの式で説明できます。
シミュレータの動作概要(テキスト版): RTT・ウィンドウ・帯域の 3 つのスライダーを動かすと、1 RTT に送れる量(= ウィンドウ)、ウィンドウ ÷ RTT で決まるスループット上限、帯域 × RTT(BDP)、実効スループット(ウィンドウ律速と帯域律速の小さい方)が数値で更新されます。あわせて、経路という土管に in-flight のセグメントがどのくらい詰まっているか(充填率と飛行中セグメント数の目安)が横棒で表示され、いまの上限を決めているのがウィンドウなのか帯域なのかが文で示されます。
シミュレータで試してほしい 3 つの実験
- RTT だけを 10 倍にする — ウィンドウと帯域はそのまま。上限が 10 分の 1 に落ち、土管がスカスカになるのを確認してください。
- ウィンドウを BDP にそろえる — 充填率がちょうど 100% になり、律速が「ウィンドウ」から「帯域」に切り替わる境目を探してください。小問 4-2 の第 3 問の答えと同じ値になるはずです。
- ウィンドウを BDP より大きくする — 上限はもう伸びません。窓を広げても、土管より太くは流れないことを確かめてください。
小問 4-2 — ウィンドウと RTT がスループットを決める
シミュレータの計算式を自分の手でも確かめます。1 MB = 100 万バイトとします。
Q4. ウィンドウ 100 KB(100,000 バイト)、RTT 100 ミリ秒のコネクションで出せるスループットの上限は何 MB/秒ですか。(1 MB = 100 万バイト)
送信側は ACK が返るまで最大でもウィンドウ分(100,000 バイト)しか送れず、ACK が返るのは 1 RTT(0.1 秒)後です。つまり 1 RTT あたり最大ウィンドウ 1 杯で、100,000 バイト ÷ 0.1 秒 = 1,000,000 バイト/秒 = 1 MB/秒。スループット上限 = ウィンドウ ÷ RTT — この講座でいちばん実務で使う式です。
Q5. 帯域 1 Gbps の高速な回線なのに、遠距離(RTT が大きい)の転送だけ遅い、ということが起こります。理由として正しいものはどれですか。
ウィンドウ 1 杯を送っては ACK を 1 往復待つ、の繰り返しなので、上限はウィンドウ ÷ RTT。RTT 5 ミリ秒なら十分でも、同じウィンドウで RTT 150 ミリ秒なら上限は 30 分の 1 になります。回線がどれだけ太くても、窓が小さければ水は流れません。対策はウィンドウを大きくすること(近年の OS はウィンドウスケーリングで自動調整します)で、プロトコルが切り替わったりはしません。
Q6. 帯域 10 MB/秒・RTT 100 ミリ秒の経路を隙間なく使い切るには、最低何 MB のウィンドウが必要ですか。(帯域 × RTT を計算する。1 MB = 100 万バイト)
経路を満たすのに必要な in-flight 量は帯域 × RTT(帯域遅延積、BDP)です。10,000,000 バイト/秒 × 0.1 秒 = 1,000,000 バイト = 1 MB。ウィンドウが BDP 以上あれば帯域が上限になり、BDP 未満ならウィンドウ ÷ RTT が上限になります。シミュレータで「ウィンドウ律速」と「帯域律速」の境目がまさにこの値になることを確かめてください。
この章で持ち帰ること
- フロー制御は受信側の都合に合わせる仕組み。rwnd は受信バッファの空きの申告
- 送ってよい右端 = 確認済みの端 + ウィンドウ。in-flight がウィンドウを超えないように送る
- スループット上限 = ウィンドウ ÷ RTT。使い切るにはウィンドウ ≥ 帯域 × RTT(BDP)
- ゼロウィンドウの主犯は受信側アプリの読み出し詰まり。ネットワークを疑う前にアプリを見る
ただし、相手の都合に合わせるだけではまだ足りません。次章は約束その 3 の後半 — 途中のネットワークの都合に合わせる輻輳制御です。もう 1 つの窓 cwnd が登場します。