一、前言
這篇博客來講講TCP的擁塞控制機制,這是TCP中比較復雜的一個部分,它與TCP的很多內容都有關聯,但是這里不可能將這些內容都說一遍,所以以下描述將建立在讀者對TCP的機制有一定了解的基礎之上。這一部分內容確實有些復雜,我盡量在少涉及TCP其他內容的條件下將它敘述清楚。
二、正文
2.1 什么是擁塞控制
我們都知道,網絡錯綜復雜,在這個復雜的網絡中,很少有兩台主機是直接相連。盡管如此,我們還是可以通過網絡與其他主機通信,這是為什么?因為我們發送到網絡中的數據,當達到網絡中的一個節點時(假設是路由器),它會根據數據包含的地址,幫我們將數據轉發到離目的地更近的路由器,或直接轉發到目的地。但是,這些路由器不是直接就可以轉發的,它們需要先將接收到的數據放入自己的內存(可能還要做一些處理),再從中取出進行轉發。
這里就面臨一個問題:路由器的內存是有限的,若同一時間到達某個路由器的數據太多,這個路由器將無法接收所有的數據,只能將一部分丟棄;或者同一台路由器數據太多,后面到達的數據將要等待較長的時間才會被轉發。網絡中的數據太多,導致某個路由器處理不過來或處理地太慢,這就是網絡擁塞。若是對於TCP這種有重傳機制的傳輸協議,當發生數據丟失時,重傳數據將延長數據到達的時間;同時,高頻率的重傳,也將導致網絡的擁塞得不到緩解。擁塞控制,就是在網絡中發生擁塞時,減少向網絡中發送數據的速度,防止造成惡性循環;同時在網絡空閑時,提高發送數據的速度,最大限度地利用網絡資源。說的簡單點,就和堵車差不多,路就這么寬,來的車多了,自然過的就慢,所以在必要的時候要限號。
2.2 擁塞控制的方法
在理論中,擁塞控制有兩種實現方式:
- 端到端擁塞控制:在這種擁塞控制方法中,由發送數據的端系統自己來判斷是否擁塞,然后調整傳輸速率。比如說發送的數據已經超時卻還沒有接收到確認報文,數據往返時延過高,接收到對同一個數據段的重復確認......都可以認為是網絡擁塞的現象;若發送端檢測到這種現象,就應該降低發送數據的速率,若沒有,則可以慢慢提高速率;
- 網絡輔助的擁塞控制:由網絡中的路由器來告訴發送方,網絡的擁塞情況。一般有兩種方式:(1)路由器直接向發送端發送報文,告知網絡擁塞情況;(2)路由器更改數據段中的某個標志,來提示網絡中的擁塞情況,然后數據將這個標志攜帶到目的主機,再由目的主機根據這個標志,向發送端發送報文,告知擁塞情況(被包含在確認報文中);
2.3 TCP的擁塞控制方法
因為網絡層不會提供擁塞的反饋信息,所以TCP協議采用的是第一種方式——自己判斷網絡的擁塞情況。當TCP檢測到網絡擁塞,則降低數據的發送速率,否則增加數據的發送速率。這里就將面臨三個問題:
TCP如何限制數據的發送速率;TCP如何檢測網絡中是否擁塞;TCP采用什么算法來調整速率(什么時候調整,調整多少);
首先來回答第一個問題。了解TCP的應該知道,TCP不是發送一個數據段,接收到確認后再發送另一個數據段,它采用的是流水線的方式。TCP的每一個數據段都有一個序號,而TCP維護一個發送窗口來發送是數據,這個窗口就是一個區間。所有序號位於這個窗口內的數據段都會被一次性發送,而不需要等待之前發送的數據段被確認。而每當最早發送出去的數據段被確認,窗口就會向前移動,直到移動到第一個沒有被確認的序號,這時候又會有新的數據段序號被包含在窗口中,然后被發送出去。所以限制數據發送速率最好的方式就是限制窗口的大小。在發送方的TCP程序會跟蹤和維護一個叫做擁塞窗口的變量,用來進行擁塞控制。擁塞窗口被稱為cwnd。在TCP發送端,所有被發送但是還沒收到確認的數據段必須落在這個窗口中,所有,當網絡擁塞時,TCP程序將減小cwnd,而網絡通暢時,增大cwnd,以此來控制數據發送的速率。
接着來回答第二個問題。TCP程序將通過數據發送的一些現象來推測網絡是否擁塞,比如:
-
若發送一條數據段后,成功接收到了接收方的確認報文,則可以認為網絡沒有擁塞;
-
若發送出一條數據段后,在規定時間內沒有收到確認報文(丟失或時延太大),則可以認為網絡出現了擁塞;
-
若連續收到接收方對同一條報文的三次冗余確認(也就是四次確認),則可以推測那條報文丟失,即發生了擁塞;
上面的第三種情況,與TCP的快速重傳機制有關,我這里不詳細說明,只是大致介紹一下。TCP無法保證數據能夠按順序到達接收端,所以,可能出現序號靠后的數據報反倒先到達的情況。而TCP接收方並不是接收到哪一條報文,就向發送方發送哪條報文的確認,它是通過發送當前應該接收到的序號最小的報文進行確認。舉個例子:
- 發送方同時發送了
1,2,3,4,5這五個序號的報文,假設接收方接收到序號1的報文,於是將向發送方發送一個確認號為2的確認報文(TCP發送方通過確認號來判斷接收方接收到的報文),告訴發送方我已經收到2之前的報文了,下一條報文我想要2; - 接收方接收到
2號報文后,發送確認號3,告訴發送方我接收到了3之前的所有報文; - 這時候因為網絡的不確定性,在
3號沒有到達前,4號報文先到達了,接收方接收到后,將它放入緩存,並依舊回復確認號3,表示它需要的是3號報文; - 可是,之后到達的卻
5,於是它將5放入緩存,並依舊回復3; - 直到
3遲遲的到來,這時候接收方接收3,同時發現緩存中存在4和5,於是回復發送方確認號6,表示自己已經接收到了6之前的全部報文,下一條需要6;
在這里,發送方一共接收到了三條確認號為3的確認報文(確認號為3是對2號報文的確認),第一條正常,后面兩條冗余。而所謂的快速重傳就是指:若發送方接收到對同一條報文的三次冗余確認(也就是四次確認),就認為這條報文的下一條已經丟失,於是不管計時器是否超時,都直接重傳這條報文的下一條。上面的例子中,2號報文被冗余確認了兩次,還不構成快速重傳的條件。而為什么是三次,其實就是概率和時間長短的折中選擇。
下面回到正題,當快速重傳的條件發生,發送方將認為出現了擁塞導致丟包。所有歸根到底,TCP判斷擁塞的方式就是檢測有沒有丟包。於是我們就可以回答第三個問題了,TCP如何調整發送速率——在沒有丟包時慢慢提高擁塞窗口cwnd的大小,當發生丟包事件時,減少cwnd的大小。當然,具體的算法要復雜的多,TCP調整擁塞窗口的主要算法有 慢啟動 , 擁塞避免 以及 快速恢復 ,其中前兩個是TCP規范要求必須實現的,而第三個則是推薦實現的,TCP根據情況在這三者之間切換。 下面我就來一一介紹。
2.4 慢啟動
在講解之前,首先得知道一些名詞:
- MSS:最大報文段長度,
TCP雙方發送的報文段中,包含的數據部分的最大字節數; - cwnd:擁塞窗口,
TCP發送但還沒有得到確認的報文的序號都在這個區間; - RTT:往返時間,發送方發送一個報文,到接收這個報文的確認報文所經歷的時間;
- ssthresh:慢啟動閾值,慢啟動階段,若
cwnd的大小達到這個值,將轉換到擁塞避免模式;
慢啟動是建立TCP連接后,采用的第一個調整發送速率的算法(或叫模式)。在這個階段,cwnd通常被初始化為1MSS,這個值比較小,在這個時候,網絡一般還有足夠的富余,而慢啟動的目的就是盡快找到上限。在慢啟動階段,發送方每接收到一個確認報文,就會將cwnd增加1MSS的大小,於是:
- 初始
cwnd=1MSS,所以可以發送一個TCP最大報文段,成功確認后,cwnd = 2MSS; - 此時可以發送兩個
TCP最大報文段,成功接收后,cwnd = 4 MSS; - 此時可以發送四個
TCP最大報文段,成功接收后,cwnd = 8 MSS......
由於TCP是一次性將窗口內的所有報文發出,所以所有報文都到達並被確認的時間,近似的等於一個RTT(記住這個結論,后面所述的RTT都是基於這個結論)。所以在這個階段,擁塞窗口cwnd的長度將在每個RTT后翻倍,也就是發送速率將以指數級別增長(所以不要被慢啟動這個名字誤導了)。那這個過程什么時候改變呢,這又分幾種情況:
- 第一種:若在慢啟動的過程中,發生了數據傳輸超時,則此時
TCP將ssthresh的值設置為cwnd / 2,然后將cwnd重新設置為1MSS,重新開始慢啟動過程,這個過程可以理解為試探上限; - 第二種:第一步試探出來的上限
ssthresh將用在此處。若cwnd的值增加到>= ssthresh時,此時若繼續使用慢啟動的翻倍增長方式可能有些魯莽,所以這個時候結束慢啟動,改為擁塞避免模式; - 第三種:若發送方接收到了某個報文的三次冗余確認(即觸發了快速重傳的條件),則進入到快速恢復階段;同時,
ssthresh = cwnd / 2,畢竟發生快速重傳也可以認為是發生擁塞導致的丟包,然后cwnd = ssthresh + 3MSS;
以上就是慢啟動的過程,下面來介紹擁塞避免。
2.5 擁塞避免
剛進入這個模式時,cwnd的大小近似的等於上次擁塞時的值的一半(這是由進入這個模式的條件決定的),也就是說當前的cwnd很接近產生擁塞的值。所以,擁塞避免是一個速率緩慢且線性增長的過程,在這個模式下,每經歷一個RTT(請注意2.4中有關RTT的結論),cwnd的大小增加1MSS,也就是說,假設cwnd包含10個報文的大小,則每接收到一個確認報文,cwnd增加1/10 MSS。這個線性增長的過程什么時候結束,分為兩種情況:
- 第一種:在這個過程中,發生了超時,則表示網絡擁塞,這時候,
ssthresh被修改為cwnd / 2,然后cwnd被置為1MSS,並進入慢啟動階段; - 第二種:若發送方接收到了某個報文的三次冗余確認(即觸發了快速重傳的條件),此時也認為發生了擁塞,則,
ssthresh被修改為cwnd / 2,然后cwnd被置為ssthresh + 3MSS,並進入快速恢復模式;
我們可以看到,慢啟動和擁塞避免在接收到三個冗余的確認報文時,處理方式是一樣的:判斷發生了擁塞,並減小ssthresh 的大小,但是cwnd的大小卻不見得有減小多少,這一點讓人疑惑。我個人認為是這樣,雖然發送方通過接收三次冗余確認報文,判斷可能存在擁塞,但是既然可以收到冗余的確認報文,表示擁塞不會太嚴重,甚至已經不再擁塞,所以對cwnd的減小不是這么劇烈。
2.6 快速恢復
快速恢復和上面兩種模式不太一樣,這種模式在TCP規范中並沒有強制要求實現,只是一種推薦實現的模式。在快速恢復階段,每接收到一個冗余的確認報文,cwnd就增加1MSS,其余不變,而當發生以下兩種情況時,將退出快速恢復模式:
- 第一種:在快速恢復過程中,計時器超時,這時候,
ssthresh被修改為cwnd / 2,然后cwnd被置為1MSS,並進入慢啟動階段; - 第二種:若發送方接收到一條新的確認報文(不是冗余確認),則
cwnd被置為ssthresh,然后進入到擁塞避免模式;
這里有一個疑問,進入到此模式的條件就是接收到三次冗余的確認報文,判斷報文丟失,那為什么再次接收到冗余確認報文時,cwnd還是要增長呢?我搜遍網上博客,只在一篇博客中找到一種說法,我認為還是有一定道理的:此時再次收到一條冗余的確認報文,表示發送端發出的報文又有一條離開網絡到達了接收端(雖然不是接收端當前想要的一條),這說明網絡中騰出了一條報文的空間,所以允許發送端再向網絡中發送一條報文。但是由於當前序號最小的報文丟失,導致擁塞窗口cwnd無法向前移動,於是只好將cwnd增加1MSS,於是發送端又可以發送一條數據段,提高了網絡的利用率。
2.7 三種模式相互轉換狀態圖
下面給出一張三種模式互相轉換的狀態圖,途中箭頭上的是轉換的條件,條件有上下兩部分,橫線上方的是上面事件引起的轉換,而下方是轉換時發生的操作。

三、總結
擁塞控制應該是TCP中相對比較復雜的一個部分了,理解起來不是那么容易。在寫這篇博客之前,對於上面所說的三種模式,我只知道具體的流程,卻有很多地方不清楚為什么要這么做。但是在寫這篇博客時,反復閱讀書本,找博客閱讀的過程讓我對慢慢理解了它們的意圖。這就是我寫博客的理由,通過復述,來加深自己的理解,同時也希望這篇博客對看到的人有所幫助。
四、參考
- 《計算機網絡——自頂向下方法(原書第七版)》
- https://blog.csdn.net/jeason29/article/details/50427397/
