計算機網絡——TCP的擁塞控制(超詳細)


一、前言

  這篇博客來講講TCP的擁塞控制機制,這是TCP中比較復雜的一個部分,它與TCP的很多內容都有關聯,但是這里不可能將這些內容都說一遍,所以以下描述將建立在讀者對TCP的機制有一定了解的基礎之上。這一部分內容確實有些復雜,我盡量在少涉及TCP其他內容的條件下將它敘述清楚。


二、正文

 2.1 什么是擁塞控制

  我們都知道,網絡錯綜復雜,在這個復雜的網絡中,很少有兩台主機是直接相連。盡管如此,我們還是可以通過網絡與其他主機通信,這是為什么?因為我們發送到網絡中的數據,當達到網絡中的一個節點時(假設是路由器),它會根據數據包含的地址,幫我們將數據轉發到離目的地更近的路由器,或直接轉發到目的地。但是,這些路由器不是直接就可以轉發的,它們需要先將接收到的數據放入自己的內存(可能還要做一些處理),再從中取出進行轉發。

  這里就面臨一個問題:路由器的內存是有限的,若同一時間到達某個路由器的數據太多,這個路由器將無法接收所有的數據,只能將一部分丟棄;或者同一台路由器數據太多,后面到達的數據將要等待較長的時間才會被轉發。網絡中的數據太多,導致某個路由器處理不過來或處理地太慢,這就是網絡擁塞。若是對於TCP這種有重傳機制的傳輸協議,當發生數據丟失時,重傳數據將延長數據到達的時間;同時,高頻率的重傳,也將導致網絡的擁塞得不到緩解。擁塞控制,就是在網絡中發生擁塞時,減少向網絡中發送數據的速度,防止造成惡性循環;同時在網絡空閑時,提高發送數據的速度,最大限度地利用網絡資源。說的簡單點,就和堵車差不多,路就這么寬,來的車多了,自然過的就慢,所以在必要的時候要限號。


 2.2 擁塞控制的方法

  在理論中,擁塞控制有兩種實現方式:

  • 端到端擁塞控制:在這種擁塞控制方法中,由發送數據的端系統自己來判斷是否擁塞,然后調整傳輸速率。比如說發送的數據已經超時卻還沒有接收到確認報文,數據往返時延過高,接收到對同一個數據段的重復確認......都可以認為是網絡擁塞的現象;若發送端檢測到這種現象,就應該降低發送數據的速率,若沒有,則可以慢慢提高速率;
  • 網絡輔助的擁塞控制:由網絡中的路由器來告訴發送方,網絡的擁塞情況。一般有兩種方式:(1)路由器直接向發送端發送報文,告知網絡擁塞情況;(2)路由器更改數據段中的某個標志,來提示網絡中的擁塞情況,然后數據將這個標志攜帶到目的主機,再由目的主機根據這個標志,向發送端發送報文,告知擁塞情況(被包含在確認報文中);

 2.3 TCP的擁塞控制方法

  因為網絡層不會提供擁塞的反饋信息,所以TCP協議采用的是第一種方式——自己判斷網絡的擁塞情況。當TCP檢測到網絡擁塞,則降低數據的發送速率,否則增加數據的發送速率。這里就將面臨三個問題:

  1. TCP如何限制數據的發送速率;
  2. TCP如何檢測網絡中是否擁塞;
  3. TCP采用什么算法來調整速率(什么時候調整,調整多少);

  首先來回答第一個問題。了解TCP的應該知道,TCP不是發送一個數據段,接收到確認后再發送另一個數據段,它采用的是流水線的方式。TCP的每一個數據段都有一個序號,而TCP維護一個發送窗口來發送是數據,這個窗口就是一個區間。所有序號位於這個窗口內的數據段都會被一次性發送,而不需要等待之前發送的數據段被確認。而每當最早發送出去的數據段被確認,窗口就會向前移動,直到移動到第一個沒有被確認的序號,這時候又會有新的數據段序號被包含在窗口中,然后被發送出去。所以限制數據發送速率最好的方式就是限制窗口的大小。在發送方的TCP程序會跟蹤和維護一個叫做擁塞窗口的變量,用來進行擁塞控制。擁塞窗口被稱為cwnd。在TCP發送端,所有被發送但是還沒收到確認的數據段必須落在這個窗口中,所有,當網絡擁塞時,TCP程序將減小cwnd,而網絡通暢時,增大cwnd,以此來控制數據發送的速率。

  接着來回答第二個問題。TCP程序將通過數據發送的一些現象來推測網絡是否擁塞,比如:

  • 若發送一條數據段后,成功接收到了接收方的確認報文,則可以認為網絡沒有擁塞;

  • 若發送出一條數據段后,在規定時間內沒有收到確認報文(丟失或時延太大),則可以認為網絡出現了擁塞;

  • 若連續收到接收方對同一條報文的三次冗余確認(也就是四次確認),則可以推測那條報文丟失,即發生了擁塞;

  上面的第三種情況,與TCP快速重傳機制有關,我這里不詳細說明,只是大致介紹一下。TCP無法保證數據能夠按順序到達接收端,所以,可能出現序號靠后的數據報反倒先到達的情況。而TCP接收方並不是接收到哪一條報文,就向發送方發送哪條報文的確認,它是通過發送當前應該接收到的序號最小的報文進行確認。舉個例子:

  1. 發送方同時發送了1,2,3,4,5這五個序號的報文,假設接收方接收到序號1的報文,於是將向發送方發送一個確認號為2的確認報文(TCP發送方通過確認號來判斷接收方接收到的報文),告訴發送方我已經收到2之前的報文了,下一條報文我想要2
  2. 接收方接收到2號報文后,發送確認號3,告訴發送方我接收到了3之前的所有報文;
  3. 這時候因為網絡的不確定性,在3號沒有到達前,4號報文先到達了,接收方接收到后,將它放入緩存,並依舊回復確認號3,表示它需要的是3號報文;
  4. 可是,之后到達的卻5,於是它將5放入緩存,並依舊回復3;
  5. 直到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后翻倍,也就是發送速率將以指數級別增長(所以不要被慢啟動這個名字誤導了)。那這個過程什么時候改變呢,這又分幾種情況:

  • 第一種:若在慢啟動的過程中,發生了數據傳輸超時,則此時TCPssthresh的值設置為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中相對比較復雜的一個部分了,理解起來不是那么容易。在寫這篇博客之前,對於上面所說的三種模式,我只知道具體的流程,卻有很多地方不清楚為什么要這么做。但是在寫這篇博客時,反復閱讀書本,找博客閱讀的過程讓我對慢慢理解了它們的意圖。這就是我寫博客的理由,通過復述,來加深自己的理解,同時也希望這篇博客對看到的人有所幫助。


四、參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM