趣談TCP擁塞控制

趣談TCP擁塞控制

若網絡中兩個端系統A和B之間進行通信,只要兩者之間存在空間距離,由於光速的傳播時延,一定意味著A與B之間存在一定的容量,可以容納若干數據段,因此可以將其中的傳輸介質看作一般的緩存,且為了更具普遍性,我們可以認為A與B之間的所有路由器、交換機等中間節點的緩存隊列也包含在A與B的網絡緩存之中。為達到最高的網絡利用率,最好的現象就是A與B之間的緩存(包括節點隊列及網絡本身)中完全充滿TCP數據段,並且能夠維持無間隙的持續流動。

趣談TCP擁塞控制


如上圖所示,在兩個端系統之間的網絡被填滿時,A與B之間共存在N個數據段,事實上發送端還是可以繼續發送數據。因為網絡被填滿後,發送端每發送一個數據段,接受端同時也會消費掉一個數據段併發出一個ACK。假定ACK的速率和數據段的速率一致,發送端能夠持續發送2*N個數據段,發送的開始時間是第一個數據段發送的時間,結束時間是第一個數據段的ACK回到發送端的時間,即為一個RTT,若發送速率為r,則可以獲得等式:

2 * N = r * RTT

由以上過程可以得知,前N個段是為了填滿A與B之間的網絡,後N個段是在A與B之間已經滿載的情況下ACK時鐘驅動的pacing。隨後的2*N個數據段是一個新的週期,即一個RTT內2*N個數據段。這就是最理想情況下的情景,數據充滿了網絡,不間斷地從發送端發出,ACK也不間斷地從接受端返回。

TCP在經歷三次握手建立連接後開始傳送數據,但並不是一開始就向網絡中發送大量的數據包,而是根據初始的cwnd大小逐步增加,cwnd初始化為一個最大報文段(MSS)的大小,每當有一個報文段被確認,cwnd大小指數增長:

開始 -> cwnd = 1

1個RTT -> cwnd = 2

2個RTT -> cwnd = 4

3個RTT -> cwnd = 8

……

若帶寬為W,那麼經過RTT * log2W時間就可以佔滿帶寬,因此cwnd不能這樣無限增長,而是需要一個限制。TCP使用慢啟動門限(ssthresh)變量檢測擁塞避免的觸發,一旦cwnd >= ssthresh,慢啟動過程結束,擁塞避免開始,此時cwdn的值不再指數上升,而是開始進行線性增加,當滑動窗口中所有的報文段都被確認時,cwnd的大小加1,直到發送方RTO超時導致重傳了一個報文段後進行如下操作:

1.把ssthresh降低為cwnd值的一半;

2.把cwnd重新設置為1;

3.重新進入慢啟動過程。

TCP還有一種情況會進行重傳,即收到3個相同的ACK。TCP在收到亂序的數據段時會立刻進行確認,利用3個相同的ACK來判定數據包的丟失,此時進行快速重傳:

1.把ssthresh降低為cwnd值的一半;

2.把cwnd再設置為ssthresh的值;

3.重新進入擁塞避免階段。

後來的“快速恢復”算法是在上述的“快速重傳”算法後添加的,當收到3個重複ACK時,TCP最後進入的不是擁塞避免階段,而是快速恢復階段。快速重傳和快速恢復算法一般同時使用。快速恢復的思想是“數據包守恆”原則,即同一個時刻在網絡中的數據包數量是恆定的,只有當“老”數據包離開了網絡後,才能向網絡中發送一個“新”的數據包,如果發送方收到一個重複的ACK,那麼根據TCP的ACK機制就表明有一個數據包離開了網絡,於是cwnd加1。如果能夠嚴格按照該原則那麼網絡中很少會發生擁塞,事實上擁塞控制的目的也就在修正違反該原則的地方。快速恢復的主要步驟是:

1.當收到3個重複ACK時,把ssthresh設置為cwnd的一半,把cwnd設置為ssthresh的值加3,然後重傳丟失的報文段,加3的原因是因為收到3個重複的ACK,表明有3個“老”的數據包離開了網絡;

2.再收到重複的ACK時,擁塞窗口加1;

3.當收到新的數據包的ACK時,把cwnd設置為第一步中的ssthresh的值。原因是因為該ACK確認了新的數據,說明從重複ACK時的數據都已收到,該恢復過程已經結束,可以回到恢復之前的狀態了,也即再次進入擁塞避免狀態。

為便於理解TCP擁塞控制的簡單過程,可進行如下類比:

慢啟動(Slow Start)

亞當隔著山頭扔玉米棒子給夏娃,亞當不知道夏娃能接多快,於是一次扔1個,編號為1。

夏娃喊2,意思是老孃1號玉米棒子已經收到,準備迎接2號玉米棒子。

亞當一次扔2個,編號為2、3。

夏娃喊4,準備迎接更多的玉米棒子。

亞當一次扔4個,編號為4、5、6、7。

夏娃喊8,意思是讓玉米棒子來得更猛烈些吧!

亞當一次扔8個,編號為8、9、…15。

夏娃嘴裡一直重複喊編號12,次數為3次,這裡傳達以下信息:

8-11號玉米棒子已經安全到達

12號玉米棒子肯定丟了

13、14、15號玉米棒子也應該安全到達,否則夏娃只會喊一次12,是13、14、15號玉米觸發夏娃重複的叫喊。

亞當意識到自己扔太快了,需要降速,降到多少合適呢?降一半,一次扔4個沒有問題。

if ( dupacks >= 3 ) {

ssthresh = max( 2 , cwnd / 2) ;

}

這裡cwnd =8,所以ssthresh=4。

但外面至少還有3個發出的玉米棒子還沒有確認(Outstanding Packet),如果將cwnd = ssthresh=4則意味著亞當最多一次可以扔四個玉米,但3個發出卻沒有確認的玉米棒子佔了3個名額,所以亞當最多一次只能扔一個玉米棒子,發送速率急劇下降,這不合理。

快速重傳

既然收到夏娃三次重複的確認,說明丟的玉米棒子(12)後的3個已經成功接收,不在空中飛,這3個雖然還沒有明確地確認,但已經隱含地確認了,所以這3個玉米棒子不應該佔據在空中飛玉米的數量,在空中飛的玉米應該是4個,再加上到達夏娃的3個,所以亞當的cwnd (Congestion Window)應該為7個。

cwnd = ssthresh + 3 * SMSS= 4+3=7

亞當的快速重傳

1.重傳12號玉米

2.扔16、17、18、19號玉米

快速重傳結束信號

一旦亞當接到夏娃16號玉米或之後的確認,快速重傳/快速修復完成。

擁塞避免(Congestion Avoidance)

亞當意識到一次扔4個安全,於是選擇以cwnd = ssthresh=4為基準線,如果一次扔4個沒有問題,那就一次扔5個、6個,線性增長到夏娃的接收極限。

參考:知乎--車小胖


分享到:


相關文章: