網絡協議 9 - TCP(下)

  1. 網絡協議 1 - 概述
  2. 網絡協議 2 - IP 是怎麼來,又是怎麼沒的?
  3. 網絡協議 3 - 從物理層到 MAC 層
  4. 網絡協議 4 - 交換機與 VLAN:辦公室太複雜,我要回學校
  5. 網絡協議 5 - ICMP 與 ping:投石問路的偵察兵
  6. 網絡協議 6 - 路由協議:敢問路在何方?
  7. 網絡協議 7 - UDP 協議:性善碰到城會玩
  8. 網絡協議 8 - TCP 協議(上):性惡就要套路深

    上次瞭解了 TCP 創建鏈接與斷開鏈接的過程,咱們發現,TCP 會經過各類「套路」來保證傳輸數據的安全。除此以外,咱們還大概瞭解了 TCP 包頭格式所對應解決的五個問題:順序問題、丟包問題、鏈接維護、流量控制、擁塞控制。今天,咱們就來看下 TCP 又是用怎樣的套路去解決這五個問題的。html

    在解決問題以前,我們先來看看 TCP 是怎麼成爲一個「靠譜」的協議的。算法

「靠譜」協議 TCP

    TCP 爲了保證順序性,每一個包都有一個 ID。這創建鏈接的時候,會商定起始 ID 的值,而後按照 ID一個個發送。緩存

    爲了保證不丟包,對於發送的包都要進行應答。可是這個應答不是一個一個來的,而是會應答某個以前的 ID,表示都收到了,這種模式稱爲累計確認累計應答安全

爲了記錄全部發送的包和接收的包,TCP 也須要發送端和接收端分別用緩存來保存這些記錄。發送端的緩存裏是按照包的 ID 一個個排列,根據處理的狀況分紅四個部分:網絡

  • 第一部分:發送且已經確認的;
  • 第二部分:發送還沒有確認的;
  • 第三部分:沒有發送,可是已經等待發送的;
  • 第四部分:沒有發送,而且暫時還不會發送的。

    因而,發送端須要保持這樣的數據結構:數據結構

  • LastByteAcked:第一部分和第二部分的分界線
  • LastByteSent:第二部分和第三部分的分界線
  • LastByteAcked:第三部分和第四部分的分界線

對於接收端來說,它緩存記錄的內容要簡單一些,分爲如下三個部分:ssh

  • 第一部分:接收且確認過的;
  • 第二部分:還沒接收,可是立刻就能接收的;
  • 第三部分:還沒接收,也沒空間接收的。

    對應的數據結構就像這樣:ide

  • MaxRcvBuffer:最大緩存量;
  • LastByteRead:這個值以後是已經接收,可是還沒被應用層讀取的;
  • NextByteExpected:第一部分和第二部分的分界線,下一個期待的包 ID。

    第二部分的窗口有多大呢?優化

    NextByteExpected 和 LastByteRead 的差起始是還沒被應用層讀取的部分佔用掉的 MaxRcvBuffer 的量,咱們定義爲 A,即:A = NextByteExpected - LastByteRead - 1。ui

    那麼,窗口大小,AdvertisedWindow = MaxRcvBuffer - A。

    也就是:AdvertisedWindow = MaxRcvBuffer - (NextByteExpected - LastByteRead - 1)

    而第二部分和三部分的分界線 = NextByteExpected + AdvertisedWindow - 1 = MaxRcvBuffer + LastByteRead。

順序與丟包問題

    接下來,咱們結合上述圖例,用一個例子來看下 TCP 如何處理順序與丟包問題的。

仍是剛纔的圖,在發送端看來:

  • 一、二、3 是已經發送並確認的;
  • 四、五、六、七、八、9 都是發送未確認的;
  • 十、十一、12 是還沒發出的;
  • 1三、1四、15 是接收方沒有空間,不許備發送的。

