1. 程式人生 > >TCP滑動視窗/超時重傳/慢啟動機制

TCP滑動視窗/超時重傳/慢啟動機制

一. TCP的優勢
從傳輸資料來講,TCP/UDP以及其他協議都可以完成資料的傳輸,從一端傳輸到另外一端,TCP比較出眾的一點就是提供一個可靠的,流控的資料傳輸,所以實現起來要比其他協議複雜的多,先來看下這兩個修飾詞的意義:
 1. Reliability ,提供TCP的可靠性,TCP的傳輸要保證資料能夠準確到達目的地,如果不能,需要能檢測出來並且重新發送資料。
 2. Data Flow Control,提供TCP的流控特性,管理髮送資料的速率,不要超過裝置的承載能力
為了能夠實現以上2點,TCP實現了很多細節的功能來保證資料傳輸,比如說 滑動視窗適應系統,超時重傳機制,累計ACK等,這次先介紹一下滑動視窗的一些知識點。

二.  TCP滑動視窗

  • 滑動視窗引入
在閱讀一些文章的時候看到一個大牛做的視訊,非常不錯易於理解滑動視窗的機制,可以先看下:http://v.youku.com/v_show/id_XNDg1NDUyMDUy.html

IP層協議屬於不可靠的協議,IP層並不關係資料是否傳送到了對端,TCP通過確認機制來保證資料傳輸的可靠性,在比較早的時候使用的是send--wait--send的模式,其實這種模式叫做stop-wait模式,傳送資料方在傳送資料之後會啟動定時器,但是如果資料或者ACK丟失,那麼定時器到期之後,收不到ACK就認為傳送出現狀況,要進行重傳。這樣就會降低了通訊的效率,如下圖所示,這種方式被稱為 positive acknowledgment with retransmission (PAR)


 
  • 滑動視窗
可以假設一下,來優化一下PAR效率低的缺點,比如我讓傳送的每一個包都有一個id,接收端必須對每一個包進行確認,這樣裝置A一次多傳送幾個片段,而不必等候ACK,同時接收端也要告知它能夠收多少,這樣傳送端發起來也有個限制,當然還需要保證順序性,不要亂序,對於亂序的狀況,我們可以允許等待一定情況下的亂序,比如說先快取提前到的資料,然後去等待需要的資料,如果一定時間沒來就DROP掉,來保證順序性!
在TCP/IP協議棧中,滑動視窗的引入可以解決此問題,先來看從概念上資料分為哪些類
1. Sent and Acknowledged:這些資料表示已經發送成功並已經被確認的資料,比如圖中的前31個bytes,這些資料其實的位置是在視窗之外了,因為視窗內順序最低的被確認之後,要移除視窗,實際上是視窗進行合攏,同時開啟接收新的帶傳送的資料
2. Send But Not Yet Acknowledged:這部分資料稱為傳送但沒有被確認,資料被髮送出去,沒有收到接收端的ACK,認為並沒有完成傳送,這個屬於視窗內的資料。
3. Not Sent,Recipient Ready to Receive:這部分是儘快傳送的資料,這部分資料已經被載入到快取中,也就是視窗中了,等待發送,其實這個視窗是完全有接收方告知的,接收方告知還是能夠接受這些包,所以傳送方需要儘快的傳送這些包

4. Not Sent,Recipient Not Ready to Receive: 這些資料屬於未傳送,同時接收端也不允許傳送的,因為這些資料已經超出了傳送端所接收的範圍


 
對於接收端也是有一個接收視窗的,類似傳送端,接收端的資料有3個分類,因為接收端並不需要等待ACK所以它沒有類似的接收並確認了的分類,情況如下
1.  Received and ACK Not Send to Process:這部分資料屬於接收了資料但是還沒有被上層的應用程式接收,也是被快取在視窗內
2.  Received  Not ACK: 已經接收並,但是還沒有回覆ACK,這些包可能輸屬於Delay ACK的範疇了
3.  Not Received:有空位,還沒有被接收的資料。
傳送視窗和可用視窗
對於傳送方來講,視窗內的包括兩部分,就是傳送視窗(已經發送了,但是沒有收到ACK),可用視窗,接收端允許傳送但是沒有傳送的那部分稱為可用視窗。
1. Send Window : 20個bytes 這部分值是有接收方在三次握手的時候進行通告的,同時在接收過程中也不斷的通告可以傳送的視窗大小,來進行適應

