TCP可靠傳輸詳解


 

TCP提供了可靠的傳輸服務,這是通過下列方式提供的:
  • 分塊發送:應用數據被分割成TCP認為最適合發送的數據塊。由TCP傳遞給IP的信息單位稱為報文段或段(segment)
  • 定時確認重傳:當TCP發出一個段后,它啟動一個定時器,等待目的端確認收到這個報文段。如果不能及時收到一個確認,將重發這個報文段。
  • 當TCP收到發自TCP連接另一端的數據,它將發送一個確認。這個確認不是立即發送,通常將推遲幾分之一秒
  • 數據校驗:TCP將保持它首部和數據的檢驗和。這是一個端到端的檢驗和,目的是檢測數據在傳輸過程中的任何變化。如果收到段的檢驗和有差錯,TCP將丟棄這個報文段和不確認收到此報文段(希望發端超時並重發)。
  • 正確排序:由於IP數據報的到達可能會失序,因此TCP報文段的到達也可能會失序。如果必要,TCP將對收到的數據進行重新排序,將收到的數據以正確的順序交給應用層。
  • 重復丟棄:IP數據報會發生重復,TCP的接收端必須丟棄重復的數據。
  • 流量控制:TCP連接的每一方都有固定大小的緩沖空間。TCP的接收端只允許另一端發送接收端緩沖區所能接納的數據。這將防止較快主機致使較慢主機的緩沖區溢出。

一、可靠傳輸的原理

由於網絡環境的復雜性,網絡上的數據可能丟失、發生錯誤,因而可靠傳輸是網絡傳輸中的最基本的問題。只要涉及到網絡就逃避不開這個問題。

 

1.可靠數據傳輸協議

1.1  完全可信信道上的可靠傳輸

如果信道完全可靠,那么可靠傳輸就不成問題了,此時的可靠傳輸非常簡單。發送方只需要將數據放到信道上它就可以可靠的到達接收方,並由接收方接收。但是這種信道是完全理想化的,不存在的。

 

1.2  會出現比特錯誤的信道上的可靠傳輸

更現實一點的信道是會發生比特錯誤的,假設現在需要在除了會出現比特錯誤之外,其它的特性和完全可靠信道一樣的信道上進行可靠傳輸.

為了實現該信道上的可靠傳輸:

  • 接收方:需要確認信息是否就是發送方所發送的,並且需要反饋是否有錯誤給發送方
  • 發送方:需要在發送的信息中添加額外信息以使得接收方可以對接收到的信息是否有錯誤進行判斷,並且需要接收接收方的反饋,如果有錯誤發生就要進行重傳
因而在這種信道上進行傳輸需要三種功能:
  • 差錯檢測:發送方提供額外信息供接收方進行校驗,接收方進行校驗以判斷是否有錯誤發生。網絡協議一般采用校驗和來完成該任務
  • 接收方反饋:接收方需要將是否有錯誤發生的信息反饋給發送方,這就是網絡協議中最常見的ACK(確認)/NAK(否定的確認)機制
  • 發送方重傳:在出現錯誤時,發送方需要重傳出錯的分組。重傳也是網絡協議中極常見的機制。

上述機制還有問題,它沒有考慮接收方的反饋出現比特錯誤 即 反饋受損 的情形。采用上述機制,在反饋受損時,發送方可以了解到這個反饋信息出現了錯誤,但是它無法知道反饋的是什么樣的信息,因此也就無法知道自己該怎么應對。

這可以有兩種解決辦法:

  • 發送方提供足夠多的信息,使得接收方不僅可以檢測比特錯誤,而且可以恢復比特錯誤。這在僅會發生比特錯誤的信道上是理論可行的,代價是需要大量額外的信息。
  • 如果收到了受損的反饋,則都認為是出現了錯誤,就進行重傳。但是這時就可能引入冗余的分組,因為被重傳的分組可能已經正確的被接收了。

網絡協議中廣泛采用的是第2種解決方案,冗余分組可以通過一種簡單的機制來解決,這就是分組序列號。被發送的每個分組都有一個序列號,接收方只需要檢測該序列號就可以知道分組是否是冗余的。

 

在引入序列號后,該機制已經可以在這種信道上工作了。不過它還可以做一點變化,有些網絡協議中並不會產生否定的確認(即報告發送方出現了錯誤),它采用的是繼續為 已經為之發送過ACK的最后一個正確接收的分組 發送ACK。當發送方收到冗余的ACK時就知道跟在被冗余ACK確認的分組之后的分組沒有被正確接收,這就達到了NAK所要的效果。

 