而在接收端看來:

  • 一、二、三、四、5 是已經完成 ACK,但還沒讀取的;
  • 六、7 是等待接收的;
  • 八、9 是已經接收,可是沒有 ACK 的。

發送端和接收端當前的狀態以下:

  • 一、二、3 沒有問題,雙方達成了一致;
  • 四、5 接收方發送 ACK 了,可是發送方還沒收到,有可能丟了,有可能還在路上;
  • 六、七、八、9 確定都發了,可是 八、9 已經到了,六、7還沒打,出現了亂序,因而在緩存中存儲,可是沒有返回 ACK。

    根據這個例子,咱們能夠知道,順序問題和丟包問題都有了能發送,因此咱們先來看確認與重發的機制
    假設 4 的確認到了,不幸的是,5 的 ACK 丟了,而且 六、7 的數據包也丟了,這時候會怎麼處理呢?

    一種方法是超時重試,也就是對每個發送了,可是沒有 ACK 的包,都有設一個定時器,一旦超過了必定的時間,就從新嘗試。這個超時時間不宜太短,時間必須大於往返時間 RTT,不然就會引發沒必要要的重傳也不宜過長,這樣超時時間變長,訪問就變慢了。

    估計往返時間,須要 TCP 經過採樣 RTT 的時間,而後進行加權平均,算出一個值,並且這個值仍是要不斷變化的,由於網絡情況不斷的變化。

    除了採樣 RTT,還要採樣 RTT 的波動範圍,計算出一個估計的超時時間。因爲重傳時間是不斷變化的,咱們稱爲自適應重傳算法(Adaptive Retransmission Algorithm)

    若是過一段時間,五、六、7 都超時了,就會從新發送。接收方發現 5 原來接收過,因而就丟棄5。收到了6,發送 ACK,要求下一個是 7,7 不幸又丟了。

    當 7 再次超時的時候,若是有須要重傳,TCP 的策略就是超時間隔加倍。每當遇到一次超時重傳的實時,都會將下一次超時時間間隔設置爲先前值的兩倍。兩次超時,就說明網絡環境差,不宜頻繁發送。

    能夠看出,超時重發存在的問題是,超時週期可能較長。那是否是能夠有更快的方式呢?

    有一個能夠快速重傳的機制。當接收方收到一個序號大於下一個所指望的報文段時,就檢測到了數據流中的一個間格,因而發送三個冗餘的ACK,客戶端收到後,就在定時器過時以前,重傳丟失的報文段。

    例如,接收方發現 六、八、9 都已經接收了,可是 7 沒來。因而發送三個 6 的 ACK,要求下一個是 7。客戶端收到三個,就會發現 7 的確丟了,不等超時,就立刻重發。

    除此以外,還有一種方式稱爲 Selective Acknowledgment(SACK)。這種方式須要在 TCP 頭裏加一個 SACK 的東西,能夠將緩存的地圖發格發送方。例如發送 ACK六、SACK八、SACK9,有了地圖,發送方一會兒就能看出來是 7 丟了,而後快速重發。

流量控制問題

    接下來,咱們再來看看流量控制機制。在對於包的確認中,會同時攜帶一個窗口大小的字段。

    咱們先假設窗口不變的狀況,發送端窗口始終爲 9。4 的確認來的時候,LastByteAcked 會右移一個,這個時候,第 13 個包就能夠發送了。

    這個時候,假設發送端發送過猛,將第三部分中的 十、十一、十二、13 所有發送,以後就中止發送,則此時未發送可發送部分爲 0。

    當對於包 5 的確認到達的時候,在客戶端至關於窗口再滑動了一格,這個時候,才能夠有更多的包能夠發送了,例如第 14 個包才能夠發送。

    若是接收方處理的太慢,致使緩存中沒有空間了,能夠經過確認信息修改窗口的大小,甚至能夠設置爲 0,則發送方將暫時中止發送。

    咱們能夠假設一個極端狀況,接收端的應用一直不讀取緩存中的數據,當數據包 6 確認後,窗口大小就不會再是 9,而是減小一個變爲了 8。

    爲何會變爲 8?你看,下圖中,當 6 的確認消息到達發送端的時候,左邊的 LastByteAcked 右移一位,而右邊的未發送可發送區域由於已經變爲 0,所以左邊的 LastByteSend 沒有移動,所以,窗口大小就從 9 變成了 8。

    而若是接收端一直不處理數據,則隨着確認的包愈來愈多,窗口愈來愈小,直到爲 0。

    當這個窗口大小經過包 14 的確認到達發送端的時候,發送端的窗口也調整爲 0,因而,發送端中止發送。

    當發生這樣的狀況時,發送方會定時發送窗口探測數據包,看是否有機會調整窗口的大小。對於接收方來講,當接收比較慢的時候,要防止低能窗口綜合徵,別空出一個字節就趕忙告訴發送方,結果又被填滿了。能夠在窗口過小的時候,不更新窗口大小,直到達到必定大小,或者緩衝區一半爲空,才更新窗口大小。

    這就是咱們常說的流量控制

擁塞控制問題

    最後,咱們來看一下擁塞控制的問題。

    這個問題,也是靠窗口來解決的。前面的滑動窗口 rwnd 是怕發送方把接收方緩存塞滿,而擁塞窗口 cwnd,是怕把網絡塞滿。

這裏有一個公式:

LastByteSent - LastByteAcked <= min{cwnd, rwnd}

    能夠看出,是擁塞窗口和滑動窗口共同控制發送的速度。

    那發送方怎麼判斷網絡是否是滿呢?這實際上是個挺難的事情。由於對於 TCP 協議來說,它壓根不知道整個網絡路徑都會經歷什麼。TCP 發送包常被比喻爲往一個水管裏灌水,而 TCP 的擁塞控制就是在不堵塞、不丟包的狀況下,儘可能發揮帶寬。

    水管有粗細,網絡有帶寬,也就是每秒鐘可以發送多少數據;

水管有長度,端到端有時延。在理想狀況下:

水管裏的水量 = 水管粗細 x 水管長度

而對於網絡來說:

通道的容量 = 帶寬 x 往返延遲

    若是咱們設置發送窗口,使得發送但未確認的包的數量爲通道的容量,就可以撐滿整個管道。

如上圖所示:

假設往返時間爲 8s,去 4s,回 4s,每秒發送一個包,每一個包 1024 byte。

    那麼在 8s 後,就發出去了 8 個包。其中前 4 個包已經到達接收端,可是 ACK 尚未返回,不能算髮送成功。而 5-8 後四個包還在路上,沒被接收。

這個時候,整個管道正好撐滿。在發送端,已發送未確認的爲 8 個包,也就是:

帶寬 = 1024byte/s x 8s(來回時間)

    若是咱們在這個基礎上再調大窗口,使得單位時間內更多的包能夠發送,會出現什麼現象呢?

    原來發送一個包,從一端到另外一端,假設一共通過四個設備,每一個設備處理一個包耗時 1s,因此到達另外一端須要耗費 4s。若是發送的更加快速,則單位時間內,會有更多的包到達這些中間設備,這些設備仍是隻能每秒處理一個包的話,多出來的包就會被丟棄,這不是咱們但願看到的。

    這個時候,咱們能夠想其餘的辦法。例如,這四個設備原本每秒處理一個包,可是咱們在這些設備上加緩存,處理不過來的就在隊列裏面排着,這樣包就不會丟失,可是缺點也是顯而易見的,增長了時延。這個緩存的包,4s 確定到達不了接收端,若是時延達到必定程度,就會超時,這也不是咱們但願看到的。

    針對上述兩種現象:包丟失超時重傳。一旦出現了這些現象就說明,發送速度太快了,要慢一點。可是一開始,發送端怎麼知道速度多快呢?怎麼知道把窗口調整到合適大小呢?

    若是咱們經過漏斗往瓶子裏灌水,咱們就知道,不能一桶水一會兒全倒進去,確定會溢出來。一開始要慢慢的倒,而後發現都可以倒進去,就加快速度。這叫作慢啓動

