TCP 擁塞窗口原理
- 2021 年 10 月 29 日
- 筆記
學過網絡相關課程的,都知道TCP中,有兩個窗口:
- 滑動窗口(在我們的上一篇文章中有講),接收方通過通告發送方自己的可以接受緩衝區大小(這個字段越大說明網絡吞吐量越高),從而控制發送方的發送速度。
- 擁塞窗口,也就是本文要講的。
概念
一個連接的TCP雙端只是網絡最邊緣的兩台主機,他們不知道整個網絡是如何工作的,因此他們不知道彼此之間的有效吞吐量。因此,他們必須找到一種方法來確定它。我們稱之為擁塞窗口 (CWND)。這是在我們必須停止並等待確認之前可以發送的位元組數。
❝
擁塞窗口是決定任何時候可以發出的位元組數的因素之一。擁塞窗口由發送方維護,是阻止發送方和接收方之間的鏈路因流量過多而過載的一種手段。這不應與發送方維護的滑動窗口相混淆,滑動窗口的存在是為了防止接收方過載。擁塞窗口是通過估計鏈路上有多少擁塞來計算的。
❞
擁塞窗口對於設備來說是本地的,並且永遠不會在連接上共享,這與在每個段中發送的接收器窗口不同。在任何給定時間,設備最多可以發送由接收器窗口和擁塞窗口之間的最小值指定的位元組數,如下面的公式所示:
transmittable bytes = min(cwnd, rwnd)
這意味着如果擁塞窗口小於接收窗口,則設備可以在等待確認之前傳輸多達擁塞窗口中定義的位元組數。相反,如果接收窗口小於擁塞窗口,則設備可以在等待確認之前最多傳輸接收器窗口中定義的位元組數。
擁塞窗口根據網絡擁塞動態變化。每次未確認段時,都假定是由於網絡擁塞。擁塞窗口隨時間演變的方式被定義為一個算法,這取決於實現。我們現在將介紹最常見的一種。該算法遵循以下規則:
- 擁塞窗口從一個段的大小開始(大約 1KB)
- 定義了一個擁塞窗口閾值(ssthresh)
- 如果收到確認,並且當前擁塞窗口大小小於 ssthresh,則擁塞窗口加倍
- 如果收到確認,但擁塞窗口大於或等於 sshthresh,則擁塞窗口增加其初始值(例如 1KB)
- 如果一個段沒有被確認從而觸發重傳,擁塞窗口就會減半並且 ssthresh 被放置在這個值
- 擁塞窗口不能大於接收器窗口
該規則中包括我們經常聽過的幾種算法:
- 慢啟動(slow-start)
- 擁塞避免(congestion avoidance)
- 快速重傳(fast retransmit)
- 快速恢復(fast recovery)
算法
通過下面的圖片,可以方便大家更加快速的理解擁塞窗口的算法機制。
從上圖中可以看出,每個設備都會跟蹤自己的擁塞窗口(CWND,綠色)和對端的接收器窗口 (RWND)。
慢啟動
當主機開始發送數據時,如果立即所大量數據位元組注入到網絡,那麼就有可能引起網絡擁塞,因為現在並不清楚網絡的負荷情況。因此,較好的方法是先探測一下,即由小到大逐漸增大發送窗口,也就是說,由小到大逐漸增大擁塞窗口數值。通常在剛剛開始發送報文段時,先把擁塞窗口 cwnd 設置為一個最大報文段MSS的數值。而在每收到一個對新的報文段的確認後,把擁塞窗口增加至多一個MSS的數值。用這樣的方法逐步增大發送方的擁塞窗口 cwnd ,可以使分組注入到網絡的速率更加合理。
每經過一個傳輸輪次,擁塞窗口 cwnd 就加倍。一個傳輸輪次所經歷的時間其實就是往返時間RTT。不過「傳輸輪次」更加強調:把擁塞窗口cwnd所允許發送的報文段都連續發送出去,並收到了對已發送的最後一個位元組的確認。
另,慢開始的「慢」並不是指cwnd的增長速率慢,而是指在TCP開始發送報文段時先設置cwnd=1,使得發送方在開始時只發送一個報文段(目的是試探一下網絡的擁塞情況),然後再逐漸增大cwnd。
下面,我們從示例圖着手,來分析慢啟動的過程。
- 在協商連接時,兩個設備交換它們的接收器窗口(在這種情況下它們都有 32KB)
- 雙端都以 1KB 的擁塞窗口開始,但由於在該示例中客戶端將是唯一發送數據的客戶端,因此它將是唯一一個顯着使用此值的客戶端。
- 在第 2 行,客戶端收到一個 ACK並將其 CWND 加倍(現在是 2k)
- 服務器在第 3 行收到一個ACK時也做同樣的事情
- 客戶端發送兩段 1k 的數據,它們稍後在第 6 行和第 7 行確認,其中客戶端上的擁塞窗口加倍(4k,然後是 8k)
- 然後,客戶端再次發送 1k 數據並立即得到確認,有效地再次將擁塞窗口加倍(現在第 9 行為 16k)。
- 這在第 10-11 行重複,其中 CWND 達到 32k。
- 此時,除非接收端窗口也增長,否則擁塞窗口不能再增長。
慢啟動的整個過程如下:
- 初始化 cwnd = 1
- 經過1個RTT,即收到一個ACK,cwnd = 2^(1) = 2
- 經過2個RTT, cwnd = 2^(2) = 4
- 經過3個 RTT, cwnd = 2^(3) = 8
擁塞避免
讓擁塞窗口cwnd緩慢地增大,即每經過一個往返時間RTT就把發送方的擁塞窗口cwnd加1,而不是加倍。這樣擁塞窗口cwnd按線性規律緩慢增長,比慢開始算法的擁塞窗口增長速率緩慢得多。
無論在慢啟動階段還是在擁塞避免階段,只要發送方判斷網絡出現擁塞(其根據就是沒有收到確認),就要把慢啟動ssthresh設置為出現擁塞時的發送方窗口值的一半(但不能小於2)。然後把擁塞窗口cwnd重新設置為1,執行慢開始算法。這樣做的目的就是要迅速減少主機發送到網絡中的分組數,使得發生 擁塞的路由器有足夠時間把隊列中積壓的分組處理完畢。
❝
「擁塞避免」並非指完全能夠避免了擁塞。利用以上的措施要完全避免網絡擁塞還是不可能的。「擁塞避免」是說在擁塞避免階段將擁塞窗口控制為按線性規律增長,使網絡比較不容易出現擁塞
❞
- cwnd = i
- 經過 1 RTT, cwnd = i+1
- 2 RTT, cwnd = i+2
- 3 RTT, cwnd = i+3
快速重傳
TCP 有一個快速傳輸特性——在它的計時器到期之前重新傳輸丟失的段。 為了允許快速傳輸,我們需要為發送方和接收方設置一些規則。
- 作為接收者,它應該始終發送它期望接收的序列號。 例如,當接收方接收到第 1 段時,它以 ACK2 響應,
- 作為發送方,它應該忽略定時器並在收到 3 個重複的ACK 後立即開始重傳丟失的段。
用一句話概況,就是發送端在收到3個重複無序的ACK時候,它假定數據包丟失並重傳該數據包,而無需等待重傳計時器到期。
而在此時,擁塞窗口的變化過程如下:
- ssthresh設置為擁塞窗口的1/2
- 擁塞窗口大小設置為ssthresh
- 重新進入擁塞避免階段
快速恢復
- 當收到第3個重複的ACK時,將ssthresh設置為當前擁塞窗口cwnd的一半。重傳丟失的報文段。設置cwnd為ssthresh加上3倍的報文段大小。
- 每次收到另一個重複的ACK時,cwnd增加1個報文段大小並發送1個分組(如果新的cwnd允許發送)。
- 當下一個確認新數據的ACK到達時,設置cwnd為ssthresh(在第1步中設置的值)。這個ACK應該是在進行重傳後的一個往返時間內對步驟1中重傳的確認。另外,這個ACK也應該是對丟失的分組和收到的第1個重複的ACK之間的所有中間報文段的確認。這一步採用的是擁塞避免,因為當分組丟失時我們將當前的速率減半。
算法
快速重傳和快速恢復的目的是:快速恢復丟失的數據包。 如果沒有快速重傳和快速恢復這倆算法,那麼tcp可能
Tahoe
Tahoe算法是TCP的早期版本。除了具備TCP的基本架構和功能外,引入了慢啟動、擁塞避免以及快速重傳機制。 在該算法中,快速重傳機制策略如下:
- ssthresh設置為擁塞窗口的1/2
- 擁塞窗口大小設置為1
- 重新進入慢啟動階段
Reno
Reno與Tahoe相比,增加了快速恢復階段,也就是說,完成快速重傳後,進入了擁塞避免階段而不是慢 啟動階段。 Reno 在快速重傳階段,重新發送數據之後:
- ssthresh設置為擁塞窗口的1/2
- 擁塞窗口設置為之前的1/2
- 進入快速恢復階段
- 在快速恢復階段,每收到重複的ACK,則cwnd加1;收到非重複ACK時,置cwnd = ssthresh,轉入擁塞避免階段;
- 如果發生超時重傳,則置ssthresh為當前cwnd的一半,cwnd = 1,重新進入慢啟動階段。
Reno快速恢復階段退出條件:收到非重複ACK。
NewReno
在Reno版本中,若同時有多個數據包丟失,則大部分必須等到TimeOut之後,才進行重傳。這是因為在Reno中,同時有多個數據包丟失時,只要收到部分丟失數據的ACK,便退出快速恢復。而之所以能收到部分丟失數據的ACK,這是因為在快速重傳階段,只重新發送了部分丟失的數據。而在Reno結束快速恢復,進入擁塞避免階段之後,對於其他未重新發送的數據包來說,常常沒有足夠的重複ACK來觸發快速重傳機制。只好等等TimeOut,而TimeOut對於TCP性能有非常大的影響,在等待TimeOut這段時間,無法發送新的數據包,而在TimeOut之後,CWND被重新設置為1。
基於上述原因,NewReno優化了該機制,NewReno在收到部分丟失數據的ACK後,並不會退出快速恢復階段,而是等待所有丟失的包都重新發送之後,才退出快速恢復階段。這就使得NewReno在遇到多個數據包同時丟失時,不需要等待TimeOut,便可重新發送所有丟失的數據包,進而減小TimeOut對性能的影響。
SACK
除了NewReno的方法之外,要解決大量數據包丟失的問題,還有一個解決方案,就是讓發送端知道,哪些數據包已經送達,哪些數據包已經丟失。 SACK修改在接收端發送重複的ACK時,同時在ACK中攜帶連續的已經收到的數據序列號範圍,而連續數據序號範圍與連續數據序號範圍之間的間隔就是已經丟失的數據。
滑動窗口與擁塞窗口
共同點:提高網絡性能。
不同點:
- 流量控制:在TCP連接上實現對發送流量的控制,考慮點對點之間對通信量的控制,端到端,即:控制發送端的數據發送速率,使接收端可以來得及接收,保證網絡高效穩定運行。
- 擁塞控制:處理網絡擁塞現象,考慮網絡能夠承受現有的網絡負荷,全局性變量,涉及所有的路由器、主機以及與降低網絡傳輸性能有關的因素。防止過多的數據注入到網絡,使網絡中的路由器或鏈路不致過載,確保通信子網可以有效為主機傳遞分組。
Q&A
1、在一個窗口內重複丟包會造成影響嗎? 會。如果只丟一個包,那麼收到非重複ACK時,就能確認完本窗口內所有的包。然後進入擁塞 避免階段。這就是Reno想達到的。 而如果丟失多個包,那麼收到非重複ACK時,不能確認完本窗口內所有的包。但是,也會退出快速恢復, 進入擁塞避免階段。
這個時候可能會發生兩種情況:
-
多次進行快速重傳和快速恢復。又發現丟包,再次進入快速重傳和快速恢復。注意,每次進入快速重傳和快速恢復時,ssthresh和CWND都要減半。多次丟包最終會導致ssthresh指數減小。通過畫cwnd(t)圖可以發現,不僅這段時間吞吐量非常低,而且導致恢復完後擁塞避免的起點非常低,從而導致之後的吞吐量也很低。
-
經過多次快速重傳和快速恢復,接着發生傳輸超時。
2、為什麼發生擁塞時,還增加cwnd?
在檢測到丟包時,窗口為CWND。這時候網絡中最多有cwnd個包(傳輸中 < CWND)。每當收到一個重複的ACK,則說明有數據包離開網絡,達到接收端了。那麼,此時網絡中還可以再容納1個包。由於發送端滑動窗口不能移動了,所以如果想保持繼續保持固定個數的傳輸數據包,可以使CWND + 1。這樣一來,可以提高吞吐量。而實際上,在fast recovery期間發送的新數據包比起發生丟包的CWND來說,已經是大大減少了。