轉http://coolshell.cn/articles/11609.htmlhtml
滑動窗口 -- 表徵發送端和接收端的接收能力算法
擁塞窗口-- 表徵中間設備的傳輸能力shell
須要說明一下,若是你不瞭解TCP的滑動窗口這個事,你等於不瞭解TCP協議。咱們都知道,TCP必須要解決的可靠傳輸以及包亂序(reordering)的問題,因此,TCP必須要知道網絡實際的數據處理帶寬或是數據處理速度,這樣纔不會引發網絡擁塞,致使丟包。網絡
因此,TCP引入了一些技術和設計來作網絡流控,Sliding Window是其中一個技術。 前面咱們說過,TCP頭裏有一個字段叫Window,又叫Advertised-Window,這個字段是接收端告訴發送端本身還有多少緩衝區能夠接收數據。因而發送端就能夠根據這個接收端的處理能力來發送數據,而不會致使接收端處理不過來。 爲了說明滑動窗口,咱們須要先看一下TCP緩衝區的一些數據結構:數據結構
上圖中,咱們能夠看到:併發
因而:ssh
下面咱們來看一下發送方的滑動窗口示意圖:tcp
(圖片來源)ide
上圖中分紅了四個部分,分別是:(其中那個黑模型就是滑動窗口)優化
下面是個滑動後的示意圖(收到36的ack,併發出了46-51的字節):
下面咱們來看一個接受端控制發送端的圖示:
上面咱們知道了,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)快速恢復。這四個算法不是一天都搞出來的,這個四算法的發展經歷了不少時間,到今天都還在優化中。 備註:
首先,咱們來看一下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。
前面說過,還有一個ssthresh(slow start threshold),是一個上限,當cwnd >= ssthresh時,就會進入「擁塞避免算法」。通常來講ssthresh的值是65535,單位是字節,當cwnd達到這個值時後,算法以下:
1)收到一個ACK時,cwnd = cwnd + 1/cwnd
2)當每過一個RTT時,cwnd = cwnd + 1
這樣就能夠避免增加過快致使網絡擁塞,慢慢的增長調整到網絡的最佳值。很明顯,是一個線性上升的算法。
前面咱們說過,當丟包的時候,會有兩種狀況:
1)等到RTO超時,重傳數據包。TCP認爲這種狀況太糟糕,反應也很強烈。
2)Fast Retransmit算法,也就是在收到3個duplicate ACK時就開啓重傳,而不用等到RTO超時。
上面咱們能夠看到RTO超時後,sshthresh會變成cwnd的一半,這意味着,若是cwnd<=sshthresh時出現的丟包,那麼TCP的sshthresh就會減了一半,而後等cwnd又很快地以指數級增漲爬到這個地方時,就會成慢慢的線性增漲。咱們能夠看到,TCP是怎麼經過這種強烈地震盪快速而當心得找到網站流量的平衡點的。
TCP Reno
這個算法定義在RFC5681。快速重傳和快速恢復算法通常同時使用。快速恢復算法是認爲,你還有3個Duplicated Acks說明網絡也不那麼糟糕,因此沒有必要像RTO超時那麼強烈。 注意,正如前面所說,進入Fast Recovery以前,cwnd 和 sshthresh已被更新:
而後,真正的Fast Recovery算法以下:
若是你仔細思考一下上面的這個算法,你就會知道,上面這個算法也有問題,那就是——它依賴於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算法的——
咱們能夠看到,這個「Fast Recovery的變動」是一個很是激進的玩法,他同時延長了Fast Retransmit和Fast Recovery的過程。
下面咱們來看一個簡單的圖示以同時看一下上面的各類算法的樣子: