TCP詳解(3):重傳、流量控制、擁塞控制……

數據傳輸

  在TCP的數據傳送狀態,很多重要的機制保證了TCP的可靠性和強壯性。它們包括:使用序號,對收到的TCP報文段進行排序以及檢測重複的數據;使用校驗和來檢測報文段的錯誤;使用確認和計時器來檢測和糾正丟包或延時。
  在TCP的連接創建狀態,兩個主機的TCP層間要交換初始序號(ISN:initial sequence number)。這些序號用於標識字節流中的數據,並且還是對應用層的數據字節進行記數的整數。通常在每個TCP報文段中都有一對序號和確認號。TCP報文發送者認爲自己的字節編號爲序號,而認爲接收者的字節編號爲確認號。TCP報文的接收者爲了確保可靠性,在接收到一定數量的連續字節流後才發送確認。這是對TCP的一種擴展,通常稱爲選擇確認(Selective Acknowledgement)。選擇確認使得TCP接收者可以對亂序到達的數據塊進行確認。每一個字節傳輸過後,ISN號都會遞增1。
  通過使用序號和確認號,TCP層可以把收到的報文段中的字節按正確的順序交付給應用層。序號是32位的無符號數,在它增大到2^32-1時,便會迴繞到0。對於ISN的選擇是TCP中關鍵的一個操作,它可以確保強壯性和安全性。
來看個例子:
  1)發送方首先發送第一個包含序列號爲1(可變化)和1460字節數據的TCP報文段給接收方。接收方以一個沒有數據的TCP報文段來回復(只含報頭),用確認號1461來表示已完全收到並請求下一個報文段。
  2)發送方然後發送第二個包含序列號爲1461和1460字節數據的TCP報文段給接收方。正常情況下,接收方以一個沒有數據的TCP報文段來回復,用確認號2921(1461+1460)來表示已完全收到並請求下一個報文段。發送接收這樣繼續下去。
  3)然而當這些數據包都是相連的情況下,接收方沒有必要每一次都回應。比如,他收到第1到5條TCP報文段,只需迴應第五條就行了。在例子中第3條TCP報文段被丟失了,所以儘管他收到了第4和5條,然而他只能迴應第2條。
  4)發送方在發送了第三條以後,沒能收到迴應,因此當時鍾(timer)過時(expire)時,他重發第三條。(每次發送者發送一條TCP報文段後,都會再次啓動一次時鐘:RTT)。
  5)這次第三條被成功接收,接收方可以直接確認第5條,因爲4,5兩條已收到。
  
  

  Acknowledgment Number Out = Sequence Number In + Bytes of Data Received

1.TCP重傳
  報文重傳是TCP最基本的錯誤恢復功能,它的目的是防止報文丟失。
  報文丟失的可能因素有很多種,包括應用故障,路由設備過載,或暫時的服務宕機。報文級別速度是很高的,而通常報文丟失是暫時的,因此TCP能夠發現和恢復報文丟失顯得尤爲重要。

  重傳機制在實現數據可靠傳輸功能的同時,也引起了相應的性能問題:何時進行數據重傳?如何保證較高的傳輸效率?
  重傳時間過短:在網絡因爲擁塞引起丟包時,頻繁的重傳會進一步加劇網絡擁塞,引起丟包,惡化網絡傳輸性能。
  重傳時間過長:接收方長時間無法完成數據接收,引起長時間佔用連接線路造成資源損耗、傳輸效率較低等問題。
  針對上述問題,TCP中設計了超時重傳機制。該機制規定當發送方A向B發送數據包P1時,開啓時長爲RTO(Retransmission Timeout)的重傳定時器,如果A在RTO內未收到B對P1的確認報文,則認爲P1在網絡中丟失,此時重新發送P1。由此,引出RTO大小的設定問題。

  決定報文是否有必要重傳的主要機制是重傳計時器(retransmission timer),它的主要功能是維護重傳超時(RTO)值。當報文使用TCP傳輸時,重傳計時器啓動,收到ACK時計時器停止。報文發送至接收到ACK的時間稱爲往返時間(RTT)。對若干次時間取平均值,該值用於確定最終RTO值。在最終RTO值確定之前,確定每一次報文傳輸是否有丟包發生使用重傳計時器,下圖說明了TCP重傳過程。
  

  當報文發送之後,但接收方尚未發送TCP ACK報文,發送方假設源報文丟失並將其重傳。重傳之後,RTO值加倍;如果在2倍RTO值到達之前還是沒有收到ACK報文,就再次重傳。如果仍然沒有收到ACK,那麼RTO值再次加倍。如此持續下去,每次重傳RTO都翻倍,直到收到ACK報文或發送方達到配置的最大重傳次數。
  最大重傳次數取決於發送操作系統的配置值。默認情況下,Windows主機默認重傳5次。大多數Linux系統默認最大15次。兩種操作系統都可配置。

