TCP提供了可靠的傳輸服務,這是經過下列方式提供的:
- 分塊發送:應用數據被分割成TCP認爲最適合發送的數據塊。由TCP傳遞給IP的信息單位稱爲報文段或段(segment)
- 定時確認重傳:當TCP發出一個段後,它啓動一個定時器,等待目的端確認收到這個報文段。若是不能及時收到一個確認,將重發這個報文段。
- 當TCP收到發自TCP鏈接另外一端的數據,它將發送一個確認。這個確認不是當即發送,一般將推遲幾分之一秒
- 數據校驗:TCP將保持它首部和數據的檢驗和。這是一個端到端的檢驗和,目的是檢測數據在傳輸過程當中的任何變化。若是收到段的檢驗和有差錯,TCP將丟棄這個報文段和不確認收到此報文段(但願發端超時並重發)。
- 正確排序:因爲IP數據報的到達可能會失序,所以TCP報文段的到達也可能會失序。若是必要,TCP將對收到的數據進行從新排序,將收到的數據以正確的順序交給應用層。
- 重複丟棄:IP數據報會發生重複,TCP的接收端必須丟棄重複的數據。
- 流量控制:TCP鏈接的每一方都有固定大小的緩衝空間。TCP的接收端只容許另外一端發送接收端緩衝區所能接納的數據。這將防止較快主機導致較慢主機的緩衝區溢出。
1、可靠傳輸的原理
因爲網絡環境的複雜性,網絡上的數據可能丟失、發生錯誤,於是可靠傳輸是網絡傳輸中的最基本的問題。只要涉及到網絡就逃避不開這個問題。html
1.可靠數據傳輸協議
1.1 徹底可信信道上的可靠傳輸
若是信道徹底可靠,那麼可靠傳輸就不成問題了,此時的可靠傳輸很是簡單。發送方只須要將數據放到信道上它就能夠可靠的到達接收方,並由接收方接收。可是這種信道是徹底理想化的,不存在的。算法
1.2 會出現比特錯誤的信道上的可靠傳輸
更現實一點的信道是會發生比特錯誤的,假設如今須要在除了會出現比特錯誤以外,其它的特性和徹底可靠信道同樣的信道上進行可靠傳輸.緩存
爲了實現該信道上的可靠傳輸:網絡
- 接收方:須要確認信息是否就是發送方所發送的,而且須要反饋是否有錯誤給發送方
- 發送方:須要在發送的信息中添加額外信息以使得接收方能夠對接收到的信息是否有錯誤進行判斷,而且須要接收接收方的反饋,若是有錯誤發生就要進行重傳
於是在這種信道上進行傳輸須要三種功能:
- 差錯檢測:發送方提供額外信息供接收方進行校驗,接收方進行校驗以判斷是否有錯誤發生。網絡協議通常採用校驗和來完成該任務
- 接收方反饋:接收方須要將是否有錯誤發生的信息反饋給發送方,這就是網絡協議中最多見的ACK(確認)/NAK(否認的確認)機制
- 發送方重傳:在出現錯誤時,發送方須要重傳出錯的分組。重傳也是網絡協議中極常見的機制。
上述機制還有問題,它沒有考慮接收方的反饋出現比特錯誤 即 反饋受損 的情形。採用上述機制,在反饋受損時,發送方能夠了解到這個反饋信息出現了錯誤,可是它沒法知道反饋的是什麼樣的信息,所以也就沒法知道本身該怎麼應對。併發
這能夠有兩種解決辦法:socket
- 發送方提供足夠多的信息,使得接收方不只能夠檢測比特錯誤,並且能夠恢復比特錯誤。這在僅會發生比特錯誤的信道上是理論可行的,代價是須要大量額外的信息。
- 若是收到了受損的反饋,則都認爲是出現了錯誤,就進行重傳。可是這時就可能引入冗餘的分組,由於被重傳的分組可能已經正確的被接收了。
網絡協議中普遍採用的是第2種解決方案,冗餘分組能夠經過一種簡單的機制來解決,這就是分組序列號。被髮送的每一個分組都有一個序列號,接收方只須要檢測該序列號就能夠知道分組是不是冗餘的。函數
在引入序列號後,該機制已經能夠在這種信道上工做了。不過它還能夠作一點變化,有些網絡協議中並不會產生否認的確認(即報告發送方出現了錯誤),它採用的是繼續爲 已經爲之發送過ACK的最後一個正確接收的分組 發送ACK。當發送方收到冗餘的ACK時就知道跟在被冗餘ACK確認的分組以後的分組沒有被正確接收,這就達到了NAK所要的效果。性能
1.3 會出現比特錯誤而且會丟包的信道上的可靠傳輸
這種信道是更常見的信道。比特錯誤的問題已經被校驗和、序號、ACK和重傳解決了。如今須要引入新的機制來解決丟包的問題。
丟包問題的解決很簡單,對於發送方來講只要在必定時內沒有收到分組的ACK就認爲分組丟失了,就進行重傳便可。固然這可能引入冗餘的分組,可是序列號是能夠解決冗餘分組的問題,於是惟一須要肯定的就是所謂的必定的時間內的時間長度。很顯然時間長度至少要爲「往返時延+分組處理時間」。因爲網絡環境的複雜性,該值的估算每每也是每一個網絡協議中的很重要的一部分。spa
2 流水線/可靠數據傳輸協議
上述可靠傳輸協議是一箇中止等待協議,它的性能不好。任意時刻信道內只有一個分組(或者數據分組或者確認分組)。這就極大的浪費了帶寬。發送方的利用率是:
(分組長度/傳輸速率) / (RTT +分組長度/傳輸速率)
傳輸速率即信道的速率。這個利用率是很低的。解決利用率低的問題的很完美的一個現實參考模型就是工廠流水線。爲了模擬流水線,對可靠傳輸協議的一個修改是:不採用中止等待協議,而是容許發送方發送多個分組而無需等待確認。具體的修改包括:
- 增長分組序號範圍,由於每一個傳輸的分組須要有一個惟一的序號,並且同時存在多個未確認的分組
- 協議的發送方和接收方須要緩存多個分組,發送方至少須要緩存已發送但未確認的分組
所需分組序號的範圍和對緩存分組的要求取決於如何解決分組丟失、出錯或者超時的問題。.net
流水線的差錯恢復有兩種手段:回退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,不然接收方就沒法區分一個分組是新的分組仍是一個重傳。
2、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定義的原則:
- 發送TCP分段時,若是尚未重傳定時器開啓,那麼開啓它。
- 發送TCP分段時,若是已經有重傳定時器開啓,再也不開啓它。
- 收到一個非冗餘ACK時,若是有數據在傳輸中,從新開啓重傳定時器。
- 收到一個非冗餘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的窗口,這就限制發送方每次只能發送包含一個字節的有效載荷的報文段。
對於這種糊塗窗口綜合症,即應用程序消耗數據比到達的慢,有兩種建議的解決方法:
- Clark解決方法 Clark解決方法是隻要有數據到達就發送確認,但通告的窗口大小爲零,這個過程持續到緩存空間已能放入具備最大長度的報文段或者緩存空間的一半已經空了。
- 延遲確認 第二個解決方法是延遲一段時間後再發送確認。這時接收方不當即確認收到的報文段。接收方在確認收到的報文段以前一直等待,直到入緩存有足夠的空間爲止。該方法阻止了發送端滑動其窗口,當發送端發送完其數據後,它就停下來了。這樣就防止了這種症狀。延遲的確認還減小了通訊量。接收端不須要確認每個報文段。但它有可能使發送端重傳其未被確認的報文段。能夠給延遲的確認加一個時間限制來下降該方法缺點的影響。
3.Nagle算法
Nagle算法的核心思想是任意時刻,最多隻能有一個未被確認的小段。 所謂「小段」,指的是小於MSS尺寸的數據塊。Nagle算法的規則:
- 若是包長度達到MSS,則容許發送;
- 若是該包含有FIN,則容許發送;
- 設置了TCP_NODELAY選項,則容許發送;
- 未設置TCP_CORK選項時,若全部發出去的小數據包(包長度小於MSS)均被確認,則容許發送;
- 上述條件都未知足,但發生了超時(通常爲200ms),則當即發送。
Nagle算法在任意時刻只容許存在一個未被確認的報文段,但他它並不關心報文段的大小,所以它事實上就是一個擴展的中止等待協議,只不過它是基於報文段的而不是基於字節的。Nagle算法徹底由TCP協議的ACK機制決定,所以也有一些缺點,好比若是對端ACK回覆很快的話,Nagle事實上不會拼接太多的數據包,雖然避免了網絡擁塞,網絡整體的利用率依然很低。
在某些時刻也可能會須要關閉該算法,尤爲是交互式的TCP應用,由於這種應用指望及時收到響應。這能夠經過打開TCP_NODELAY選項來實現。
4.TCP_CORK 選項
設置該選項後,內核會盡力把小數據包拼接成一個大的數據包(一個MTU)再發送出去,固然若必定時間後(通常爲200ms,該值尚待確認),內核仍然沒有組合成一個MTU時也必須發送現有的數據。
5.Nagle算法與CORK算法區別
Nagle算法和CORK算法很是相似,可是它們也有區別:
- 它們的目地不一樣。Nagle算法主要避免網絡由於有太多的小報文段而擁塞,而CORK算法則是爲了提升網絡的利用率,使得整體上協議頭佔用的比例儘量的小。
- 用戶經過TCP_NODELAY來啓用或禁用Nagle算法而經過TCP_CORK來啓用或禁用CORK算法。
- 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不是以線性的方式增大擁塞窗口,而是以指數的方式增長的,即:
- 初始設置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)。
- 收到對該報文的ack,則cwnd被設置爲2個MSS,能夠發送兩個報文
- 收到對2個報文的確認後,cwnd更新爲4個MSS,能夠發送四個報文,一依次類推。
該過程會一直持續直到遇到一個丟包事件爲止(或者等於ssthresh時,ssthresh事實上是用於區分是進行的擁塞避免仍是慢啓動,初始化時它被設置爲65536),事實上該算法至關於每收到對一個MSS的確認就將cwnd增大一個MSS(cwnd決定了當前能夠發送cwnd/MSS個報文,當這個cwnd/MSS個報文段都被確認後,就將cwdn增大了cwnd/MSS × MSS即cwnd)。發送方取擁塞窗口與對端通告窗口中的最小值做爲發送上限。因爲初始的擁塞窗口很小的值,由於該啓動方式被稱爲慢啓動。
可是對於SCTP,它只有在知足三個條件時才增大cwnd,min(上次發送的全部數據的大小,MTU):
- cwnd已經被用完
- 累積確認被更新了
- 當前不處於快速恢復模式
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. 快速恢復
快速恢復通常和快速重傳一塊兒實現,其算法爲:
- 當收到第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)做爲退出快速恢復的序列號)
- 再收到重複的ACK時,cwnd增長一個MSS。
- 當收到確認新數據包的ACK時,把cwnd設置爲第一步中的ssthresh的值。此時就從新進入到了第一步丟包時本應進入的擁塞避免。(對於SCTP,若是收到的SACK的累積確認確認了步驟1中的退出快速恢復的序列號,則退出快速恢復)
快速恢復意在經過快速重傳丟失的報文,使得接收端能夠將累積確認的最後一個報文和亂序到達的報文之間的「間隙」填充起來,從而儘快進行新的確認。在這個過程當中,每當接收斷收到一個新的亂序的報文,發送端就將本身的cwnd增大一個MSS,使得發送端能夠儘快填充接收端的「間隙」,直到累積確認的報文段以後有新的連續的報文段被接收端接收到了,這個時候接收端會更新一個新的ACK,發送端在收到該ACK後就退出快速恢復並進入擁塞避免。
須要注意的是不管是擁塞避免仍是慢啓動,SCTP規定,若是某個地址不用於發送數據,則每一個RTO都要對cwnd作一次調整,調整後的值爲max(cwnd/2, 4*MTU).
3、緊急方式
TCP提供了「緊急方式(urgentmode)」,它使鏈接的一端能夠告訴另鏈接的一端有些 「緊急數據」已經被放置在數據流中。緊急數據的處理方式由接收方決定。
要發送緊急數據須要設置TCP首部中的兩個字段來。URG比特被置1,而且要將16bit的緊急指針設置爲一個正的偏移量,該偏移量必須與TCP首部中的序號字段相加,以便得出緊急數據的最後一個字節的序號。
TCP必須通知接收進程,什麼時候已接收到一個緊急數據指針以及什麼時候某個緊急數據指針還不在此鏈接上,或者緊急指針是否在數據流中向前移動。接着接收進程能夠讀取數據流,並必須可以被告知什麼時候碰到了緊急數據指針。只要從接收方當前讀取位置到緊急數據指針之間有數據存在,就認爲應用程序處於「緊急方式」。在緊急指針經過以後,應用程序便轉回到正常方式。
沒有辦法指明緊急數據從數據流的何處開始。TCP經過鏈接傳送的惟一信息就是緊急方式已經開始(TCP首部中的URG比特)和指向緊急數據最後一個字節的指針。其餘的事情留給應用程序去處理。