1.3  會出現比特錯誤並且會丟包的信道上的可靠傳輸

這種信道是更常見的信道。比特錯誤的問題已經被校驗和、序號、ACK和重傳解決了。現在需要引入新的機制來解決丟包的問題。

丟包問題的解決很簡單,對於發送方來說只要在一定時內沒有收到分組的ACK就認為分組丟失了,就進行重傳即可。當然這可能引入冗余的分組,但是序列號是可以解決冗余分組的問題,因而唯一需要確定的就是所謂的一定的時間內的時間長度。很顯然時間長度至少要為“往返時延+分組處理時間”。由於網絡環境的復雜性,該值的估算往往也是每個網絡協議中的很重要的一部分。

 

2 流水線/可靠數據傳輸協議

上述可靠傳輸協議是一個停止等待協議,它的性能很差。任意時刻信道內只有一個分組(或者數據分組或者確認分組)。這就極大的浪費了帶寬。發送方的利用率是:
 (分組長度/傳輸速率) /  (RTT +分組長度/傳輸速率)
傳輸速率即信道的速率。這個利用率是很低的。解決利用率低的問題的很完美的一個現實參考模型就是工廠流水線。為了模擬流水線,對可靠傳輸協議的一個修改是:不采用停止等待協議,而是允許發送方發送多個分組而無需等待確認。具體的修改包括:
  • 增加分組序號范圍,因為每個傳輸的分組需要有一個唯一的序號,而且同時存在多個未確認的分組
  • 協議的發送方和接收方需要緩存多個分組,發送方至少需要緩存已發送但未確認的分組

所需分組序號的范圍和對緩存分組的要求取決於如何解決分組丟失、出錯或者超時的問題。

           流水線的差錯恢復有兩種手段:回退N步(GBN)和  選擇重傳。

2.1  GBN

該協議中,流水線中未被確認的分組數目不能超過N。 GBN協議中發送方兩個重要的序號為:
  • 基序號(send_base):最早未被確認的序號
  • 下一個序號(nextseqnum):最小未使用的序號
發送方看到的序號被分為幾部分:
  • [0, send_base - 1]:已發送並且收到確認了的序列號
  • [send_base,  nextseqnum - 1]:已發送但未收到確認了的序列號
  • [nextseqnum, send_base + N - 1]:可被用於發送新分組的序列號
  • 大於等於send_base + N:不能使用的序列號
本協議中,已發送但未被確認的分組數目不能超過N。因此N可以看做是從第一個已發送但未被確認的序號(就是基序號)開始的長為N的序號窗口,也稱為發送窗口。隨着分組被確認,該窗口逐漸滑動,send_base增加,但是其大小不變,因此該協議也稱為滑動窗口協議。假設序號空間總大小為X,所有的序號計算都要對X取模。

1)在GBN協議中,發送方需要處理以下事件:

  • 上層調用進行發送:當發送數據時,首先判斷發送窗口是否已滿,只有不滿時才會啟動發送,如果滿了則不能發送或者緩存或者通知調用者,這取決於實現。(判滿 )
  • 收到ACK:如果收到了分組n的ACK,並且分組n的序號在[send_base,  nextseqnum - 1]之間,則更新send_base。GBN協議采用累積確認,含義是如果發送方接收到了對分組n的確認,則表明分組n之前的所有分組都已經被接收。
  • 超時事件:GBN協議名字來自該協議對分組丟失或超時事件的處理。如果分組丟失或者指定的時間內仍未收到對已發送但是未收到確認的分組的確認,則發送方重傳[send_base, nextseqnum-1]之間的所有分組。實現中可以只為序號為send_base的分組啟動一個定時器,如果send_base被更新就重啟send_base相關的定時器,如果沒有已經被發送但未被確認的分組,則關閉定時器。

