TCP 滑動窗口和 擁塞窗口

轉http://coolshell.cn/articles/11609.htmlhtml

 

滑動窗口 -- 表徵發送端和接收端的接收能力算法

擁塞窗口-- 表徵中間設備的傳輸能力shell

 

TCP滑動窗口

須要說明一下,若是你不瞭解TCP的滑動窗口這個事,你等於不瞭解TCP協議。咱們都知道,TCP必須要解決的可靠傳輸以及包亂序(reordering)的問題,因此,TCP必須要知道網絡實際的數據處理帶寬或是數據處理速度,這樣纔不會引發網絡擁塞,致使丟包。網絡

因此,TCP引入了一些技術和設計來作網絡流控,Sliding Window是其中一個技術。 前面咱們說過,TCP頭裏有一個字段叫Window,又叫Advertised-Window,這個字段是接收端告訴發送端本身還有多少緩衝區能夠接收數據因而發送端就能夠根據這個接收端的處理能力來發送數據,而不會致使接收端處理不過來。 爲了說明滑動窗口,咱們須要先看一下TCP緩衝區的一些數據結構:數據結構

上圖中,咱們能夠看到:併發

  • 接收端LastByteRead指向了TCP緩衝區中讀到的位置,NextByteExpected指向的地方是收到的連續包的最後一個位置,LastByteRcved指向的是收到的包的最後一個位置,咱們能夠看到中間有些數據尚未到達,因此有數據空白區。
  • 發送端的LastByteAcked指向了被接收端Ack過的位置(表示成功發送確認),LastByteSent表示發出去了,但尚未收到成功確認的Ack,LastByteWritten指向的是上層應用正在寫的地方。

因而:ssh

  • 接收端在給發送端回ACK中會彙報本身的AdvertisedWindow = MaxRcvBuffer – LastByteRcvd – 1;
  • 而發送方會根據這個窗口來控制發送數據的大小,以保證接收方能夠處理。

下面咱們來看一下發送方的滑動窗口示意圖:tcp

圖片來源ide

上圖中分紅了四個部分,分別是:(其中那個黑模型就是滑動窗口)優化

  • #1已收到ack確認的數據。
  • #2發還沒收到ack的。
  • #3在窗口中尚未發出的(接收方還有空間)。
  • #4窗口之外的數據(接收方沒空間)

下面是個滑動後的示意圖(收到36的ack,併發出了46-51的字節):

下面咱們來看一個接受端控制發送端的圖示:

 

 

 

TCP的擁塞處理 – Congestion Handling

上面咱們知道了,TCP經過Sliding Window來作流控(Flow Control),可是TCP以爲這還不夠,由於Sliding Window須要依賴於鏈接的發送端和接收端,其並不知道網絡中間發生了什麼。TCP的設計者以爲,一個偉大而牛逼的協議僅僅作到流控並不夠,由於流控只是網絡模型4層以上的事,TCP的還應該更聰明地知道整個網絡上的事。

具體一點,咱們知道TCP經過一個timer採樣了RTT並計算RTO,可是,若是網絡上的延時忽然增長,那麼,TCP對這個事作出的應對只有重傳數據,可是,重傳會致使網絡的負擔更重,因而會致使更大的延遲以及更多的丟包,因而,這個狀況就會進入惡性循環被不斷地放大。試想一下,若是一個網絡內有成千上萬的TCP鏈接都這麼行事,那麼立刻就會造成「網絡風暴」,TCP這個協議就會拖垮整個網絡。這是一個災難。

因此,TCP不能忽略網絡上發生的事情,而無腦地一個勁地重發數據,對網絡形成更大的傷害。對此TCP的設計理念是:TCP不是一個自私的協議,當擁塞發生的時候,要作自我犧牲。就像交通阻塞同樣,每一個車都應該把路讓出來,而不要再去搶路了。

關於擁塞控制的論文請參看《Congestion Avoidance and Control》(PDF)

擁塞控制主要是四個算法:1)慢啓動2)擁塞避免3)擁塞發生4)快速恢復。這四個算法不是一天都搞出來的,這個四算法的發展經歷了不少時間,到今天都還在優化中。 備註:

  • 1988年,TCP-Tahoe 提出了1)慢啓動,2)擁塞避免,3)擁塞發生時的快速重傳
  • 1990年,TCP Reno 在Tahoe的基礎上增長了4)快速恢復
慢熱啓動算法 – Slow Start

首先,咱們來看一下TCP的慢熱啓動。慢啓動的意思是,剛剛加入網絡的鏈接,一點一點地提速,不要一上來就像那些特權車同樣霸道地把路佔滿。新同窗上高速仍是要慢一點,不要把已經在高速上的秩序給搞亂了。

慢啓動的算法以下(cwnd全稱Congestion Window):

1)鏈接建好的開始先初始化cwnd = 1,代表能夠傳一個MSS大小的數據。

2)每當收到一個ACK,cwnd++; 呈線性上升

3)每當過了一個RTT,cwnd = cwnd*2; 呈指數讓升

4)還有一個ssthresh(slow start threshold),是一個上限,當cwnd >= ssthresh時,就會進入「擁塞避免算法」(後面會說這個算法)

