合同会社小村ソフト
第 4 章

フロー制御 — TCP のスライディングウィンドウ

受信側は ACK のたびに「あと何バイト受け取れるか」を広告し、送信側は ACK 待ちのバイト数をその枠内に収める。この窓の滑りがスループットの上限を決める。

速い送信側は、遅い受信側を溺れさせる

再送の仕組みが揃っても、まだ約束は完成しません。送信側のサーバが高性能で、受信側が非力なモバイル端末だったら? 届いたデータはいったん受信側の受信バッファに置かれ、アプリが recv() で読み出すのを待ちます。アプリの読み出しが送信ペースに追いつかなければバッファはあふれ、せっかく届いたデータを捨てて再送してもらう羽目になります。これでは本末転倒です。

そこで TCP は約束その 3 の前半、フロー制御を入れます。仕組みは第 3 章の ACK への相乗りです。受信側は ACK を返すたびに「確認した位置から、あとこれだけなら受け取れる」という空き容量 — ウィンドウ(rwnd) — を一緒に申告します。送信側は、送信済みでまだ ACK が返っていないバイト数(in-flight)がこの枠を超えない範囲でだけ送り進みます。

バイト列が送信済みで確認済み、送信済みで未確認、ウィンドウ内で送信可能、ウィンドウ外で送信不可の 4 つの区間に分かれ、ACK とともに窓が右へ滑る様子の図

確認済みの端(ack = 20001)からウィンドウ 5000 バイト分が「送ってよい範囲」。ACK が進むと窓ごと右へ滑るので、スライディング(滑る)ウィンドウと呼ばれます。

バイト列を 4 色に塗り分ける

送信側から見ると、バイトの列はいつでも 4 つの区間に塗り分けられます。ack = 20001、ウィンドウ 5000、バイト 23000 まで送信済み、という例で見てみましょう。

〜20000: 送信済み・確認済み
もう用済み。バッファから捨ててよい
20001〜23000: 送信済み・未確認(in-flight = 3000 バイト)
飛行中。失われたら再送するので、まだ手元にも保持している
23001〜25000: 未送信・送信可能(残り枠 2000 バイト)
窓の右端は 20000 + 5000 = 25000。ここまでは ACK を待たずに送ってよい
25001〜: 未送信・送信不可
窓の外。ACK が進んで窓が滑るのを待つ

ウィンドウが 0 と広告されることもあります(ゼロウィンドウ)。受信アプリがバッファを読み出していない状態で、送信側は送信を止め、ウィンドウプローブという小さな問い合わせで時々様子を聞きながら待ちます。キャプチャにゼロウィンドウが並んでいたら、疑うべきはネットワークではなく受信側アプリの処理詰まりです。

小問 4-1 — ウィンドウの端を手計算する

「どこまで送ってよいか」の線引きを、バイトの番号で正確に引けるようにします。

Q1. 受信側が ACK に載せて広告するウィンドウ(rwnd)の値が表しているものはどれですか。

Q2. 送信側は ack = 20001(20000 まで確認済み)、広告ウィンドウ 5000 バイトの ACK を受け取りました。すでにバイト 23000 まで送信済みです。いま追加で送ってよいのは何バイトですか。

バイト

Q3. 受信側がウィンドウ 0 を広告してきました(ゼロウィンドウ)。送信側の正しい振る舞いはどれですか。

窓の大きさが速度の上限を決める — ウィンドウ ÷ 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 つの実験

  1. RTT だけを 10 倍にする — ウィンドウと帯域はそのまま。上限が 10 分の 1 に落ち、土管がスカスカになるのを確認してください。
  2. ウィンドウを BDP にそろえる — 充填率がちょうど 100% になり、律速が「ウィンドウ」から「帯域」に切り替わる境目を探してください。小問 4-2 の第 3 問の答えと同じ値になるはずです。
  3. ウィンドウを BDP より大きくする — 上限はもう伸びません。窓を広げても、土管より太くは流れないことを確かめてください。

小問 4-2 — ウィンドウと RTT がスループットを決める

シミュレータの計算式を自分の手でも確かめます。1 MB = 100 万バイトとします。

Q4. ウィンドウ 100 KB(100,000 バイト)、RTT 100 ミリ秒のコネクションで出せるスループットの上限は何 MB/秒ですか。(1 MB = 100 万バイト)

MB/秒

Q5. 帯域 1 Gbps の高速な回線なのに、遠距離(RTT が大きい)の転送だけ遅い、ということが起こります。理由として正しいものはどれですか。

Q6. 帯域 10 MB/秒・RTT 100 ミリ秒の経路を隙間なく使い切るには、最低何 MB のウィンドウが必要ですか。(帯域 × RTT を計算する。1 MB = 100 万バイト)

MB

この章で持ち帰ること

  • フロー制御は受信側の都合に合わせる仕組み。rwnd は受信バッファの空きの申告
  • 送ってよい右端 = 確認済みの端 + ウィンドウ。in-flight がウィンドウを超えないように送る
  • スループット上限 = ウィンドウ ÷ RTT。使い切るにはウィンドウ ≥ 帯域 × RTT(BDP)
  • ゼロウィンドウの主犯は受信側アプリの読み出し詰まり。ネットワークを疑う前にアプリを見る

ただし、相手の都合に合わせるだけではまだ足りません。次章は約束その 3 の後半 — 途中のネットワークの都合に合わせる輻輳制御です。もう 1 つの窓 cwnd が登場します。