網絡編程基礎(6)-協議概要-TCP的擁塞控制

擁塞

當數據從一個大的管道(如一個快速局域網)向一個較小的管道(如一個慢速廣域網)發送時便會發生擁塞。當多個輸入流到達一個路由器,而路由器的輸出流小於這些輸入流的總和時也會發生擁塞。如下圖示例:
這裏寫圖片描述
上圖展示了一個典型的從大管道向小管道發送報文的情況。路由器R1就是“瓶頸”,是擁塞發生的地方。如果按照局域網的帶寬不加控制地儘可能地發送報文,而路由器R1向R2的鏈路卻是一條承載不高的小管道,那麼對於路由器R1在這種高流入低流出的情況下,R1的緩存就會很快被用盡,引起路由器丟包。
前文說過出現丟包超時時,TCP規定會重傳數據。但是,重傳會導致網絡的負擔更重,導致更大的延遲以及更多的丟包。試想一下,如果一個網絡內有成千上萬的TCP連接都如此行事,那麼馬上就會形成“網絡風暴”,TCP這個協議就會拖垮整個網絡。
所以,TCP需要一些措施來減輕或者避免上述情況。TCP不是一個自私的協議,當擁塞發生的時候,要做自我犧牲。就像交通阻塞一樣,每個車都應該把路讓出來,而不要再去搶路了。
TCP的經典擁塞控制措施主要是下面四個算法,這四個算法的發展經歷了很多時間,到今天都還在優化中。分別爲:
1988年Tahoe 提出的 1)慢啓動,2)擁塞避免,3)擁塞發生時的快速重傳
1990年Reno 在Tahoe的基礎上增加的 4)快速恢復

經典擁塞控制算法

慢啓動與擁塞避免

上面的例子中發送方一開始便向網絡發送多個報文段,這樣做在局域網是可行的,但是在發送方和接收方中間存在多個路由器多個較慢的鏈路時,就可能造成一些路由器緩存用盡而丟包。所以,我們不能一開始就發送大量的報文段,而是應該採用更溫和的策略慢慢提速來發送報文段,此之謂慢啓動算法。

具體來說: 慢啓動爲發送方的TCP增加了一個擁塞窗口cwnd,當與另一個主機連接時,擁塞窗口被初始化爲1個(現在某些實現中可能不只一個)報文段大小(另一端通知的報文段大小)。每收到一個ACK,擁塞窗口就增加一個報文段。發送方取擁塞窗口與通告窗口中的最小值作爲發送上限。擁塞窗口是發送方使用的流量控制,而通告窗口則是接收方使用的流量控制。
該過程如下圖所示:
這裏寫圖片描述
從圖中可以看出慢啓動是呈指數增大的,如果網速很快的話,ACK也會返回得快,RTT也會短,那麼,這個慢啓動就一點也不會慢。

慢啓動算法是在一個連接上發起數據流的方法,但是擁塞窗口不能無限的增加而導致網絡擁塞。我們還需要一個慢啓動門限ssthresh的狀態變量,當擁塞窗口增加到ssthresh大小的時候執行擁塞避免算法。擁塞避免算法讓擁塞窗口緩慢增長,即每經過一個往返時間RTT就把發送方的擁塞窗口cwnd加1,而不是加倍。這樣擁塞窗口呈線性增大的。

無論是在慢啓動階段還是在擁塞避免階段,只要發送方判斷網絡出現擁塞(其根據就是沒有收到確認,雖然沒有收到確認可能是其他原因的分組丟失,但是因爲無法判定,所以都當做擁塞來處理),就把慢啓動門限設置爲出現擁塞時的發送窗口大小的一半(乘法減小)。然後把擁塞窗口設置爲1,執行慢啓動算法

下面我們把整個過程重新簡單的描述一遍。
擁塞避免和慢啓動算法需要對每個連接的發送方維持兩個變量,一個擁塞窗口cwnd和一個慢啓動門限ssthresh,具體工作過程如下:
1:初始化。一般情況下,cwnd = 1(MSS),ssthresh = 65535(字節)。
2:執行慢啓動直到 cwnd >= ssthresh 轉而執行擁塞避免。
3:在步驟2中任何時刻擁塞發生時,慢啓動門限設置爲當前發送窗口大小的一半但最小爲2個報文段,然後把擁塞窗口設置爲1,即ssthresh = max(cwnd/2,2),cwnd = 1,隨即轉入步驟2。

慢啓動和擁塞避免實現舉例圖示如下:
這裏寫圖片描述

快速重傳和快速恢復