2. Window Already Sent: 已經發送的資料,但是並沒有收到ACK。


 
  • 滑動視窗原理
TCP並不是每一個報文段都會回覆ACK的,可能會對兩個報文段傳送一個ACK,也可能會對多個報文段傳送1個ACK【累計ACK】,比如說傳送方有1/2/3 3個報文段,先發送了2,3 兩個報文段,但是接收方期望收到1報文段,這個時候2,3報文段就只能放在快取中等待報文1的空洞被填上,如果報文1,一直不來,報文2/3也將被丟棄,如果報文1來了,那麼會發送一個ACK對這3個報文進行一次確認。
舉一個例子來說明一下滑動視窗的原理:
1. 假設32~45 這些資料,是上層Application傳送給TCP的,TCP將其分成四個Segment來發往internet
2. seg1 32~34 seg3 35~36 seg3 37~41 seg4 42~45  這四個片段,依次傳送出去,此時假設接收端之接收到了seg1 seg2 seg4
3. 此時接收端的行為是回覆一個ACK包說明已經接收到了32~36的資料,並將seg4進行快取(保證順序,產生一個儲存seg3 的hole)
4. 傳送端收到ACK之後,就會將32~36的資料包從傳送並沒有確認切到傳送已經確認,提出視窗,這個時候視窗向右移動
5. 假設接收端通告的Window Size仍然不變,此時視窗右移,產生一些新的空位,這些是接收端允許傳送的範疇
6. 對於丟失的seg3,如果超過一定時間,TCP就會重新傳送(重傳機制),重傳成功會seg3 seg4一塊被確認,不成功,seg4也將被丟棄
就是不斷重複著上述的過程,隨著視窗不斷滑動,將真個資料流傳送到接收端,實際上接收端的Window Size通告也是會變化的,接收端根據這個值來確定何時及傳送多少資料,從對資料流進行流控。原理圖如下圖所示:
 
  • 滑動視窗動態調整
主要是根據接收端的接收情況,動態去調整Window Size,然後來控制傳送端的資料流量
1. 客戶端不斷快速傳送資料,伺服器接收相對較慢,看下實驗的結果
a. 包175,傳送ACK攜帶WIN = 384,告知客戶端,現在只能接收384個位元組
b. 包176,客戶端果真只發送了384個位元組,Wireshark也比較智慧,也宣告TCP Window Full
c. 包177,伺服器回覆一個ACK,並通告視窗為0,說明接收方已經收到所有資料,並儲存到緩衝區,但是這個時候應用程式並沒有接收這些資料,導致緩衝區沒有更多的空間,故通告視窗為0, 這也就是所謂的零視窗,零視窗期間,傳送方停止傳送資料
d. 客戶端察覺到視窗為0,則不再發送資料給接收方
e. 包178,接收方傳送一個視窗通告,告知傳送方已經有接收資料的能力了,可以傳送資料包了

f.  包179,收到視窗通告之後,就傳送緩衝區內的資料了.


總結一點,就是接收端可以根據自己的狀況通告視窗大小,從而控制傳送端的接收,進行流量控制

三. TCP超時和重傳

對每個連線, TCP管理4個不同的定時器。
1.重傳定時器適用於當希望收到另一端的確認。
2.堅持(persist)定時器使視窗大小資訊保持不斷流動,即使另一端關閉了其接收視窗。
3.保活(keepalive)定時器可檢測到一個空閒連線的另一端何時崩潰或重啟。
4.2MSL定時器測量一個連線處於TIME_WAIT狀態的時間。
超時重傳是TCP協議保證資料可靠性的一個重要機制,其原理是在傳送某一個數據以後就開啟一個計時器,在一定時間內如果沒有得到傳送的資料報的ACK報文,那麼就重新發送資料,直到傳送成功為止。
  • 超時
