詳解 TCP 超時與重傳機制——長文預警

上一篇介紹 TCP 的文章「TCP 三次握手,四次揮手和一些細節」反饋還不錯,仍是蠻開心的,此次接着講一講關於超時和重傳那一部分。php


咱們都知道 TCP 協議具備重傳機制,也就是說,若是發送方認爲發生了丟包現象,就重發這些數據包。很顯然,咱們須要一個方法來「猜想」是否發生了丟包。最簡單的想法就是,接收方每收到一個包,就向發送方返回一個 ACK,表示本身已經收到了這段數據,反過來,若是發送方一段時間內沒有收到 ACK,就知道極可能是數據包丟失了,緊接着就重發該數據包,直到收到 ACK 爲止。html

你可能注意到我用的是「猜想」,由於即便是超時了,這個數據包也可能並無丟,它只是繞了一條遠路,來的很晚而已。畢竟 TCP 協議是位於傳輸層的協議,不可能明確知道數據鏈路層和物理層發生了什麼。但這並不妨礙咱們的超時重傳機制,由於接收方會自動忽略重複的包。算法

超時和重傳的概念其實就是這麼簡單,但內部的細節倒是不少,咱們最早想到的一個問題就是,到底多長時間才能算超時呢bash

超時是怎麼肯定的?

一刀切的辦法就是,我直接把超時時間設成一個固定值,好比說 200ms,但這樣確定是有問題的,咱們的電腦和不少服務器都有交互,這些服務器位於天南海北,國內國外,延遲差別巨大,打個比方:服務器

  • 個人我的博客搭在國內,延遲大概 30ms,也就是說正常狀況下的數據包,60ms 左右就已經能收到 ACK 了,可是按照咱們的方法,200ms 才能肯定丟包(正常多是 90 到 120 ms),這效率實在是有點低
  • 假設你訪問某國外網站,延遲有 130 ms,這就麻煩了,正常的數據包均可能被認爲是超時,致使大量數據包被重發,能夠想象,重發的數據包也很容易被誤判爲超時。。。雪崩效應的感受

因此設置固定值是很不可靠的,咱們要根據網絡延遲,動態調整超時時間,延遲越大,超時時間越長。網絡

在這裏先引入兩個概念:tcp

  • RTT(Round Trip Time):往返時延,也就是**數據包從發出去到收到對應 ACK 的時間。**RTT 是針對鏈接的,每個鏈接都有各自獨立的 RTT。
  • RTO(Retransmission Time Out):重傳超時,也就是前面說的超時時間。

比較標準的 RTT 定義:網站

Measure the elapsed time between sending a data octet with a particular sequence number and receiving an acknowledgment that covers that sequence number (segments sent do not have to match segments received). This measured elapsed time is the Round Trip Time (RTT).spa

經典方法

最初的規範「RFC0793」採用了下面的公式來獲得平滑的 RTT 估計值(稱做 SRTT):3d

SRTT <- α·SRTT +(1 - α)·RTT

RTT 是指最新的樣本值,這種估算方法叫作「指數加權移動平均」,名字聽起來比較高大上,但整個公式比較好理解,就是利用現存的 SRTT 值和最新測量到的 RTT 值取一個加權平均。

有了 SRTT,就該設置對應的 RTO 的值了,「RFC0793」是這麼算的:

RTO = min(ubound, max(lbound, (SRTT)·β))

這裏面的 ubound 是 RTO 的上邊界lbound 爲 RTO 的下邊界,β 稱爲時延離散因子,推薦值爲 1.3 ~ 2.0。這個計算公式就是將 (SRTT)·β 的值做爲 RTO,只不過另外限制了 RTO 的上下限

這個計算方法,初看是沒有什麼問題(至少我是這麼感受的),可是實際應用起來,有兩個缺陷:

There were two known problems with the RTO calculations specified in RFC-793. First, the accurate measurement of RTTs is difficult when there are retransmissions. Second, the algorithm to compute the smoothed round-trip time is inadequate [TCP:7], because it incorrectly assumed that the variance in RTT values would be small and constant. These problems were solved by Karn's and Jacobson's algorithm, respectively.

