壅塞控制 — TCP 向網路的壅塞退讓
路徑有多擠沒有人會告訴你。TCP 把自備的窗 cwnd 成倍撐大,並以封包遺失為訊號退讓 — 用手算追蹤這套邊試探邊傳送的機制。
對方很健康,路卻很擠
有了第 4 章的流量控制,再也不會把對方淹死了。那麼,只要 rwnd 允許就全力送出可以嗎?答案是否定的。送信端與接收端之間,是別人的通訊也會流進來的共用網路。超過途中路由器處理能力的部分會積在佇列裡,佇列溢出就會被丟棄。所有人都全力送的話,重傳會招來更多重傳,整個網路堵死、誰的資料都流不動(壅塞崩潰 — 1980 年代實際發生過的事故)。
麻煩的是,路徑的剩餘容量沒有人會告訴你。不存在像 rwnd 那樣的申告。於是 TCP 作為承諾之 3 的後半,擁有由送信端自力推定路徑容量的壅塞控制。推定值以另一扇窗 — 壅塞視窗(cwnd) — 的形式持有,實際可以送出的量由 min(rwnd, cwnd) 決定。對方的狀況與路的狀況,配合較嚴格的一方。
cwnd 的一生。以倍增衝刺(慢啟動),過了門檻值就一步一步(壅塞避免),因遺失而退讓再重新爬。鋸齒狀的波形是 TCP 健全地持續試探極限的樣子。
試探的方式 — 倍速衝刺,再一步步逼近
在不知道容量的路上,只能從少量開始、邊觀察邊增加。TCP 的試探是 2 段變速。
然後,一旦越過路徑的極限就會發生封包遺失,第 3 章的偵測開始運作。這裡依證據的重量改變懲罰正是 TCP 的智慧。
增加、撞上、退讓、再增加。cwnd 曲線描出的鋸齒狀波形,就像是 TCP 為了讓所有人分享網路容量而持續支付的問候。如果是誰都不退讓的設計,網際網路是不可能成立的。
練習 5-1 — 區分 2 種煞車
練習用「配合的是誰的狀況」來說清楚 2 扇窗的差別。
Q1. 關於流量控制與壅塞控制的差異,正確的說明是哪一個?
配合的對象不同。流量控制(第 4 章的 rwnd)是接收端申告過來的緩衝區空位。壅塞控制(本章的 cwnd)是送信端對沒有人會申告的路徑壅塞自力推定的結果。接收端很健康但網路很擠、或者相反的情況都存在,所以需要 2 個獨立的煞車。
Q2. TCP 把封包遺失當作「壅塞的訊號」使用,是為什麼?
路徑上的路由器基本上什麼都不會告訴你(雖然也有明確通知的機制,但不能總是依賴)。然而作為壅塞的物理性結果,溢出的佇列會丟棄封包。所以「發生了遺失 = 某處的佇列快溢出了」這個推理才成立。遺失也有電波品質等壅塞以外的原因,並非完美的訊號,但它是不需要額外協助就能使用的最普遍的訊號。
Q3. 決定送信端在某個瞬間可以送出的資料量(in-flight 的上限)的是哪一個?
2 個煞車是同時作用的。對方的緩衝區(rwnd)與路徑的推定容量(cwnd),較嚴格的一方 = min(rwnd, cwnd) 才是實際的上限。第 4 章「上限 = 視窗 ÷ RTT」式子裡的視窗,準確來說也是這個 min 的值。只把其中一邊加大,另一邊若很小,吞吐量也不會增加。
在實務上會怎麼呈現
壅塞控制平常是透明的,但在效能問題上必然現身。
- 剛連上時誰都很慢 — 不管線路多粗,都是從 cwnd = 1 MSS 附近開始試探,所以短的通訊一次都達不到最高速。與其大量重新建立小請求的連線,不如重複使用 1 條連線(keep-alive)更快,理由就在這裡。
- 1 次遺失的成本不是「重傳 1 個」就了事 — 因為 cwnd 會掉到一半(或 1),吞吐量的恢復要花上好幾個 RTT。遺失率 1% 不是「慢 1%」,而是可能慢上好幾個數量級。
- 速度碰頂有 3 個嫌疑人 — rwnd(對方的緩衝區)、cwnd(路徑的壅塞),以及頻寬本身。套進第 4 章的式子,思考是哪扇窗取了 min,就是切分問題的定式。
練習 5-2 — 手算 cwnd 的成長
cwnd 以區段數(MSS 為單位)來數。請計算倍增與加法這 2 種成長。
Q4. 以慢啟動從 cwnd = 1 MSS 開始傳送。當 cwnd 每 1 RTT 變成 2 倍時,經過 3 個 RTT 後的 cwnd 是多少 MSS?
1 → 2 → 4 → 8,3 RTT 後是 8 MSS。與「慢」這個名字相反,成長是指數的 — 每當 1 RTT 內送出的區段各有 1 個 ACK 回來就把 cwnd 加 1,結果就是每個 RTT 翻倍。10 個 RTT 就能達到 1024 MSS 的氣勢,空閒的線路很快就能用滿。
Q5. 超過門檻值(ssthresh)進入壅塞避免模式,從 cwnd = 10 MSS 起每 1 RTT 增加 1 MSS。經過 5 個 RTT 後的 cwnd 是多少 MSS?
10 + 5 = 15 MSS。明知極限已近仍繼續倍增的話,超過的瞬間會引發大量遺失。所以在危險水域切換成加法(每 RTT +1 MSS),輕輕地試探極限。離得遠就倍速衝刺,接近極限就一步一步 — 這種 2 段變速就是 TCP 的試探方式。
Q6. TCP 偵測到遺失時的反應,依偵測方式而不同。正確的組合是哪一個?(請回想第 3 章的 2 種偵測方法)
讓懲罰的重量配合證據的重量。重複 ACK 3 次(快速重傳)是「後續有送達 = 路徑活著」的輕症訊號,所以把 cwnd 砍半後繼續跑。逾時是「什麼都沒回來」的重症訊號,所以退回 cwnd = 1 MSS,從慢啟動重新來過。在封包擷取中看到吞吐量的斷崖時,懷疑觸發的是哪一種偵測 — 這是第 7 章會用到的讀法。
本章帶走的重點
- 流量控制配合對方的狀況,壅塞控制配合網路的狀況。可以送的量是 min(rwnd, cwnd)
- 路徑的容量沒有人申告,所以以封包遺失為壅塞的訊號自力試探
- 成長是 2 段變速 — 慢啟動每 RTT 倍增,壅塞避免每 RTT +1 MSS
- 懲罰也是 2 段 — 重複 ACK 3 次減半,逾時退回 1 重新開始。鋸齒狀的波形是健全互讓的樣子
至此 3 項承諾全部到齊。下一章把視角移到應用程式端。TCP 明明完美地守著承諾,應用程式的資料看起來卻「缺漏、黏在一起」 — 實務上最常見的 TCP 誤解,位元組串流與 framing 的故事。