2)GBN協議中,接收方的行為:

       接收方接收到分組n時,如果n是按序接收的,即分組n之前的所有分組都已經到達,則發送一個對分組n的確認,並將分組提交給上層。所有其它情況,接收方都丟棄分組,並重傳對最后一個按序接收的分組的確認。這種設計簡化了接收方接收緩存的設計,同時被丟棄的分組早晚都會被重傳,因而可靠性也是有保證的。
       GBN協議的接收方只維護了一個期望收到的下一個序號的信息,並保存在expectedseqnum中,在expectedseqnum之前的所有分組都已經被正確接收並提交給了上層。接收方具體的行為如下:
  • 如果收到的分組的序號與expectedseqnum相同,則將分組提交給上層,並更新它的值為下一個期望收到的序號。
  • 如果收到的分組的序號大於expectedseqnum,就丟棄分組。
  • 如果收到的分組的序號小於expectedseqnum,就重傳對最后一個按序接收的分組的確認。由於GBN是累積確認的,因此該確認可以確保發送窗口可以向前移動。

2.2  選擇重傳

GBN協議存在缺陷,在超時或者分組丟失時它會重傳所有的在[send_base, nextseqnum-1]之間的分組,而接收方也會丟棄非按序到達的分組,這也浪費了帶寬。

1)發送方

選擇重傳協議通過讓發送方僅重傳那些它懷疑在發送方出錯的分組來避免不必要的重傳。

該協議中發送方看到的序號空間與GBN協議完全相同,唯一不同的在於序號空間[send_base,  nextseqnum - 1]中的分組包含了一些已經被接收方確認了的分組。

發送行為:
  • 上層調用進行發送:當發送數據時,首先判斷發送窗口是否已滿,只有不滿時才會啟動發送,如果滿了則不能發送或者緩存或者通知調用者,這取決於實現,
  • 收到ACK:如果收到分組n的ACK,並且n的序號在[send_base,  nextseqnum - 1]之間,則將分組n狀態更新為已確認,如果n的序號等於send_base,則更新send_base到下一個已發送但未被確認的分組的序號處。
  • 超時事件:該協議使用選擇重傳,因此每個分組都有一個定時器,如果某個分組的定時器到期了就重傳該分組。

2)接收方

該協議中,接收方也需要維護一個長度為N的接收緩存,並且需要維護一個變量rcv_base,它表示接收方期望收到的下一個分組的序號。接收方看到序號空間被划分為:
  • [0, rcv_base - 1]:已經正確接收並且被確認了的序號空間
  • [rcv_base, rcv_base + N -1]:期望接收的序號空間
  • 大於rcv_base + N :不可用的序號空間
接收行為:
  • 收到了序號在[rcv_base – N , rcv_base -1]之間的分組:產生一個ACK,這是必須的,因為發送方重傳了該分組就說明它沒有收到對它的ACK,如果不發送ACK,發送方的窗口將不會移動。假設N=5,考慮以下場景
    • 發送方發送了序號為5,6,7,8,9的分組,因此send_base=5
    • 接收方接收了這些分組並且為所有分組都發送了ACK,因此rcv_base=10
    • 所有ACK都丟失
    • 發送方重傳分組5
    • 接收方必須發送ACK,否則發送方窗口將不會被更新
  • 收到了序號在[rcv_base, rcv_base + N -1]之間的分組:發送ACK給發送方。如果該分組以前未接收,則緩存它;如果該分組的序號等於rcv_base,則將從rcv_base開始的序號連續的被緩存的接收到的分組提交給上層,同時更新rcv_base為有序接收到的最后一個分組的序號的下一個序號。
  • 接收到其它分組:丟棄分組

注意到接收方必須可以處理序號范圍在[rcv_base, rcv_base + N -1]和[rcv_base – N , rcv_base -1]之間的分組,這兩個區間的總長度為2N。因此這就意味着序號空間至少要有2N個可用序號值,即序號空間的大小至少要為2N,否則接收方就無法區分一個分組是新的分組還是一個重傳。

 

二、TCP數據傳輸

當TCP連接建立之后,應用程序即可使用該連接進行數據收發。應用程序將數據提交給TCP,TCP將數據放入自己的緩存,並且在其認為合適的時候將數據發送出去。在TCP中,數據會被當做字節流並按照MSS的大小進行分段,然后加上TCP頭部並提交給網絡層。之后數據就會被網絡層提交給目地主機,目地主機的IP層會將分組提交給TCP,TCP根據報文段的頭部信息找到相應的socket,並將報文段提交給該socket,socket是和應用關聯的,也就提交給了應用。

 