超時時間的計算是超時的核心部分,TCP要求這個演算法能大致估計出當前的網路狀況,雖然這確實很困難。要求精確的原因有兩個:(1)定時太久會造成網路利用率不高。(2)定時太短會造成多次重傳,使得網路阻塞。所以,書中給出了一套經驗公式,和其他的保證計時器準確的措施。
計時器的使用
1.    一個連線中,有且僅有一個測量定時器被使用。也就是說,如果TCP連續發出3組資料,只有一組資料會被測量。
2.    ACK資料報不會被測量,原因很簡單,沒有ACK的ACK迴應可以供結束定時器測量。
RTT(往返時間):指傳送端傳送TCP報文段開始到接收到對方的確定所使用的時間.
RTO(超時重傳時間):傳送端傳送TCP報文段後,在RTO時間內沒有收到對方確定,即重傳該報文段.
  • 擁塞避免演算法
擁塞避免演算法是一種處理丟失分組的方法。
該演算法假定由於分組受到損壞引起的丟失是非常少的,因此分組丟失意味著網路擁塞。
有兩種分組丟失的指示:超時 和 重複的ACK。
擁塞避免演算法和慢啟動演算法對每個連線維持兩個變數: 擁塞視窗( cwnd ) 和 慢啟動門限( ssthresh )
演算法工作過程:
(1)對一個給定的連線,初始化cwnd為1個報文段, ssthresh為65535個位元組.
(2)TCP輸出例程的輸出不能超過cwnd和接收方通告視窗的大小.擁塞避免是傳送方使用的流量控制,而通告視窗則是接收方進行的流量控制.前者是傳送方感受到的網路擁塞的估計,後者則與接收方在該連線上的可用快取大小有關.
(3)當擁塞發生時(超時或收到重複確認),ssthresh被設定為當前視窗大小的一半(cwnd和接收方通告視窗大小的最小值,但最少為2個報文段).
【此外,如果是超時引起了擁塞,則cwnd被設定為1個報文段(這就是慢啟動).】
(4)當新的資料被對方確認時,就增加cwnd,但增加的方法依賴於我們是否正在進行慢啟動或擁塞避免.如果cwnd <= ssthresh,則正在進行慢啟動,否則正在進行擁塞避免.
慢啟動一直持續到我們回到當擁塞發生時所處位置的半時候才停止,然後轉為執行擁塞避免。

cwnd增加方式:
慢啟動初始cwnd為1,每收到一個確定就加1.成指數增長.
擁塞避免演算法在每個RTT內增加 1/cwnd 個報文,成線性增長.
慢啟動根據收到的ACK次數增加cwnd,而擁塞避免演算法在一個RTT不管收有多少ACK也只增加一次.
網路中擁塞的發生會導致資料分組丟失,需要儘量避免。在實際中,擁塞演算法與慢啟動通常在一起實現,其基本過程:
   1. 對一個給定的連線,初始化cwnd為1個報文段,ssthresh為65535個位元組。
   2. TCP輸出例程的輸出不能超過cwnd和接收方通告視窗的大小。擁塞避免是傳送方使用 的流量控制,而通告視窗則是接收方進行的流量控制。前者是傳送方感受到的網路擁塞的估 計,而後者則與接收方在該連線上的可用快取大小有關。
   3. 當擁塞發生時(超時或收到重複確認),ssthresh被設定為當前視窗大小的一半(cwnd 和接收方通告視窗大小的最小值,但最少為2個報文段)。此外,如果是超時引起了擁塞,則 cwnd被設定為1個報文段(這就是慢啟動)。
   4. 當新的資料被對方確認時,就增加cwnd,但增加的方法依賴於是否正在進行慢啟動或擁塞避免。如果cwnd小於或等於ssthresh,則正 在進行慢啟動,否則正在進行擁塞避免。慢啟動一直持續到回到當擁塞發生時所處位置的半時候才停止(因為記錄了在步驟2 中製造麻煩的視窗大小的一半),然後轉為執行擁塞避免。
   慢啟動演算法初始設定cwnd為1個報文段,此後每收到一個確認就加 1。那樣,這會使視窗按指數方式增長:傳送 1個報文段,然後是2個,接著是4個……。

  • 快速重傳與快速恢復演算法