1)超時重傳
  超時重傳機制用來保證TCP傳輸的可靠性。每次發送數據包時,發送的數據報都有seq號,接收端收到數據後,會回覆ack進行確認,表示某一seq號數據已經收到。發送方在發送了某個seq包後,等待一段時間,如果沒有收到對應的ack回覆,就會認爲報文丟失,會重傳這個數據包。
2)快速重傳
  接受數據一方發現有數據包丟掉了(並不是所期望的值。這意味着報文在傳送中丟失。接收端注意到報文亂序,並且在第三個報文中發送重複ACK)。就會發送重複ACK報文告訴發送端重傳丟失的報文。
  當重傳主機從發送端接收到3個重複ACK時,它會假設此報文確實在傳送中丟失,並且立即發送一個快速重傳。一旦觸發了快速重傳,所有正在傳輸的其他報文都被放入隊列中,直到快速重傳報文發送爲止。過程如下圖所示:

  

  比較超時重傳和快速重傳,可以發現超時重傳是發送端在傻等超時,然後觸發重傳;而快速重傳則是接收端主動告訴發送端數據沒收到,然後觸發發送端重傳。
  由此可看出,快速重傳機制在一定程度上彌補了超時重傳機制,使得重傳更加及時。

2.流量控制
  這裏主要說TCP滑動窗口流量控制。滑動窗口(Sliding window )是一種流量控制技術。早期的網絡通信中,通信雙方不會考慮網絡的擁擠情況直接發送數據。由於大家不知道網絡擁塞狀況,一起發送數據,導致中間結點阻塞掉包,誰也發不了數據。所以就有了滑動窗口機制來解決此問題。
  爲了理解TCP的窗口大小是怎麼樣變化的,我們先需要理解它的含義。最簡單的方式就是認爲窗口大小”意味着接收方能接收數據的大小”,這也是說接收端設備再應用程序讀取buffer中數據之前,能從對端連接處理多少數據。比如說server端窗口大小是360,那麼就意味着server端一次只能從客戶端接收不超過360bytes的數據。當server端收到數據,它會將數據放到buffer裏,然後server端必須對這份數據做兩件事:
  1)server端必須發送一個 ACK 到client端來確認數據已經收到
  2)server端必須處理這份數據,把它交給對應的應用程序
  要區分上面兩件事對理解窗口很重要,接收方收到數據後會確認,但是數據並不一定是裏面就是從buffer裏取出的,這是受應用層邏輯控制的。所以很有可能如果接收數據過快,而取出數據更慢,就會導致buffer滿。一旦這種情況發生,窗口大小就開始調整來防止接收方負載過高。

  TCP頭裏有一個字段叫Window,又叫Advertised-Window,這個字段是接收端告訴發送端自己還有多少緩衝區可以接收數據。於是發送端就可以根據這個接收端的處理能力來發送數據,而不會導致接收端處理不過來。   Window是一個16bit位字段,它代表的是窗口的字節容量,也就是TCP的標準窗口最大爲2^16-1=65535個字節。
  另外在TCP的選項字段中還包含了一個TCP窗口擴大因子,option-kind爲3,option-length爲3個字節,option-data取值範圍0-14。窗口擴大因子用來擴大TCP窗口,可把原來16bit的窗口,擴大爲31bit。
  
  

  1)對於TCP會話的發送方,任何時候在其發送緩存內的數據都可以分爲4類,“已經發送並得到對端ACK的”,“已經發送但還未收到對端ACK的”,“未發送但對端允許發送的”,“未發送且對端不允許發送”。“已經發送但還未收到對端ACK的”和“未發送但對端允許發送的”這兩部分數據稱之爲發送窗口。

  

  當收到接收方新的ACK對於發送窗口中後續字節的確認是,窗口滑動,滑動原理如下圖:

  

  一個例子:

  

滑動窗口協議
1)比特滑動窗口協議
  當發送窗口和接收窗口的大小固定爲1時,滑動窗口協議退化爲停等協議(stop-and-wait)。該協議規定發送方每發送一幀後就要停下來,等待接收方已正確接收的確認(acknowledgement)返回後才能繼續發送下一幀。由於接收方需要判斷接收到的幀是新發的幀還是重新發送的幀,因此發送方要爲每一個幀加一個序號。由於停等協議規定只有一幀完全發送成功後才能發送新的幀,因而只用一比特來編號就夠了。

