理解 TCP(五):可靠性交付的實現

更好閱讀體驗:《理解 TCP 和 UDP》— By Gitbook html

TCP 是一種提供可靠性交付的協議。
也就是說,經過 TCP 鏈接傳輸的數據,無差錯、不丟失、不重複、而且按序到達。
可是在網絡中相連兩端之間的介質,是複雜的,並不確保數據的可靠性交付,那麼 TCP 是怎麼樣解決問題的?
這就須要瞭解 TCP 的幾種技術: git

  1. 滑動窗口
  2. 超時重傳
  3. 流量控制
  4. 擁塞控制

下面來分別講一下這幾種技術的實現原理。 算法

超時重傳

重傳時機

TCP 報文段在傳輸的過程當中,下面的狀況都是有可能發生的:shell

  1. 數據包中途丟失;
  2. 數據包順利到達,但對方發送的 ACK 報文中途丟失;
  3. 數據包順利到達,但對方異常未響應 ACK 或被對方丟棄;

當出現這些異常狀況時,TCP 就會超時重傳。
TCP 每發送一個報文段,就對這個報文段設置一次計時器。只要計時器設置的重傳時間到了,但尚未收到確認,就重傳這一報文段,這個就叫作「超時重傳」。 windows

重傳算法

先認識兩個概念

RTO ( Retransmission Time-Out ) 重傳超時時間

指發送端發送數據後、重傳數據前等待接收方收到該數據 ACK 報文的時間。
大白話就是,須要等待多長時間還沒收到確認,就從新傳一次。 緩存

RTO 的設置對於重傳很是重要: 網絡

  1. 設長了,重發就慢,沒有效率,性能差;
  2. 設短了,重發得就快,會增長網絡擁塞,致使更多的超時,更多的超時致使更多的重發。

RTT ( Round Trip Time ) 鏈接往返時間

指發送端從發送 TCP 包開始到接收它的 ACK 報文之間所耗費的時間。
而在實際的網絡傳輸中,RTT 的值每次都是隨機的,沒法事先預預知。
TCP 經過測量來得到鏈接當前 RTT 的一個估計值,並以該 RTT 估計值爲基準來設置當前的 RTO。
這就引入了一類算法的稱呼:自適應重傳算法(Adaptive Restransmission Algorithm)
這類算法的關鍵就在於對當前 RTT 的準確估計,以便適時調整 RTO。 electron

關於自適應重傳算法,經歷過屢次的迭代和修正。
從 1981 年的 RFC793 說起的經典算法,到 1987 年 Karn 提出的 Karn/Partridge 算法,再到後來的 1988 年的 Jacobson / Karels 算法。
最後的這個算法在被用在今天的 TCP 協議中(Linux的源代碼在:tcp_rtt_estimator)。 tcp

自適應重傳算法的發展讀者有興趣能夠參考其餘資料,在這裏我拎一個如今在用的算法出來說講,隨意感覺一下。 ide

Jacobson / Karels 算法

1988年,有人推出來了一個新的算法,這個算法叫 Jacobson / Karels Algorithm(參看RFC6298)。
其計算公式:

SRTT = SRTT + α ( RTT – SRTT ) —— 計算平滑 RTT

DevRTT = ( 1-β ) DevRTT + β ( | RTT - SRTT | ) ——計算平滑 RTT 和真實的差距(加權移動平均)

RTO= µ SRTT + ∂ DevRTT

其中:

  • αβμ 是能夠調整的參數,在 RFC6298 中給出了對應的參考值,而在Linux下,α = 0.125,β = 0.25, μ = 1,∂ = 4;

  • SRTT 是 Smoothed RTT 的意思,是 RTT 的平滑計算值,即根據每次測量的 RTT 和舊的 RTT 進行運算,得出新的 RTT。SRTT 的值,會在每一次測量到 RTT 以後進行更新;

  • DevRTT 是 Deviation RTT 的意思,根據每次測量的 RTT 和舊的 SRTT 值進行運算,得出新的 DevRTT;

由算法能夠知道 RTO 的值會根據每次測量的 RTT 值變化而變化,基本要點是 TCP 監視每一個鏈接的性能,由每個 TCP 的鏈接狀況推算出合適的 RTO 值,根據不一樣的網絡狀況,自動修改 RTO 值,以適應負責的網絡變化。

擁塞控制

滑動窗口 Sliding Window

滑動窗口協議比較複雜,也是 TCP 協議的精髓所在。

TCP 頭裏有一個字段叫 Window,叫 Advertised-Window,這個字段是接收端告訴發送端本身還有多少緩衝區能夠接收數據。因而發送端就能夠根據這個接收端的處理能力來發送數據,而不會致使接收端處理不過來。

滑動窗口分爲「接收窗口」和「發送窗口」
由於 TCP 協議是全雙工的,會話的雙方均可以同時接收和發送,那麼就須要各自維護一個「發送窗口」和「接收窗口」。

發送窗口

大小取決於對端通告的接受窗口。
只有收到對端對於本端發送窗口內字節的 ACK 確認,纔會移動發送窗口的左邊界。

下圖是發送窗口的示意圖:

tcps-send-wwindows.png

對於發送窗口,在緩存內的數據有四種狀態:

  • #1 已發送,並獲得接收方 ACK 確認;
  • #2 已發送,但還未收到接收方 ACK;
  • #3 未發送,但接收方容許發送,接收方還有空間
  • #4 未發送,且接收方不容許發送,接收方沒有空間

若是下一刻,收到了接收方對於 32-36 字節序的數據包的 ACK 確認,那麼發送方的窗口就會發生「滑動」。
而且發送下一個 46-51 字節序的數據包。

tcps-send-wslide.png

滑動窗口的概念,描述了 TCP 的數據是怎麼發送,以及怎麼接收的。
TCP 的滑動窗口是動態的,咱們能夠想象成小學常見的一個數學題,一個水池,體積 V,每小時進水量 V1, 出水量 V2。
當水池滿了就不容許再注入了,若是有個液壓系統控制水池大小,那麼就能夠控制水的注入速率和量了。
應用程序能夠根據自身的處理能力變化,經過 API 來控制本端 TCP 接收窗口的大小,來進行流量控制。

接收窗口

大小取決於應用、系統、硬件的限制。

下圖是接收窗口的示意圖(找不到圖,惟有本身畫了):

tcps-receive-wwindows.png

相對於發送窗口,接受窗口在緩存內的數據只有三種狀態:

  • 已接收已確認;
  • 未接收,準備接收;
  • 未接收,並未準備接收;

下一刻接收到來自發送端的 32-36 數據包,而後回送 ACK 確認報,而且移動接收窗口。

tcps-receive-wslide.png

另外接收端相對於發送端還有不一樣的一點,只有前面全部的段都確認的狀況下才會移動左邊界,
在前面還有字節未接收但收到後面字節的狀況下,窗口不會移動,並不對後續字節確認,以此確保對端會對這些數據重傳。
假如 32-36 字節不是一個報文段的,而是每一個字節一個報文段的話,那麼就會分紅了 5 個報文段。
在實際的網絡環境中,不能確保是按序收到的,其中會有一些早達到,一些遲到達。

tcps-receive-disorder.png

如圖中的 3四、35 字節序,先收到了,接收窗口也不會移動。
由於有可能 3二、33 字節序會出現丟包或者超時,這時就須要發送端重發報文段了。

參考

The TCP/IP Guide
TCP 的那些事兒(下)
《後臺開發 核心技術與應用實踐》
《計算機網絡》

相關文章
相關標籤/搜索