如果收到3個重複ACK,可認為該報文段已經丟失,此時無需等待超時定時器溢位,直接重傳丟失的包,這就叫【快速重傳演算法】.而接下來執行的不是慢啟動而是擁塞避免演算法,這就叫【快速恢復演算法】.
快重傳配合使用快恢復演算法,有以下兩個要點:
①當傳送方連續收到三個重複確認時,就執行“乘法減小”演算法,把ssthresh門限減半。但是接下去並不執行慢啟動演算法。
②考慮到如果網路出現擁塞的話就不會收到好幾個重複的確認,所以傳送方現在認為網路可能沒有出現擁塞。所以此時不執行慢啟動演算法,而是將cwnd設定為ssthresh的大小,然後執行擁塞避免演算法。
 這是資料丟包的情況下給出的一種修補機制。一般來說,重傳發生在超時之後,但是如果傳送端接受到3個以上的重複ACK的情況下(上面的圖中第二個包丟失了,就收到了兩個相同的ack=11),就應該意識到,資料丟了,需要重新傳遞。這個機制是不需要等到重傳定時器溢位的,所以叫做快速重傳,它可以避免傳送端因等待重傳計時器的超時而空閒較長時間,以此增加網路吞吐量。而重新傳遞以後,因為走的不是慢啟動而是擁塞避免演算法,所以這又叫做快速恢復演算法。演算法流程如下:
  1. 當收到第3個重複的ACK時,將ssthresh設定為當前擁塞視窗cwnd的一半。重傳丟失的報文段。設定cwnd為ssthresh加上3倍的報文段大小。
  2. 每次收到另一個重複的ACK時, cwnd增加1個報文段大小併發送1個分組(如果新的cwnd允許傳送)。
  3. 當下一個確認新資料的ACK到達時,設定cwnd為ssthresh(在第1步中設定的值)。這個 ACK應該是在進行重傳後的一個往返時間內對步驟1中重傳的確認。另外,這個ACK也應該是對丟失的分組和收到的第1個重複的ACK之間的所有中間報文段 的確認。

 
  • TCP超時與重傳機制
  TCP協議是一種面向連線的可靠的傳輸層協議,它保證了資料的可靠傳輸,對於一些出錯,超時丟包等問題TCP設計的超時與重傳機制。其基本原理:在傳送一個數據之後,就開啟一個定時器,若是在這個時間內沒有收到傳送資料的ACK確認報文,則對該報文進行重傳,在達到一定次數還沒有成功時放棄併發送一個復位訊號。
  這裡比較重要的是重傳超時時間,怎樣設定這個定時器的時間(RTO),從而保證對網路資源最小的浪費。因為若RTO太小,可能有些報文只是遇到擁堵或網路不好延遲較大而已,這樣就會造成不必要的重傳。太大的話,使傳送端需要等待過長的時間才能發現數據丟失,影響網路傳輸效率。
  由於不同的網路情況不一樣,不可能設定一樣的RTO,實際中RTO是根據網路中的RTT(傳輸往返時間)來自適應調整的。具體關係參考相關演算法。
  通過圖來了解重傳機制:
   
從圖可以知道,傳送方連續傳送3個數據包,其中第二個丟失,沒有被接收到,因此不會返回對應的ACK,沒傳送一個數據包,就啟動一個定時器,當第二個包的定時器溢位了還沒有收到ack,這時就進行重傳。
  • TCP確認