1.TCP的可靠傳輸

       IP提供的服務是盡力交付的服務,也是不可靠的服務。但是TCP在IP之上提供了可靠度傳輸服務。TCP采用了流水線下的可靠數據傳輸協議,但是在差錯恢復時,並沒有簡單的采取GBN協議或者選擇重傳協議,而是將二者結合了起來。

 

      TCP采用了累積確認的方式,這類似於GBN,即如果TCP發送了對某個序號N的確認,則表明在N之前的所有字節流都已經被正確接收。但是另一方面,TCP又不會像GBN協議那樣簡單丟棄失序到達的報文段,而是會將它們緩存起來,但是這些被緩存的報文段不會逐個被確認。當發生超時時,TCP只會重傳發生超時的那一個報文段。

      TCP還允許接收方選擇性的確認失序到達的分組,而不是累積的對最后一個確認最后一個正確到達的分組,將它與TCP所采取的選擇重傳結合起來看就很想選擇重傳協議的工作機制。因此說TCP的差錯恢復結合了GBN和選擇重傳。

 

      選擇重傳中每個報文段都有自己的超時值,TCP采用了RFC2988建議的機制用一個單一定時器來完成該功能。RFC2988定義的原則:

  1. 發送TCP分段時,如果還沒有重傳定時器開啟,那么開啟它。
  2. 發送TCP分段時,如果已經有重傳定時器開啟,不再開啟它。
  3. 收到一個非冗余ACK時,如果有數據在傳輸中,重新開啟重傳定時器。
  4. 收到一個非冗余ACK時,如果沒有數據在傳輸中,則關閉重傳定時器。

1.1  基本工作過程

發送,ACK,重傳共同保證了TCP的可靠傳輸,其基本工作過程(考慮累積確認的情形)如下:

1.2  發送分組

TCP會為發送的每一個分組分配一個唯一的序號,該序號和ISN以及該報文段在字節流中的位置有關。序號被填入TCP頭部的序號字段。如果重傳定時器還沒有運行,則會啟動重傳定時器。

1.3 接收到ACK

由於是累積確認的,因此如果收到的ACK是合法的,即是對已發送但未被確認的報文段的確認,則更新send_base,並且如果還有未被確認的已發送的報文段,則重啟重傳定時器。

1.4 超時

重傳引起超時的報文段,並重啟定時器。TCP的重傳不一定是重傳引起超時的報文段本身,TCP可能重新進行分組然后重傳,唯一被保證的是所有數據都會被傳輸。

1.5 產生ACK

每個TCP報文段的TCP頭部都固定包含了ACK域,如果在傳輸中,為了確認一個報文段而單獨發送一個ACK,則該ACK就是一個數據部分長度為0的特殊TCP報文段,如果這樣的分段太多,網絡的利用率就會下降。為此,TCP采取了延遲確認的機制。其工作過程:
  • 如果收到的報文段的序號等於rcv_base,並且所有在rvc_base之前的報文段的確認都已經被發送,則只更新rcv_base,但是延遲該報文段的ACK的發送,最多延遲500ms。延遲的ACK可能會在接收端有數據要發送給發送端時被發送或者在接收端有多個ACK需要被發送給發送端時被發送。
  • 如果收到的報文段的序號等於rcv_base,並且有延遲的ACK待發送,則更新rcv_base,並發送累積的ACK以確認這兩個按序報文段
  • 如果收到的報文段的序號大於rcv_base,則發送冗余的ACK,即重傳對已經確認過的最后一個按序到達的報文段的ACK

2.往返時延的估算與超時

TCP協議定義了RTT來代表一個TCP分段的往返時間。然而由於IP網絡是盡力而為的,並且路由是動態的,且路由器可能緩存或者丟棄IP數據報,因此一個TCP連接的RTT是動態變化的,因而也需要動態測量。樣本RTT(SampleRTT)是報文段被發出到報文段的確認被收到的時間間隔。TCP不會為每一個發動的報文段測量一個SampleRTT,而是僅為已發送但是未被確認的分組測量SampleRTT。這樣做是為了產生一個近似於RTT的SampleRTT。TCP不會為重傳的報文段測量SampleRTT。
得到多個SampleRTT后,TCP會嘗試使用這些信息來盡可能得到一個較為准確的RTT,為此TCP采用了經常被采用的收到即使用一個濾波器來對多個SampleRTT進行計算。TCP使用如下的濾波器來計算一個EstimateRTT:
 EstimateRTT= (1- α) * EstimateRTT  +α * SampleRTT
