1. 程式人生 > >TCP可靠傳輸詳解

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_baseGBN協議採用累積確認,含義是如果傳送方接收到了對分組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位元)和指向緊急資料最後一個位元組的指標。其他的事情留給應用程式去處理

From: http://blog.csdn.net/goodluckwhh/article/details/10161753