2)後退n協議
  由於停等協議要爲每一個幀進行確認後才繼續發送下一幀,大大降低了信道利用率,因此又提出了後退n協議。後退n協議中,發送方在發完一個數據幀後,不停下來等待應答幀,而是連續發送若干個數據幀,即使在連續發送過程中收到了接收方發來的應答幀,也可以繼續發送。且發送方在每發送完一個數據幀時都要設置超時定時器。只要在所設置的超時時間內仍未收到確認幀,就要重發相應的數據幀。如:當發送方發送了N個幀後,若發現該N幀的前一個幀在計時器超時後仍未返回其確認信息,則該幀被判爲出錯或丟失,此時發送方就不得不重新發送出錯幀及其後的N幀。

來看下面的例子,這裏假設n=9:

  

  首先發送方一口氣發送10個數據幀,前面兩個幀正確返回了,數據幀2出現了錯誤,這時發送方被迫重新發送2-8這7個幀,接受方也必須丟棄之前接受的3-8這幾個幀。

  從這裏不難看出,後退n協議一方面因連續發送數據幀而提高了效率,但另一方面,在重傳時又必須把原來已正確傳送過的數據幀進行重傳(僅因這些數據幀之前有一個數據幀出了錯),這種做法又使傳送效率降低。由此可見,若傳輸信道的傳輸質量很差因而誤碼率較大時,連續測協議不一定優於停止等待協議。此協議中的發送窗口的大小爲k,接收窗口仍是1。

3)選擇重傳協議
  在後退n協議中,接收方若發現錯誤幀就不再接收後續的幀,即使是正確到達的幀,這顯然是一種浪費。另一種效率更高的策略是當接收方發現某幀出錯後,其後繼續送來的正確的幀雖然不能立即遞交給接收方的高層,但接收方仍可收下來,存放在一個緩衝區中,同時要求發送方重新傳送出錯的那一幀。
  但是必須強調一點,接收方永遠不會把分組失序地交給應用層.在他們被交付給應用層之前,先要等待那些更早發出來的分組到達。一旦收到重新傳來的幀後,就可以原已存於緩衝區中的其餘幀一併按正確的順序遞交高層。這種方法稱爲選擇重發(SELECTICE REPEAT),其工作過程如圖所示。顯然,選擇重發減少了浪費,但要求接收方有足夠大的緩衝區空間。
  
  

4)零窗口問題
  某些情況下,服務器無法再處理從客戶端發送的數據。可能是由於內存不足,處理能力不夠,或其他原因。這可能會造成數據被丟棄以及傳輸暫停,但接收窗口能夠幫助減小負面影響。
  當上述情況發生時,服務器會發送窗口爲0的報文。當客戶端接收到此報文時,它會暫停所有數據傳輸,但會保持與服務器的連接以傳輸探測(keep-alive Zero Window Probe)報文。探測報文在客戶端以穩定間隙發送,以查看服務器接收窗口狀態。一旦服務器能夠再次處理數據,將會返回非零值窗口大小,傳輸會恢復。下圖示例了零窗口通知過程。

  

5)Silly Window Syndrome(糊塗窗口綜合症)
  Silly window syndrome定義爲一次僅發送少量的TCP負載數據,就像用一個飛機只運送你一個人(你又不是總統,哼),這種情況下帶寬利用率很低,一般儘量避免。

  

  對接收端來說,window size小於某個值,可以直接ack(0)回sender,這樣就把window給關閉了,也阻止了sender再發數據過來。當接收端size重新達到MSS或者接收端緩存區的一半.
  對於發送端來說,呵呵,不是有Nagle’s algorithm嘛。這個算法的思路也是延時處理,兩個主要的條件1)要等到 Window Size>=MSS 或是 Data Size >=MSS,2)等待時間或是超時200ms,滿足這兩個條件之一再發送,否則就是就接着攢數據。