RFC2988給出的α參考值為1/8。EstimateRTT 是一個平滑后的RTT。
除此之外,TCP還將RTT的變化率也應該考慮在內,如果變化率過大,則通過以變化率為自變量的函數為主計算RTT(如果陡然增大,則取值為比較大的正數,如果陡然減小,則取值為比較小的負數,然后和平均值加權求和),反之如果變化率很小,則取測量平均值。TCP計算了一個DevRTT。它用於估量SampleRTT偏離EstimateRTT的程度。其公式為:
 DevRTT= (1-β)* DevRTT +  β* |SampleRTT - EstimateRTT |
β的參考值為1/4。

之后重傳定時器的值會被設置為EstimateRTT + 4 * DevRTT

3.倍數增加的重傳間隔

       在發生超時重傳時,TCP不是以固定的時間間隔來重傳的,而是會再每次重傳時都將下一次重傳的間隔設置為上次重傳間隔的2倍,因此重傳間隔是倍數增加的。直到收到確認或者徹底失敗。由於正常發送報文段時,重傳定時器的超時值為EstimateRTT + 4 * DevRTT,因此第一重傳時會將下一次的超時時間設置為2倍的該值,依次類推。

4.快速重傳

      倍數增加的重傳間隔會增大端到端的時延,使得發送端可能不得不等待很長時間才能重傳報文段。冗余ACK使得TCP可以得到分組丟失的線索。TCP基於冗余ACK提供了一種快速重傳機制。其原理是:如果收到了對相同數據的三個冗余的ACK,發送端就認為跟在這個被確認了三次的報文段之后的報文段丟失了,因此重傳它,而不是等待它的超時定時器到期。這就是快速重傳。

5.流量控制

      在TCP中,連接雙都為該連接設置了接收緩存,當報文段被連接的一端接收時,它會進入該接收緩存,被接收的數據並不一定立即被提交給應用程序。因為應用可能由於各種而沒能及時讀取緩存中的數據。如果發送方發送的數據太快,而應用沒有及時讀取被緩存的數據,緩存就會變滿,此時為了防止緩存溢出,就要丟棄報文段,顯然丟棄已經正確接收的報文段是對網絡資源的浪費。為了解決該問題,TCP需要提供一種機制來防止接收緩存溢出。
      TCP提供了流量控制功能,來防止發送方發送過快而導致接收方緩存溢出的情形出現。這是通過讓接收方通告一個接收窗口大小來實現的。接收窗口的大小包含在TCP頭部的窗口大小字段中。其工作原理為:
接收方通過窗口大小通告本地可以接收的報文段的總大小。發送方將根據該信息來判斷自己可以發送多少數據。發送方保證自己發送的未被確認的報文段的總大小不超過接收方通告的窗口大小。對應到我們所描述的GBN和選擇重傳協議中,就是發送方會用接收方通告的窗口大小更新本地的窗口大小N的值。一個可視化的描述如下圖:

 

但是TCP連接的一端可能通告一個大小為0的窗口,這時候接收到對端通告大小為0的窗口的一端並不會停止發送,而是會啟動一個定時器來發送窗口探測報文段,該報文段只包含一個字節,該報文段會被接收方確認,該定時器會一直重啟自身來發送窗口探測包直到對端通告了一個大小不為0的窗口為止。定時器的超時值會逐漸增大到一個最大值,然后固定以該值重發窗口探測包。

2.SWS(糊塗窗口綜合症)

糊塗窗口綜合症是指在發送端應用進程產生數據很慢、或接收端應用進程處理接收緩沖區數據很慢,或二者都存在時,通過TCP連接傳輸的報文段會很小,這會導致有效載荷很小。極端情況下,有效載荷可能只有1個字節;而傳輸開銷有40字節(20字節的IP頭+20字節的TCP頭) 這種現象就叫糊塗窗口綜合症。

1.發送端引起的SWS

如果TCP發送端的應用是產生數據很慢的應用程序(比如telnet),它可能一次只產生一個字節。這種應用程序一次只往TCP提交一個字節的數據,如果沒有特殊的處理,這就會導致TCP每次都產生一個只有一個有效載荷的報文段。最終導致網絡的有效利用率非常低。解決辦法是防止TCP發送過小的報文段,如果應用提交的數據較短,就等待足夠的數據來組成一個較大的報文段再發送,為了防止長時間等待導致時延過大,可以加入一個等待時間限制,如果時間到期還沒等到足夠的數據就直接發送不再等待。Nagle算法就是這樣的一種算法。