因此,咱們能夠看到,若是網速很快的話,ACK也會返回得快,RTT也會短,那麼,這個慢啓動就一點也不慢。下圖說明了這個過程。

這裏,我須要提一下的是一篇Google的論文《An Argument for Increasing TCP’s Initial Congestion Window》Linux 3.0後採用了這篇論文的建議——把cwnd 初始化成了 10個MSS。 而Linux 3.0之前,好比2.6,Linux採用了RFC3390,cwnd是跟MSS的值來變的,若是MSS< 1095,則cwnd = 4;若是MSS>2190,則cwnd=2;其它狀況下,則是3。

 擁塞避免算法 – Congestion Avoidance

前面說過,還有一個ssthresh(slow start threshold),是一個上限,當cwnd >= ssthresh時,就會進入「擁塞避免算法」。通常來講ssthresh的值是65535,單位是字節,當cwnd達到這個值時後,算法以下:

1)收到一個ACK時,cwnd = cwnd + 1/cwnd

2)當每過一個RTT時,cwnd = cwnd + 1

這樣就能夠避免增加過快致使網絡擁塞,慢慢的增長調整到網絡的最佳值。很明顯,是一個線性上升的算法。

擁塞狀態時的算法

前面咱們說過,當丟包的時候,會有兩種狀況:

1)等到RTO超時,重傳數據包。TCP認爲這種狀況太糟糕,反應也很強烈。

    • sshthresh =  cwnd /2
    • cwnd 重置爲 1
    • 進入慢啓動過程

2)Fast Retransmit算法,也就是在收到3個duplicate ACK時就開啓重傳,而不用等到RTO超時。

    • TCP Tahoe的實現和RTO超時同樣。
    • TCP Reno的實現是:
      • cwnd = cwnd /2
      • sshthresh = cwnd
      • 進入快速恢復算法——Fast Recovery

上面咱們能夠看到RTO超時後,sshthresh會變成cwnd的一半,這意味着,若是cwnd<=sshthresh時出現的丟包,那麼TCP的sshthresh就會減了一半,而後等cwnd又很快地以指數級增漲爬到這個地方時,就會成慢慢的線性增漲。咱們能夠看到,TCP是怎麼經過這種強烈地震盪快速而當心得找到網站流量的平衡點的。

快速恢復算法 – Fast Recovery

TCP Reno

這個算法定義在RFC5681。快速重傳和快速恢復算法通常同時使用。快速恢復算法是認爲,你還有3個Duplicated Acks說明網絡也不那麼糟糕,因此沒有必要像RTO超時那麼強烈。 注意,正如前面所說,進入Fast Recovery以前,cwnd 和 sshthresh已被更新:

  • cwnd = cwnd /2
  • sshthresh = cwnd

而後,真正的Fast Recovery算法以下:

  • cwnd = sshthresh  + 3 * MSS (3的意思是確認有3個數據包被收到了)
  • 重傳Duplicated ACKs指定的數據包
  • 若是再收到 duplicated Acks,那麼cwnd = cwnd +1
  • 若是收到了新的Ack,那麼,cwnd = sshthresh ,而後就進入了擁塞避免的算法了。

若是你仔細思考一下上面的這個算法,你就會知道,上面這個算法也有問題,那就是——它依賴於3個重複的Acks。注意,3個重複的Acks並不表明只丟了一個數據包,頗有多是丟了好多包。但這個算法只會重傳一個,而剩下的那些包只能等到RTO超時,因而,進入了惡夢模式——超時一個窗口就減半一下,多個超時會超成TCP的傳輸速度呈級數降低,並且也不會觸發Fast Recovery算法了。

一般來講,正如咱們前面所說的,SACK或D-SACK的方法可讓Fast Recovery或Sender在作決定時更聰明一些,可是並非全部的TCP的實現都支持SACK(SACK須要兩端都支持),因此,須要一個沒有SACK的解決方案。而經過SACK進行擁塞控制的算法是FACK(後面會講)

TCP New Reno

因而,1995年,TCP New Reno(參見 RFC 6582 )算法提出來,主要就是在沒有SACK的支持下改進Fast Recovery算法的——

  • 當sender這邊收到了3個Duplicated Acks,進入Fast Retransimit模式,開發重傳重複Acks指示的那個包。若是隻有這一個包丟了,那麼,重傳這個包後回來的Ack會把整個已經被sender傳輸出去的數據ack回來。若是沒有的話,說明有多個包丟了。咱們叫這個ACK爲Partial ACK。
  • 一旦Sender這邊發現了Partial ACK出現,那麼,sender就能夠推理出來有多個包被丟了,因而乎繼續重傳sliding window裏未被ack的第一個包。直到再也收不到了Partial Ack,才真正結束Fast Recovery這個過程

咱們能夠看到,這個「Fast Recovery的變動」是一個很是激進的玩法,他同時延長了Fast Retransmit和Fast Recovery的過程。

算法示意圖

下面咱們來看一個簡單的圖示以同時看一下上面的各類算法的樣子:

相關文章
相關標籤/搜索