這段話摘自「RFC1122」,我來解釋一下:

  • 出現數據包重傳的狀況下,RTT 的計算就會很「麻煩」,我畫了張圖來講明這些狀況:

    圖上列了兩種狀況,這兩種狀況下計算 RTT 的方法是不同的(這就是所謂的重傳二義性):

    • 狀況一:RTT = t2 - t0
    • 狀況二:RTT = t2 - t1

    可是對於客戶端來講,它不知道發生了哪一種狀況,選錯狀況的結果就是 RTT 偏大/偏小,影響到 RTO 的計算。(最簡單粗暴的解決方法就是忽略有重傳的數據包,只計算那些沒重傳過的,但這樣會致使其餘問題。。詳見 Karn's algorithm

  • 另外一個問題是,這個算法假設 RTT 波動比較小,由於這個加權平均的算法又叫低通濾波器,對忽然的網絡波動不敏感。若是網絡時延忽然增大致使實際 RTT 值遠大於估計值,會致使沒必要要的重傳,增大網絡負擔。( RTT 增大已經代表網絡出現了過載,這些沒必要要的重傳會進一步加劇網絡負擔)。

標準方法

說實話這個標準方法比較,,,麻煩,我就直接貼公式了:

SRTT <- (1 - α)·SRTT + α·RTT //跟基本方法同樣,求 SRTT 的加權平均

rttvar <- (1 - h)·rttvar + h·(|RTT - SRTT |) //計算 SRTT 與真實值的差距(稱之爲絕對偏差|Err|),一樣用到加權平均

RTO = SRTT + 4·rttvar //估算出來的新的 RTO,rttvar 的係數 4 是調參調出來的

這個算法的總體思想就是結合平均值(就是基本方法)和平均誤差來進行估算,一波玄學調參獲得不錯的效果。若是想更深刻了解這個算法,參考「RFC6298」。

重傳——TCP的重要事件

基於計時器的重傳

這種機制下,每一個數據包都有相應的計時器,一旦超過 RTO 而沒有收到 ACK,就重發該數據包。沒收到 ACK 的數據包都會存在重傳緩衝區裏,等到 ACK 後,就從緩衝區裏刪除。

首先明確一點,對 TCP 來講,超時重傳是至關重要的事件(RTO 每每大於兩倍的 RTT,超時每每意味着擁塞),一旦發生這種狀況,TCP 不只會重傳對應數據段,還會下降當前的數據發送速率,由於TCP 會認爲當前網絡發生了擁塞。

簡單的超時重傳機制每每比較低效,以下面這種狀況:

假設數據包5丟失,數據包 6,7,8,9 都已經到達接收方,這個時候客戶端就只能等服務器發送 ACK,注意對於包 6,7,8,9,服務器都不能發送 ACK,這是滑動窗口機制決定的,所以對於客戶端來講,他徹底不知道丟了幾個包,可能就悲觀的認爲,5 後面的數據包也都丟了,就重傳這 5 個數據包,這就比較浪費了。

快速重傳

快速重傳機制「RFC5681」基於接收端的反饋信息來引起重傳,而非重傳計時器超時。

剛剛提到過,基於計時器的重傳每每要等待很長時間,而快速重傳使用了很巧妙的方法來解決這個問題:服務器若是收到亂序的包,也給客戶端回覆 ACK,只不過是重複的 ACK。就拿剛剛的例子來講,收到亂序的包 6,7,8,9 時,服務器全都發 ACK = 5。這樣,客戶端就知道 5 發生了空缺。通常來講,若是客戶端連續三次收到重複的 ACK,就會重傳對應包,而不須要等到計時器超時。

但快速重傳仍然沒有解決第二個問題:到底該重傳多少個包?

帶選擇確認的重傳

改進的方法就是 SACK(Selective Acknowledgment),簡單來說就是在快速重傳的基礎上,返回最近收到的報文段的序列號範圍,這樣客戶端就知道,哪些數據包已經到達服務器了。

來幾個簡單的示例:

  • case 1:第一個包丟失,剩下的 7 個包都被收到了。

    當收到 7 個包的任何一個的時候,接收方會返回一個帶 SACK 選項的 ACK,告知發送方本身收到了哪些亂序包。注:Left Edge,Right Edge 就是這些亂序包的左右邊界

Triggering    ACK      Left Edge   Right Edge
             Segment

             5000         (lost)
             5500         5000     5500       6000
             6000         5000     5500       6500
             6500         5000     5500       7000
             7000         5000     5500       7500
             7500         5000     5500       8000
             8000         5000     5500       8500
             8500         5000     5500       9000

複製代碼
  • case 2:第 2, 4, 6, 8 個數據包丟失。
    • 收到第一個包時,沒有亂序的狀況,正常回復 ACK。

    • 收到第 3, 5, 7 個包時,因爲出現了亂序包,回覆帶 SACK 的 ACK。

    • 由於這種狀況下有不少碎片斷,因此相應的 Block 段也有不少組,固然,由於選項字段大小限制, Block 也有上限。

Triggering  ACK    First Block   2nd Block     3rd Block
          Segment            Left   Right  Left   Right  Left   Right
                             Edge   Edge   Edge   Edge   Edge   Edge

          5000       5500
          5500       (lost)
          6000       5500    6000   6500
          6500       (lost)
          7000       5500    7000   7500   6000   6500
          7500       (lost)
          8000       5500    8000   8500   7000   7500   6000   6500
          8500       (lost)
複製代碼

不過 SACK 的規範「RFC2018」有點坑爹,接收方可能會在提供一個 SACK 告訴發送方這些信息後,又「食言」,也就是說,接收方可能把這些(亂序的)數據包刪除掉,而後再通知發送方。如下摘自「RFC2018」:

Note that the data receiver is permitted to discard data in its queue that has not been acknowledged to the data sender, even if the data has already been reported in a SACK option. Such discarding of SACKed packets is discouraged, but may be used if the receiver runs out of buffer space.

最後一句是說,當接收方緩衝區快被耗盡時,能夠採起這種措施,固然並不建議這種行爲。。。

因爲這個操做,發送方在收到 SACK 之後,也不能直接清空重傳緩衝區裏的數據,一直到接收方發送普通的,ACK 號大於其最大序列號的值的時候才能清除。另外,重傳計時器也收到影響,重傳計時器應該忽略 SACK 的影響,畢竟接收方把數據刪了跟丟包沒啥區別。

DSACK 擴展

DSACK,即重複 SACK,這個機制是在 SACK 的基礎上,額外攜帶信息,告知發送方有哪些數據包本身重複接收了。DSACK 的目的是幫助發送方判斷,是否發生了包失序、ACK 丟失、包重複或僞重傳。讓 TCP 能夠更好的作網絡流控。

關於 DSACK,「RFC2883」裏舉了不少例子,有興趣的讀者能夠去閱讀一下,我這裏就不講那麼細了。


超時和重傳的內容大概就是這麼多,但願對你有所幫助。

相關文章
相關標籤/搜索