2.接收端引起的SWS

如果TCP接收端的應用處理數據的速度很慢,一次只從TCP緩存取走很小數量的數據,比如一個字節,而發送方發送的速度較快,這就會導致接收方的緩存被填滿,然后接收方每次在應用取走一個字節的數據后都通告一個大小為1的窗口,這就限制發送方每次只能發送包含一個字節的有效載荷的報文段。
對於這種糊塗窗口綜合症,即應用程序消耗數據比到達的慢,有兩種建議的解決方法:
  1. Clark解決方法 Clark解決方法是只要有數據到達就發送確認,但通告的窗口大小為零,這個過程持續到緩存空間已能放入具有最大長度的報文段或者緩存空間的一半已經空了。
  2. 延遲確認 第二個解決方法是延遲一段時間后再發送確認。這時接收方不立即確認收到的報文段。接收方在確認收到的報文段之前一直等待,直到入緩存有足夠的空間為止。該方法阻止了發送端滑動其窗口,當發送端發送完其數據后,它就停下來了。這樣就防止了這種症狀。延遲的確認還減少了通信量。接收端不需要確認每一個報文段。但它有可能使發送端重傳其未被確認的報文段。可以給延遲的確認加一個時間限制來降低該方法缺點的影響。

3.Nagle算法

Nagle算法的核心思想是任意時刻,最多只能有一個未被確認的小段。 所謂“小段”,指的是小於MSS尺寸的數據塊。Nagle算法的規則:
  1. 如果包長度達到MSS,則允許發送;
  2. 如果該包含有FIN,則允許發送;
  3. 設置了TCP_NODELAY選項,則允許發送;
  4. 未設置TCP_CORK選項時,若所有發出去的小數據包(包長度小於MSS)均被確認,則允許發送; 
  5. 上述條件都未滿足,但發生了超時(一般為200ms),則立即發送。
Nagle算法在任意時刻只允許存在一個未被確認的報文段,但他它並不關心報文段的大小,因此它事實上就是一個擴展的停止等待協議,只不過它是基於報文段的而不是基於字節的。Nagle算法完全由TCP協議的ACK機制決定,因此也有一些缺點,比如如果對端ACK回復很快的話,Nagle事實上不會拼接太多的數據包,雖然避免了網絡擁塞,網絡總體的利用率依然很低。 
在某些時刻也可能會需要關閉該算法,尤其是交互式的TCP應用,因為這種應用期望及時收到響應。這可以通過打開TCP_NODELAY選項來實現。

4.TCP_CORK 選項

設置該選項后,內核會盡力把小數據包拼接成一個大的數據包(一個MTU)再發送出去,當然若一定時間后(一般為200ms,該值尚待確認),內核仍然沒有組合成一個MTU時也必須發送現有的數據。

5.Nagle算法與CORK算法區別

Nagle算法和CORK算法非常類似,但是它們也有區別:

 

  1. 它們的目地不同。Nagle算法主要避免網絡因為有太多的小報文段而擁塞,而CORK算法則是為了提高網絡的利用率,使得總體上協議頭占用的比例盡可能的小。
  2. 用戶通過TCP_NODELAY來啟用或禁用Nagle算法而通過TCP_CORK來啟用或禁用CORK算法。
  3. Nagle算法關心的是網絡擁塞問題,只要有ACK回來則發包,而CORK算法關心的是報文段大小,在前后數據發送間隔很短的前提下,即使你是分散發送多個小數據包,你也可以通過使能CORK算法將這些內容拼接在一個包內,如果此時用Nagle算法的話,則可能做不到這一點。

 

3.擁塞控制

當網絡擁塞時數據報不能及時被轉,在分組轉發網絡中,數據報就會被排隊,甚至出現丟包因此說網絡擁塞會帶來網絡銷:
  • 引入大的排隊時延
  • 當數據報被丟失時發送方必須重傳,因此引入了重傳開銷
  • 當數據報被丟失時,丟失路由器的上游路由器做的工作都變成了無用功
