更好閱讀體驗:《理解 TCP 和 UDP》— By Gitbook
TCP 是一種提供可靠性交付的協議。
也就是說,通過 TCP 連線傳輸的資料,無差錯、不丟失、不重複、並且按序到達。
但是在網路中相連兩端之間的介質,是複雜的,並不確保資料的可靠性交付,那麼 TCP 是怎麼樣解決問題的?
這就需要了解 TCP 的幾種技術:
- 滑動視窗
- 超時重傳
- 流量控制
- 擁塞控制
下面來分別講一下這幾種技術的實現原理。
超時重傳
重傳時機
TCP 報文段在傳輸的過程中,下面的情況都是有可能發生的:
- 資料包中途丟失;
- 資料包順利到達,但對方傳送的 ACK 報文中途丟失;
- 資料包順利到達,但對方異常未響應 ACK 或被對方丟棄;
當出現這些異常情況時,TCP 就會超時重傳。
TCP 每傳送一個報文段,就對這個報文段設定一次計時器。只要計時器設定的重傳時間到了,但還沒有收到確認,就重傳這一報文段,這個就叫做「超時重傳」。
重傳演算法
先認識兩個概念
RTO ( Retransmission Time-Out ) 重傳超時時間
指傳送端傳送資料後、重傳資料前等待接收方收到該資料 ACK 報文的時間。
大白話就是,需要等待多長時間還沒收到確認,就重新傳一次。
RTO 的設定對於重傳非常重要:
- 設長了,重發就慢,沒有效率,效能差;
- 設短了,重發得就快,會增加網路擁塞,導致更多的超時,更多的超時導致更多的重發。
RTT ( Round Trip Time ) 連線往返時間
指傳送端從傳送 TCP 包開始到接收它的 ACK 報文之間所耗費的時間。
而在實際的網路傳輸中,RTT 的值每次都是隨機的,無法事先預預知。
TCP 通過測量來獲得連線當前 RTT 的一個估計值,並以該 RTT 估計值為基準來設定當前的 RTO。
這就引入了一類演算法的稱呼:自適應重傳演算法(Adaptive Restransmission Algorithm)
這類演算法的關鍵就在於對當前 RTT 的準確估計,以便適時調整 RTO。
關於自適應重傳演算法,經歷過多次的迭代和修正。
從 1981 年的 RFC793 提及的經典演算法,到 1987 年 Karn 提出的 Karn/Partridge 演算法,再到後來的 1988 年的 Jacobson / Karels 演算法。
最後的這個演算法在被用在今天的 TCP 協議中(Linux的原始碼在:tcp_rtt_estimator
)。
自適應重傳演算法的發展讀者有興趣可以參考其他資料,在這裡我拎一個現在在用的演算法出來講講,隨意感受一下。
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 確認,才會移動傳送視窗的左邊界。
下圖是傳送視窗的示意圖:
對於傳送視窗,在快取內的資料有四種狀態:
- #1 已傳送,並得到接收方 ACK 確認;
- #2 已傳送,但還未收到接收方 ACK;
- #3 未傳送,但接收方允許傳送,接收方還有空間
- #4 未傳送,且接收方不允許傳送,接收方沒有空間
如果下一刻,收到了接收方對於 32-36 位元組序的資料包的 ACK 確認,那麼傳送方的視窗就會發生「滑動」。
並且傳送下一個 46-51 位元組序的資料包。
滑動視窗的概念,描述了 TCP 的資料是怎麼傳送,以及怎麼接收的。
TCP 的滑動視窗是動態的,我們可以想象成小學常見的一個數學題,一個水池,體積 V,每小時進水量 V1, 出水量 V2。
當水池滿了就不允許再注入了,如果有個液壓系統控制水池大小,那麼就可以控制水的注入速率和量了。
應用程式可以根據自身的處理能力變化,通過 API 來控制本端 TCP 接收視窗的大小,來進行流量控制。
接收視窗
大小取決於應用、系統、硬體的限制。
下圖是接收視窗的示意圖(找不到圖,唯有自己畫了):
相對於傳送視窗,接受視窗在快取內的資料只有三種狀態:
- 已接收已確認;
- 未接收,準備接收;
- 未接收,並未準備接收;
下一刻接收到來自傳送端的 32-36 資料包,然後回送 ACK 確認報,並且移動接收視窗。
另外接收端相對於傳送端還有不同的一點,只有前面所有的段都確認的情況下才會移動左邊界,
在前面還有位元組未接收但收到後面位元組的情況下,視窗不會移動,並不對後續位元組確認,以此確保對端會對這些資料重傳。
假如 32-36 位元組不是一個報文段的,而是每個位元組一個報文段的話,那麼就會分成了 5 個報文段。
在實際的網路環境中,不能確保是按序收到的,其中會有一些早達到,一些遲到達。
如圖中的 34、35 位元組序,先收到了,接收視窗也不會移動。
因為有可能 32、33 位元組序會出現丟包或者超時,這時就需要傳送端重發報文段了。