3.擁塞控制
  滑動窗用來做流量控制。流量控制只關注發送端和接受端自身的狀況,而沒有考慮整個網絡的通信情況。擁塞控制,則是基於整個網絡來考慮的。考慮一下這樣的場景:某一時刻網絡上的延時突然增加,那麼,TCP對這個事做出的應對只有重傳數據,但是,重傳會導致網絡的負擔更重,於是會導致更大的延遲以及更多的丟包,於是,這個情況就會進入惡性循環被不斷地放大。試想一下,如果一個網絡內有成千上萬的TCP連接都這麼行事,那麼馬上就會形成“網絡風暴”,TCP這個協議就會拖垮整個網絡。爲此,TCP引入了擁塞控制策略。擁塞策略算法主要包括:慢啓動,擁塞避免,擁塞發生,快速恢復。

  對於擁塞現象,我們可以進一步用圖1來描述。當網絡負載較小時,吞吐量基本上隨着負載的增長而增長,呈線性關係,響應時間增長緩慢。當負載達到網絡容量時,吞吐量呈現出緩慢增長,而響應時間急劇增加,這一點稱爲Knee。假如負載繼續增加,路由器開始丟包,當負載超過一定量時,吞吐量開始急劇下降,這一點稱爲Cliff。
  擁塞控制機制實際上包含擁塞避免(congestion avoidance)和擁塞控制(congestion control)兩種策略。前者的目的是使網絡運行在Knee四周,避免擁塞的發生;而後者則是使得網絡運行在Cliff的左側區域。前者是一種“預防”措施,維持網絡的高吞吐量、低延遲狀態,避免進入擁塞;後者是一種“恢復”措施,使網絡從擁塞中恢復過來,進入正常的運行狀態。

         

  擁塞發生的主要原因在於網絡能夠提供的資源不足以滿足用戶的需求,這些資源包括緩存空間、鏈路帶寬容量和中間節點的處理能力。由於互聯網的設計機制導致其缺乏“接納控制”能力,因此在網絡資源不足時不能限制用戶數量,而只能靠降低服務質量來繼續爲用戶服務,也就是“盡力而爲”的服務。
  擁塞雖然是由於網絡資源的稀缺引起的,但單純增加資源並不能避免擁塞的發生。例如增加緩存空間到一定程度時,只會加重擁塞,而不是減輕擁塞,這是因爲當數據包經過長時間排隊完成轉發時,它們很可能早已超時,從而引起源端超時重發,而這些數據包還會繼續傳輸到下一路由器,從而浪費網絡資源,加重網絡擁塞。同樣如果單純增加鏈路帶寬,如果中間路由來不及好處理,就會造成大量丟包,引發擁塞。
  單純地增加網絡資源之所以不能解決擁塞問題,是因爲擁塞本身是一個動態問題,它不可能只靠靜態的方案來解決,而需要協議能夠在網絡出現擁塞時保護網絡的正常運行。目前對互聯網進行的擁塞控制主要是依靠在源端執行的基於窗口的TCP擁塞控制機制。網絡本身對擁塞控制所起的作用較小。

  擁塞控制假設分組的丟失都是由網絡繁忙造成的。擁塞控制有三種動作,分別對應主機感受到的情況:
  收到一條新確認。這很好,表明當前的單次發送量小於網絡的承載量。
  收到三條對同一分組的確認,即三條重複的確認。單次發送量往往大於3,例如發送序號爲0、10、20、30、40的5條長度爲10字節的分組,其中序號20的丟了,則返回的確認是10、20、20、20。3個20就是重複的確認。
  對某一條分組的確認遲遲未到,即超時。例如發送序號爲0、10、20、30、40的5條長度爲10字節的分組,其中序號30的丟了,則返回的確認是10、20、30、30。這才只有兩條重複確認。然而剛剛說過,單次發送量往往大於3,所以超時更可能是因爲不止一條分組或確認丟失而引起的,這說明網絡比上一情況中的更加繁忙。

  上面提到擁塞控制主要是四個算法:1)慢啓動,2)擁塞避免,3)擁塞發生,4)快速恢復。
  1988年,TCP-Tahoe 提出了1)慢啓動,2)擁塞避免,3)擁塞發生時的快速重傳
  1990年,TCP Reno 在Tahoe的基礎上增加了4)快速恢復

  

1)慢熱啓動算法(Slow Start)
  TCP擁塞控制所使用的一種算法稱爲慢性啓動(slow start),這種算法是基於這樣的想法,它在開始時設置擁塞窗口大小(cwnd)
爲一個最長段長度(MSS),每次接到一個確認時,窗口的大小就增加一個MSS值。窗口是慢速啓動的,但是按指數規則增長。
  開始     —> cwnd = 1
  經過1個RTT後 —> cwnd = 2*1 = 2
  經過2個RTT後 —> cwnd = 2*2= 4
  經過3個RTT後 —> cwnd = 4*2 = 8
  如果帶寬爲W,那麼經過RTT*log2W時間就可以佔滿帶寬。

  

  當然,慢速啓動不能一直繼續下去,到達某個值必須停止該階段。發送方保存一個稱爲ssthresh(慢速啓動閾值)變量,當擁塞窗口中的字節達到這個閾值時,慢速啓動階段結束而下一個階段開始。在大多數實現中,ssthresh值是65536個字節。