因此必須采取技術來盡可能避免擁塞。由於IP層不提供網絡是否擁塞的信息,因而TCP必須自己來判斷網絡是否出現了擁塞。
TCP將丟包(可能是超時也可能是收到了三個冗余的ACK)看做是網絡擁塞的線索,將RTT增加看做是網絡擁塞程度加重的線索。
TCP讓連接雙方根據自己所判斷的網絡擁塞的程度來限制其發往網絡的流量。TCP在連接的每一端都增加了一個變量cong_win,它表示擁塞窗口,用於限制一端可以向網絡發送的數據。TCP連接的每一端都保證它所已經發送的未被確認的報文段的總大小不會超過擁塞窗口和對方通告的窗口大小中的較小的那一個。
TCP通過ACK到達的情況(即是否到達,到達的速率)來調整擁塞窗口的大小。

1.慢啟動

在建立TCP連接時,擁塞窗口被初始化為 min (4*SMSS, max (2*SMSS, 4380 bytes)) 。但是TCP不是以線性的方式增大擁塞窗口,而是以指數的方式增加的,即:

 

  1. 初始設置cwnd=1個 min (4*SMSS, max (2*SMSS, 4380 bytes)) ,發送一個報文,在RFC5861中有更新,但是總體就是一個較小的值(SMSS:SENDER MAXIMUM SEGMENT SIZE : The SMSS is the size of the largest segment that the sender can transmit)。(對於SCTP,cwnd初始值為min(4*MTU)。
  2. 收到對該報文的ack,則cwnd被設置為2個MSS,可以發送兩個報文
  3. 收到對2個報文的確認后,cwnd更新為4個MSS,可以發送四個報文,一依次類推。

 

該過程會一直持續直到遇到一個丟包事件為止(或者等於ssthresh時,ssthresh事實上是用於區分是進行的擁塞避免還是慢啟動,初始化時它被設置為65536),事實上該算法相當於每收到對一個MSS的確認就將cwnd增大一個MSS(cwnd決定了當前可以發送cwnd/MSS個報文,當這個cwnd/MSS個報文段都被確認后,就將cwdn增大了cwnd/MSS × MSS即cwnd)。發送方取擁塞窗口與對端通告窗口中的最小值作為發送上限。由於初始的擁塞窗口很小的值,因為該啟動方式被稱為慢啟動。

但是對於SCTP,它只有在滿足三個條件時才增大cwnd,min(上次發送的所有數據的大小,MTU):

 

  1. cwnd已經被用完
  2. 累積確認被更新了
  3. 當前不處於快速恢復模式

 

2.擁塞避免(加性增,乘性減)

當遇到一個丟包事件時,TCP會將其擁塞窗口降低為原來值的一半,同時將ssthresh設置為 max (FlightSize / 2, 2*SMSS) (FlightSize:The amount of data that has been sent but not yet acknowledged.)(對於SCTP為:max(cwnd/2, 4*MTU),總體而言,SCTP和TCP的擁塞控制、避免算法是一致的,用MTU替換掉MSS即可。另外SCTP只在有大於等於cwnd的數據正在被發送時(onflight)才更新cwnd) 。其目的是通過降低發送速率來緩解、避免擁塞。但是擁塞窗口大小至少為1個MMS。
在非啟動期間,當TCP探測到沒有擁塞時,即當連接的一端收到了對它已經發送但未被確認的報文段的確認時,它就會增大擁塞窗口,增大的方式是每收到一個ACK將擁塞窗口大小增大MSS×MSS/cwnd,因此在該算法下,經過一個RTT,cwnd最多增大一個MSS。
因此TCP的擁塞控制方式又稱為加性增,乘性減。擁塞窗口的增加受惠的只是自己,而擁塞窗口減少受益的是大家,當出現擁塞時,通過乘性減雖然損害了自己,但是可以讓更多的其它網絡參與者受益,這也證實TCP擁塞控制中的公平性的核心所在。

3.對超時事件的處理

雖然超時和收到三個冗余ACK(SCTP中不存在三個冗余ACK,對應的事件是三個SACK都不包含都某個報文段的確認,則認為該報文段丟失需要重傳)都被認為是丟包事件,但是TCP在二者的處理上並不全相同。當收到三個冗余ACK時,TCP的處理就是“加性增,乘性減”。但是如果是超時事件,則TCP會更新ssthresh的值為max (FlightSize / 2, 2*SMSS)(對於SCTP,max(cwnd/2, 4*MTU)),然后進入“慢啟動”過程,即將擁塞窗口設置為一個MSS,然后指數增加擁塞窗口大小。此時“慢啟動”會持續到遇到一個丟包事件或者擁塞窗口被增大到了ssthresh,如果是增大到了ssthresh則進入“擁塞避免”的模式,即開始加性增。
因此對於丟包事件來說,只要發生了丟包,則ssthresh都會更新max (FlightSize / 2, 2*SMSS)(對於SCTP,max(cwnd/2, 4*MTU))。如果是超時,cwnd被設置為一個MSS(對於SCTP,1個MTU );如果是冗余ACK,則cwnd被更新為更新后的ssthresh。隨后cwnd的更新方式取決於它和ssthresh之間的大小關系,如果cwdn小於或等於ssthresh,則就是在執行慢啟動,否則就是在執行擁塞避免。

對超時時間和三個冗余ACK處理方式上存在區別的原因在於,收到冗余ACK表明了網絡時可以交付報文段的,是可用的,而超時則就是明確的丟包。而收到冗余ACK至少表明網絡還是可用的,只是出現了丟包,事實上,在出現三個丟包的時候,采用的是快速恢復+快速重傳+擁塞避免。

 

4. 快速恢復

快速恢復一般和快速重傳一起實現,其算法為:
  1. 當收到第3個重復的ACK時(對於SCTP,有三個SACK都不包含某個報文的確認時。具體規則:1.只有報文TSN小於當前SACK中新被確認的最大TSN的被丟失的報文的丟失計數才會增加,2.如果已經處於快速重傳模式,並且當前的SACK會更新累積確認點,則所有丟失的報文的丟失計數都會增加(這一點將保證報文會被盡快快速重傳,從而使得盡快退出快速重傳模式)。),把ssthresh設置為max (FlightSize / 2, 2*SMSS)(對於SCTP,max(cwnd/2, 4*MTU)),把cwnd設置為ssthresh的值加3個SMSS(對於SCTP,不增加)。然后重傳丟失的報文段。因為收到3個重復的ACK表明有三個報文已經離開網絡到達了接收斷,被且被接收端給接收了。(同時,對於SCTP,還會將當前已經發出的最大的報文序號(TSN)作為退出快速恢復的序列號)
  2. 再收到重復的ACK時,cwnd增加一個MSS。
  3. 當收到確認新數據包的ACK時,把cwnd設置為第一步中的ssthresh的值。此時就重新進入到了第一步丟包時本應進入的擁塞避免。(對於SCTP,如果收到的SACK的累積確認確認了步驟1中的退出快速恢復的序列號,則退出快速恢復)

       快速恢復意在通過快速重傳丟失的報文,使得接收端可以將累積確認的最后一個報文和亂序到達的報文之間的“間隙”填充起來,從而盡快進行新的確認。在這個過程中,每當接收斷收到一個新的亂序的報文,發送端就將自己的cwnd增大一個MSS,使得發送端可以盡快填充接收端的“間隙”,直到累積確認的報文段之后有新的連續的報文段被接收端接收到了,這個時候接收端會更新一個新的ACK,發送端在收到該ACK后就退出快速恢復並進入擁塞避免。

        需要注意的是無論是擁塞避免還是慢啟動,SCTP規定,如果某個地址不用於發送數據,則每個RTO都要對cwnd做一次調整,調整后的值為max(cwnd/2, 4*MTU).

 

三、緊急方式

       TCP提供了“緊急方式(urgentmode)”,它使連接的一端可以告訴另連接的一端有些 “緊急數據”已經被放置在數據流中。緊急數據的處理方式由接收方決定。
       要發送緊急數據需要設置TCP首部中的兩個字段來。URG比特被置1,並且要將16bit的緊急指針設置為一個正的偏移量,該偏移量必須與TCP首部中的序號字段相加,以便得出緊急數據的最后一個字節的序號。
       TCP必須通知接收進程,何時已接收到一個緊急數據指針以及何時某個緊急數據指針還不在此連接上,或者緊急指針是否在數據流中向前移動。接着接收進程可以讀取數據流,並必須能夠被告知何時碰到了緊急數據指針。只要從接收方當前讀取位置到緊急數據指針之間有數據存在,就認為應用程序處於“緊急方式”。在緊急指針通過之后,應用程序便轉回到正常方式。

       沒有辦法指明緊急數據從數據流的何處開始。TCP通過連接傳送的唯一信息就是緊急方式已經開始(TCP首部中的URG比特)和指向緊急數據最后一個字節的指針。其他的事情留給應用程序去處理。

 


免責聲明!

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



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