本篇中先介紹一下慢啟動和擁塞避免的大概過程,下一篇中將會給出多個linux下reno擁塞控制算法的wireshark示例,並詳細解釋慢啟動和擁塞避免的過程。
一、慢啟動(slow start)
一個TCP連接啟動的時候並不知道cwnd應該取多大的值適合當前的網絡狀況,因此TCP發送方會從一個較小的初始值指數抬升cwnd到某一個值,這個cwnd抬升的過程就叫做慢啟動。除了初始建立tcp連接(SYN包交換后)后的數據發送使用慢啟動外,在TCP超時重傳、TCP空閑一段時間后重新開始數據發送這些場景下也會觸發慢啟動過程。
在TCP連接的慢啟動過程中,cwnd的初始值為IW(initial window)。IW的原始值為1個SMSS的大小,但是RCF5681協議允許IW按照如下設置(RFC3390中也有詳細的設置原則描述和討論)
SMSS>2190bytes時,IW=2*SMSS 且最大不能超過2個報文段
2190bytes>=SMSS>1095bytes時,IW=3*SMSS 且最大不能超過3個報文段
其他場景下,IW=4*SMSS 且最大不能超過4個報文段
為了接下來描述方便,我們取cwnd=IW=1*SMSS。其中SMSS一般為min(接收方的MSS,扣除header的path MTU)。假設沒有丟包發生並且接收方每接收一個包就反饋一個ACK,則每當發送方接收到一個Good ACK的時候,發送方就會調整cwnd=cwnd+min(N,SMSS),其中N表示這個Good ACK反饋確認的接收方新收到的數據量,單位為byte。比如之前收到的最大ACK Number為1000,新收到的Good ACK中ACK Number為2000,則這個Good ACK指示接收方新收到了1000byte的數據,這種場景下N=1000bytes。當發送端以SMSS大小發送數據報文,接收方對每個報文都立即回復ACK的時候,簡潔的整理以下算法流程如下
1)連接建好的開始先初始化cwnd = 1,表明可以傳一個SMSS大小的數據。
2)每當收到一個ACK,cwnd++; 呈線性上升
3)每當過了一個RTT,cwnd = cwnd*2; 呈指數讓升
4)還有一個慢啟動門限ssthresh(slow start threshold),是一個上限,當cwnd >= ssthresh時,就會進入擁塞避免。
5)當遇到RTO超時重傳時會觸發cwnd和ssthresh的調整,ssthresh=max(cwnd/2, 2),cwnd=1,然后重新開始慢啟動過程。
連接初始建立場景下可以設置ssthresh為一個很大的值,然后開始慢啟動流程后,直到發送速率一直增大,最終觸發RTO超時或者快速重傳,然后根據cwnd設置ssthresh從而得到一個有效的ssthresh值。這里我們暫時只考慮RTO超時場景,后面會介紹快速重傳、ECN等場景下的處理。
慢啟動流程示意圖如下
從上圖可以看到每過一個RTT,cwnd就會增加一倍,因此TCP的慢啟動是指數增長的(慢啟動的"慢"是指TCP的發送端不能直接以接收方通告的awnd為窗口進行發送,而要有一個啟動過程,因此叫做慢啟動)。當發送端使用delay ACK時,慢啟動的cwnd仍然是指數增長的,但是增長速度會稍微慢一些。因此有一些TCP實現會等TCP連接完成慢啟動過程后在使用delay ACK,例如linux中在連接初始建立時候的quick ACK模式。
二、擁塞避免(congestion avoidance)
從慢啟動可以看到,cwnd可以很快的增長上來,從而最大程度利用網絡帶寬資源,但是cwnd不能一直這樣無限增長下去,一定需要某個限制。TCP使 用了一個叫慢啟動門限(ssthresh)的變量,當cwnd超過該值后,慢啟動過程結束,進入擁塞避免階段。慢啟動門限 ssthresh 的用法如下:
當 cwnd < ssthresh 時,使用慢開始算法。
當 cwnd > ssthresh 時,停止使用慢開始算法而改用擁塞避免算法。
當 cwnd = ssthresh 時,既可使用慢開始算法,也可使用擁塞避免算法。
擁塞避免算法的思路是讓擁塞窗口 cwnd 緩慢地增大,假設cwnd=k*SMSS,當發送端收到一個包的ACK反饋的時候,按照cwnd=cwnd+(1/k)*SMSS。這樣每經過一個RTT發送k個數據包的時候,cwnd增長了大約一個SMSS,通常上我們即描述為每經過一個往返時間 RTT 就把發送方的擁塞窗口 cwnd 加 1,使擁塞窗口 cwnd 按接近線性規律緩慢增長。這種增長方式叫做“加法增大”(Additive Increase)。另外當接收方使用delay ACK時,cwnd的增長仍然是接近線性的,只不過增長速度相對慢一些。總結如下
1)收到一個ACK時,cwnd = cwnd + 1/cwnd
2)當每過一個RTT時,cwnd = cwnd + 1
簡單圖示如下
無論在慢開始階段還是在擁塞避免階段,在Tahoe版本中,只要發送方判斷網絡出現擁塞(其根據就是觸發了快速重傳或者RTO重傳),就要更新慢開始門限 ssthresh =max(flight size/2,2*SMSS),因為一般情況下flight size接近或者等於cwnd,所以也可以認為更新ssthresh =max(cwnd/2,2*SMSS),這個過程叫做“乘法減小”(Multiplicative Decrease)。加法增大與乘法減小常常合稱為AIMD算法。另外,Windows中的實現據傳為ssthresh =max(min(cwnd, awnd)/2, 2*SMSS) 。總結如下
- sshthresh = max(cwnd/2,2*SMSS) (一般來說當發生丟包的時候 cwnd一般都是大於等於4的,所以也可以認為ssthresh =max(cwnd/2,2*SMSS)=cwnd/2)
- cwnd 重置為 1
- 進入慢啟動過程
三、擁塞控制示例
上面所講解的慢啟動和擁塞避免就是Tahoe版本的TCP實現,這個也是帶有擁塞控制功能的第一個TCP版本,隨着4.2 BSD UNIX一起發布。同時Tahoe版本是支持快速重傳的,但是Tahoe版本對於快速重傳和RTO重傳的處理是一致(Reno版本的TCP對於快速重傳和RTO重傳的處理略有不同,接下來的擁塞控制系列會講解)。
下面我們看一個圖解示例以加深理解,下圖中橫坐標以RTT為單位,縱坐標cwnd用報文段為單位(可以看成SMSS大小為單位),我們假設接收端通告的窗口足夠大,只考慮cwnd的變化。
分別對圖中標示的箭頭做如下說明
1、在標號為1的箭頭處,TCP初始連接進行數據交換,開始慢啟動,初始cwnd=IW=1,ssthresh=16,在傳輸輪次0-4階段進行慢啟動過程,cwnd按照1-2-4-8-16的順序進行指數增長
2、在標號為2的箭頭處,cwnd=16=ssthresh,此時觸發擁塞避免過程,開始線性增長,在傳輸輪次4-12階段,cwnd按照16-17-18-19-20-21-22-23-24進行線性增長。
3、在標號為3的箭頭處,TCP發生了RTO重傳,認為網絡發生擁塞,於是設置ssthresh=cwnd/2=12,cwnd=1重新進行慢啟動過程
4、在標號為4的箭頭處,TCP從cwnd=1開始重新開始慢啟動過程
5、在標號為5的箭頭處,當 cwnd = 12 時改為執行擁塞避免算法,擁塞窗口按按線性規律增長,每經過一個往返時延就增加一個 MSS 的大小。
附件列表