TCP資料包中的序列號(Sequence Number)不是以報文段來進行編號的,而是將連線生存週期內傳輸的所有資料當作一個位元組流,序列號就是整個位元組流中每個位元組的編號。一個TCP資料包中包含多個位元組流的資料(即資料段),而且每個TCP資料包中的資料大小不一定相同。在建立TCP連線的三次握手過程中,通訊雙方各自已確定了初始的序號x和y,TCP每次傳送的報文段中的序號欄位值表示所要傳送本報文中的第一個位元組的序號。
        TCP的報文到達確認(ACK),是對接收到的資料的最高序列號的確認,並向傳送端返回一個下次接收時期望的TCP資料包的序列號(Ack Number)。例如,主機A傳送的當前資料序號是400,資料長度是100,則接收端收到後會返回一個確認號是501的確認號給主機A。
        TCP提供的確認機制,可以在通訊過程中可以不對每一個TCP資料包發出單獨的確認包(Delayed ACK機制),而是在傳送資料時,順便把確認資訊傳出,這樣可以大大提高網路的利用率和傳輸效率。同時,TCP的確認機制,也可以一次確認多個數據報,例如,接收方收到了201,301,401的資料報,則只需要對401的資料包進行確認即可,對401的資料包的確認也意味著401之前的所有資料包都已經確認,這樣也可以提高系統的效率。
        若傳送方在規定時間內沒有收到接收方的確認資訊,就要將未被確認的資料包重新發送。接收方如果收到一個有差錯的報文,則丟棄此報文,並不向傳送方傳送確認資訊。因此,TCP報文的重傳機制是由設定的超時定時器來決定的,在定時的時間內沒有收到確認資訊,則進行重傳。這個定時的時間值的設定非常重要,太大會使包重傳的延時比較大,太小則可能沒有來得及收到對方的確認包傳送方就再次重傳,會使網路陷入無休止的重傳過程中。接收方如果收到了重複的報文,將會丟棄重複的報文,但是必須發回確認資訊,否則對方會再次傳送。
        TCP協議應當保證資料報按序到達接收方。如果接收方收到的資料報文沒有錯誤,只是未按序號,這種現象如何處理呢?TCP協議本身沒有規定,而是由TCP協議的實現者自己去確定。通常有兩種方法進行處理:一是對沒有按序號到達的報文直接丟棄,二是將未按序號到達的資料包先放於緩衝區內,等待它前面的序號包到達後,再將它交給應用程序。後一種方法將會提高系統的效率。例如,傳送方連續傳送了每個報文中100個位元組的TCP資料報,其序號分別是1,101,201,…,701。假如其它7個數據報都收到了,而201這個資料報沒有收到,則接收端應當對1和101這兩個資料報進行確認,並將資料遞交給相關的應用程序,301至701這5個數據報則應當放於緩衝區,等到201這個資料報到達後,然後按序將201至701這些資料報遞交給相關應用程序,並對701資料報進行確認,確保了應用程序級的TCP資料的按序到達。
在TCP確認機制中,無法有效處理非連續TCP片段。確認號表明所有低於該編號的sequence number已經被髮送該編號的裝置接收。如果我們收到的位元組數落在兩個非連續的範圍內,則無法只通過一個編號來確認。這可能導致潛在嚴重的效能問題,特別是高速或可靠性較差的網路。

還是以下圖為例,伺服器傳送了4個片段並收到1條回覆,確認號為201。因此,片段1和片段2被當成已確認。它們從重傳佇列中移出,同時允許伺服器傳送視窗向右移動200位元組,從而傳送資料增加200個位元組。

然而,再次假設片段3,從sequence number201開始,在傳送過程中丟失了。由於客戶端從沒有收到這一片段,所以它也無法傳送確認號高於201的確認資訊,從而導致滑動視窗停滯。伺服器可以繼續傳送其他片段直到填滿客戶端的接收視窗,但是直到客戶端傳送另一條確認資訊,伺服器的傳送視窗都不會滑動。