我們已經在超時與重傳一文說過,當發送端接收到3個重複的ACK時就重傳丟失的報文段而無需等待超時定時器溢出,這就是快速重傳。跟超時重傳對比一下,這兩種重傳的意義是不同的。超時重傳一般是因爲網絡出現了嚴重擁塞,此時需要更加嚴厲的縮小擁塞窗口,因此此時進入慢啓動階段。而收到3個重複ACK後說明確實有中間的分段丟失,並且後面的分段確實到達了接收端,正因爲這樣纔會發送重複ACK,這一般是路由器故障或者輕度擁塞或者其它不太嚴重的原因引起的,因此擁塞窗口縮小的幅度就不能太大,此時進入快速重傳/快速恢復階段。
具體來說:
1:當收到第3個重複的ACK時,重傳丟失的報文段。並將ssthresh設置爲當前cwnd的一半(乘法減小),設置cwnd爲 ssthresh加上3MSS的大小。即ssthresh = cwnd/2, cwnd = ssthresh + 3(MSS)。
2:執行擁塞避免而非慢啓動(線性增大)。

快速重傳/快速恢復的實例圖示如下:
這裏寫圖片描述

實際上,TCP擁塞控制算法發展到現今出現了不同版本的改進甚至不同的算法,它們在公平性和擁塞流量控制方面各有長短,而基於Tahoe & Reno的經典擁塞控制算法以其簡單、有效、魯棒性和公平性成爲主流,被廣泛的採用。想更加全面深入瞭解可以查閱相關資料,如維基百科https://en.wikipedia.org/wiki/TCP_congestion_control、RFC等。

顯式擁塞通告

經典擁塞控制算法的不足

TCP 在設計之初就假設包的丟失很大程度上是因爲路由器的擁塞,也即是路由器用的緩衝區已經被填滿而靜默丟棄接下來進入的包。慢啓動和擁塞避免算法對於時間敏感的,成塊數據流的控制效果非常好。然而,TCP處理丟包的方法對於交互式的,丟失敏感和時間敏感的流量來說效果不是很好。
另外一點,當路由器開始丟包時,它一般並不區分數據流的不同。當多個TCP數據流都產生丟包時,根據TCP擁塞控制措施,所有的數據流都要減少自身的發送速率,然後根據路由器擁塞減輕的程度,多個TCP數據流又會逐漸恢復自身的發送速率。這種週期性減少快增長慢的過程會降低路由器及相關鏈路的使用率,尤其是在大窗口和大帶寬的鏈路下,這種弊端越來越明顯。

顯式擁塞通告

上述吞吐量的問題是僅僅使用發送端管理擁塞帶來的結果。既然路由器是產生擁塞丟包主要角色,那麼可不可以讓路由器在傳輸數據過程中多幹點有益的事呢?答案自然是可以的。RFC3168描述了路由器可以監控轉發數據包隊列的狀態,以提供一個路由器向發送端報告發生擁塞的機制,讓發送端在路由器開始丟包前降低發送速率。這種路由器報告和主機響應機制被稱爲顯式擁塞通告(ECN)。

在網絡層,一個發送主機必須能夠表明自身可以進行ECN,路由器在轉發時必須能夠表明它正在經歷擁塞。
在傳輸層,TCP端必須對對方表明自身是可以進行ECN操作的。接收端必須能夠通知發送端它收到了一個來自路由器的擁塞通告。發送端必須能夠通知接收端它受到了來自接收端的通告並且已經降低了發送速率。

我們現在結合IP首部的TOS字段和TCP首部控制位字段來詳細描述ECN工作過程。
IP首部TOS字段如下圖:
這裏寫圖片描述
TCP首部標誌位字段如下圖:
這裏寫圖片描述

在IP層,DSCP值表明一個在路由器上配置的和隊列相關聯的發送優先級。IP對ECN的支持使用到了TOS域中剩下的這兩位。
ECN取值狀態如下:
00:發送主機不支持ECN
01或者10:發送主機支持ECN
11:路由器正在經歷擁塞
一個支持ECN的主機發送數據包時將ECN域設置爲01或者10。對於支持ECN的主機發送的包,如果路徑上的路由器支持ECN並且經歷擁塞,它將ECN域設置爲11。如果該數值已經被設置爲11,那麼下游路由器不會修改該值。

在TCP層,ECE和CWR取值狀態如下:
ECE置爲1:分爲兩種情況,其一是在TCP三次握手時表明TCP端是否支持ECN,其二在傳輸數據時表明接收到的TCP段的IP首部ECN字段被置爲11,即接收端發現了擁塞。
CWR置爲1:發送端縮小擁塞窗口標誌,用來通知接收端它已經收到了設置ECE標誌的ACK。

最後綜合來說一下ECN整個處理流程:
在一個支持ECN的TCP連接上,發送端發送設置了IP首部ECN域爲10或者01的TCP段。
支持ECN的路由器在經歷擁塞時會設置該TCP段IP首部的ECN域爲11。
接收端收到了設置ECN域爲11的TCP段,會在回覆的ACK中設置ECE標誌,並在接下來的回覆ACK中設置同樣的標誌。
發送端接收到設置ECE標誌的ACK包,開始減小發送窗口。在發送的下一個數據包中,發送端設置CWR標誌。
接收端收到設置CWR標誌的包時,停止在接下來的ACK中設置ECE標誌。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章