小村軟體有限公司
第 4 章

流量控制 — TCP 的滑動視窗

接收端在每次 ACK 時通告「還能再收多少位元組」,送信端把等待 ACK 的位元組數收在這個額度內。這扇窗的滑動決定了吞吐量的上限。

快的送信端,會把慢的接收端淹死

就算重傳的機制齊全了,承諾仍未完成。如果送信端的伺服器高性能,而接收端是非力的行動裝置呢?送達的資料會先放在接收端的接收緩衝區,等待應用程式以 recv() 讀走。應用程式的讀取若跟不上傳送的步調,緩衝區就會溢出,好不容易送達的資料被丟棄、還得請對方重傳。這就本末倒置了。

於是 TCP 加入了承諾之 3 的前半 — 流量控制。機制是共乘在第 3 章的 ACK 上。接收端每次回 ACK 時,都一併申告「從已確認的位置起,再這麼多我還收得下」的剩餘容量 — 視窗(rwnd)。送信端只在已送出但 ACK 尚未回來的位元組數(in-flight)不超過這個額度的範圍內繼續送。

位元組序列被分成已送信且已確認、已送信但未確認、視窗內可送信、視窗外不可送信 4 個區間,且窗隨 ACK 向右滑動的示意圖

從已確認的端點(ack = 20001)起視窗 5000 位元組份是「可以送的範圍」。ACK 前進時整扇窗向右滑,所以稱為滑動(sliding)視窗。

把位元組序列塗成 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 之間能送的最多就是一整個視窗。也就是說,不管頻寬多粗,都無法超過

吞吐量上限 = 視窗 ÷ 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 的後半 — 配合途中網路狀況的壅塞控制。另一扇窗 cwnd 將要登場。