2)擁塞避免:加法增加
  以慢速啓動算法開始,則擁塞窗口大小按指數規則增長,這樣增長太快了。爲了在擁塞發生之前避免擁塞,必須降低指數增長的速度。
  擁塞避免的主要思想是加法增大,也就是cwnd的值不再指數級往上升,開始加法增加。此時當窗口中所有的報文段都被確認時,cwnd的大小加1,cwnd的值就隨着RTT開始線性增加,這樣就可以避免增長過快導致網絡擁塞,慢慢的增加調整到網絡的最佳值。

3)擁塞發生算法:乘性減少
  上面討論的兩個機制都是沒有檢測到擁塞的情況下的行爲,那麼當發現擁塞了cwnd又該怎樣去調整呢?

  

  之前提到過,重傳是在兩種情況下發生:
  1)如果RTO超時,那麼存在非常嚴重的擁塞的可能性;包可能已在網絡中丟失。
  在這種情況下,TCP做出強烈的反應。
  a.設置閾值爲cwnd的一半。
  b.重新設置cwnd爲1。
  c.啓動慢速啓動階段。

  2)如果收到3個相同的ACK,那麼存在着輕度擁塞的可能性。TCP在收到亂序到達包時就會立即發送ACK,TCP利用3個相同的ACK來判定數據包的丟失,此時進行快速重傳。
在這種情況下,TCP做出輕度的反應。
  a.設置閾值爲cwnd的一半。
  b.設置cwnd爲閾值(有些實現是閾值加上3)
  c.啓動擁塞避免階段。

4)快速恢復
  “快速恢復”算法是在“快速重傳”算法後添加的,當收到3個重複ACK時,TCP最後進入的不是擁塞避免階段,而是快速恢復階段。快速重傳和快速恢復算法一般同時使用。快速恢復的思想是“數據包守恆”原則,即同一個時刻在網絡中的數據包數量是恆定的,只有當“老”數據包離開了網絡後,才能向網絡中發送一個“新”的數據包,如果發送方收到一個重複的ACK,那麼根據TCP的ACK機制就表明有一個數據包離開了網絡,於是cwnd加1。如果能夠嚴格按照該原則那麼網絡中很少會發生擁塞,事實上擁塞控制的目的也就在修正違反該原則的地方。
  快速恢復狀態是一種介於慢啓動和擁塞避免之間的狀態。它像慢啓動,其中cwnd以指數增長,但是cwnd以ssthresh加3MSS(而不是1)開始。當TCP進入快速恢復階段,可能發生三種主要事件。如果重複ACK繼續到達,那麼TCP保持這種狀態,但是cwnd呈指數增長。如果發生超時,TCP假設網絡中有真實的擁塞,並進入慢啓動狀態。如果一個新的(非重複)ACK到達,TCP進入擁塞避免階段,但是將cwnd的大小減小到ssthresh值,好像三次重複ACK沒有發生過一樣,並且轉換是從慢啓動狀態到擁塞避免狀態。
  具體來說快速恢復的主要步驟是:
  1.當收到3個重複ACK時,把ssthresh設置爲cwnd的一半,把cwnd設置爲ssthresh的值加3,然後重傳丟失的報文段,加3的原因是因爲收到3個重複的ACK,表明有3個“老”的數據包離開了網絡。
  2.再收到重複的ACK時,擁塞窗口增加1。
  3.當收到新的數據包的ACK時,把cwnd設置爲第一步中的ssthresh的值。原因是因爲該ACK確認了新的數據,說明從重複ACK時的數據都已收到,該恢復過程已經結束,可以回到恢復之前的狀態了,也即再次進入擁塞避免狀態。注意,如果在此過程出現超時,則重新進入慢啓動階段。

  好了,講了這麼多,完全可以一圖概之:

  

參考:
http://baike.baidu.com/view/1199185.htm
http://www.knowsky.com/383954.html
http://blog.jobbole.com/71427/
http://blog.csdn.net/todd911/article/details/10026441
http://www.netis.com.cn/flows/2012/08/tcp-%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E7%9A%84%E7%AE%80%E4%BB%8B/
http://www.firefoxbug.com/index.php/archives/2798/
http://coolshell.cn/articles/11609.html
http://www.ccs-labs.org/teaching/rn/animations/gbn_sr/
http://www.cnblogs.com/fll/archive/2008/06/10/1217013.html
http://blog.csdn.net/todd911/article/details/10026441

注)本文有些圖片未找到出處,僅作學習用。

發佈了89 篇原創文章 · 獲贊 9 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章