一個 TCP 鏈接開始

  • cwnd 設置爲一個報文段,一次只能發送 1 個;
  • 當收到這一個確認的時候,cwnd 加 1,因而一次可以發送 2 個;
  • 當這兩個包的確認到來的時候,每一個確認的 cwnd 加 1,兩個確認 cwnd 加 2,因而一次可以發送 4 個;
  • 當這四個的確認到來的時候,每一個確認 cwnd 加 1,四個確認 cwnd 加 4,因而一次可以發送 8 個。

    從上面這個過程能夠看出,這是指數性的增加

    可是漲到何時是個頭呢?一個值 ssthresh 爲 65535 個字節,當超過這個值的時候,就會將將增加速度降下來。

    此時,每收到一個確認後,cwnd 增長 1/cwnd。一次發送 8 個,當 8 個確認到來的時候,每一個確認增長 1/8,8個確認一共增長 1,因而一次就可以發送 9 個,變成了線性增加

    即便增加變成了線性增加,仍是會出現「溢出」的狀況,出現擁塞。這時候通常就會直接下降倒水的速度,等待溢出的水慢慢滲透下去。

    擁塞的一種變現形式是丟包,須要超時重傳。這個時候,將 ssthresh 設爲 cwnd/2,將 cwnd 設爲 1,從新開始慢啓動。也就是,一旦超時重傳,立刻「從零開始」。

    很明顯,這種方式太激進了,將一個高速的傳輸速度一會兒停了下來,會形成網絡卡頓。

    前面有提過快速重傳算法。當接收端發現丟了一箇中間包的時候,發送三次前一個包的 ACK,告訴發送端要趕忙給我發下一個包,別等超時再重傳。TCP 認爲這種狀況不嚴重,由於大部分沒丟,只丟了一小部分,cwnd 變爲 cwnd/2,而後 sshthresh = cwnd。當三個包返回的時候,cwnd = sshthresh + 3。

    能夠看出這種狀況降低速沒有那麼激進,cwnd 仍是在一個比較高的值,呈線性增加。下圖是二者的對比。

    就像前面說的同樣,正是這種知進退,使得時延在很重要的狀況下,反而下降了速度。可是,咱們仔細想想,TCP 的擁塞控制主要用來避免的兩個現象都是有問題的。

    第一個問題是丟包。丟包並不必定表示通道滿了,也多是管子原本就」漏水」。就像公網上帶寬不滿也會丟包,這個時候就認爲擁塞,而下降發送速度實際上是不對的。

    第二個問題是 TCP 的擁塞控制要等到將中間設備都填滿了,才發送丟包,從而下降速度。但其實,這時候下降速度已經晚了,在將管道填滿後,不該該接着填,直到發生丟包才降速。

    爲了優化這兩個問題,後來就有了 TCP BBR 擁塞算法。它企圖找到一個平衡點,就是經過不斷的加快發送速度,將管道填滿,可是不要填滿中間設備的緩存,由於這樣時延會增長,在這個平衡點能夠很好的達到高帶寬和低時延的平衡

    下圖是 BBR 算法與普通 TCP 的對比:

小結

  • 順序問題、丟包問題、流量控制都是經過滑動窗口來解決的。這就至關於領導和員工的備忘錄,佈置過的工做要有編號,幹完了有反饋,活不能派太多,也不能太少;
  • 擁塞控制是經過擁塞窗口來解決的,至關於往管道里面倒水,快了容易溢出,慢了浪費帶寬,要摸着石頭過河,找到最優值。

參考:

  1. The TCP/IP Guide;
  2. 百度百科 - TCP詞條;
  3. 劉超 - 趣談網絡協議系列課;
相關文章
相關標籤/搜索