另一個問題是如果片段3丟失了,客戶端將無法告知伺服器是否收到後續的片段。在客戶端接收視窗填滿之前,很有可能客戶端已經接收到片段4以及之後的片段。但是客戶端無法傳送值為501的確認資訊以表明接收到片段4,因為這意味著片段3也接收到了。


 
這裡我們看到了TCP單編號,累積確認機制的缺點。我們可以想象一個最差的情況,伺服器被告知它有一個10,000位元組視窗,20個片段每個片段500位元組。第一個片段丟失了,其他19個被接收到了。但是由於第一個片段從沒有接收到,其他19個也無法確認。
 
 
未確認片段處理策略:
 
我們怎樣處理丟失片段之後的片段呢?本例中,當伺服器片段3重傳超時,它必須決定怎樣處理片段4,它不知道客戶端是否已經接收到。在上述最差情況下,第一個片段丟失後,其餘19個可能或可能無法被客戶端接收到。
處理這種情況有兩種可能的方式:

僅重傳超時片段:這是一種更加保守的方式,僅重傳超時的片段,希望其他片段都能夠成功接收。如果該片段之後的其他片段實際上接收到了,這一方式是最佳的,如果沒接收到,就無法正常執行。後者的情況每一個片段需要單獨計時並重傳。假設上述最壞情況下,所有20個500位元組片段都丟失了。我們需要等片段1超時並重傳。這一片段也許會得到確認,但之後我們需要等待片段2超時並重傳。這一過程會重複多次。

重傳所有片段:這是一種更激進或者說更悲觀的方式。無論何時一個片段超時了,不僅重傳該片段,還有所有其他尚未確認的片段。這一方式確保了任何時間都有一個等待確認的停頓時間,在所有未確認片段丟失的情況下,會重新整理全部未確認片段,以使對端裝置多一次接收機會。在所有20個片段都丟失的情況下,相對於第一種方式節省了大量時間。這種方式的問題在於可能這些重傳是不必要的。如果第一個片段丟失而其他19個實際上接收到了,也得重傳那9500位元組資料。

由於TCP不知道其他片段是否接收到,所以它也無法確認哪種方法更好,但只能選擇一種方式。上圖示例了保守的方式,而下圖顯示的是激進的方式:
 
問題的關鍵在於無法確認非連續片段。解決方式是對TCP滑動視窗演算法進行擴充套件,新增允許裝置分別確認非連續片段的功能。這一功能稱為選擇確認(selective acknowledgment, SACK)。
 
選擇確認:
 
通過SACK,連線的兩方裝置必須同時支援這一功能,通過連線時使用的SYN片段來協商是否允許SACK。這一過程完成之後,任一裝置都可以在常規TCP片段中使用SACK選項。這一選項包含一個關於已接收但未確認片段資料sequence number範圍的列表,由於它們是非連續的。
各裝置對重傳佇列進行修改,如果該片段已被選擇確認過,則該片段中的SACK位元位置為1。該裝置使用圖2中激進方式的改進版本,一個片段重傳之後,之後所有片段也會重傳,除非SACK位元位為1。

例如,在4個片段的情況下,如果客戶端接收到片段4而沒有接收到片段3,當它發回確認號為201(片段1和片段2)的確認資訊,其中包含一個SACK選項指明:“已接收到位元組361至500,但尚未確認”。如果片段4在片段1和2之後到達,上述資訊也可以通過第二個確認片段來完成。伺服器確認片段4的位元組範圍,併為片段4開啟SACK位。當片段3重傳時,伺服器看到片段4的SACK位為1,就不會對其重傳。如下圖所示。

在片段3重傳之後,片段4的SACK位被清除。這是為了防止客戶端出於某種原因改變片段4已接收的想法。客戶端應當傳送確認號為501或更高的確認資訊,正式確認片段3和4接收到。如果這一情況沒有發生,伺服器必須接收到片段4的另一條選擇確認資訊才能將它的SACK位開啟,否則,在片段3重傳時或計時器超時的情況下會對其自動重傳。