1 RTT算法
1.1 概述
-
上一節說了重傳機制需要設置一個重傳超時值(RTO,Retransmission TimeOut),RTO設長了,重發太慢;設短了,可能導致包沒有丟,就重發了,可能導致雪崩效應(重發多,失敗多,失敗多,導致更多的重發...請參考: 暴風門事件)。
-
那么該值怎么設置?
- 由於一開始無法確定設置某個值,所以需要程序自動適應,動態地去設置
- RTT,Round Trip Time,設置的參考值為數據報來回所需要的時間
1.2 經典算法
-
采樣最近幾次的RTT
-
SRTT計算(Smoothed RTT):α (加權移動平均)取值在0.8 到 0.9之間
$$
SRTT = ( α * SRTT ) + ((1- α) * RTT)
$$ -
計算RTO:UBOUND為最大RTT(上限值),LBOUND為最小RTT(下限值),β 值一般在1.3到2.0之間
$$
RTO = min [ UBOUND, max [ LBOUND, (β * SRTT) ] ]
$$
1.3 Karn / Partridge 算法(SRTT算法的優化)
- 經典算法的問題:
- 原始 + 重傳 + ACK = 總時間作為RTO,算長了(特殊情況)
- 重傳 + ACK = 總時間作為RTO,算短了(特殊情況)
- 該算法的最大特點:忽略重傳,不把重傳作為采樣
1.4 Jacobson / Karels 算法
-
忽略重傳的問題:
- 在某一時間,網絡閃動,突然變慢了,產生了比較大的延時,這個延時導致要重傳所有的包(RTO設置的比較小)
- 但是由於重傳不會重新更新RTO,導致一直丟包,一直重試了。
-
SRTT算法以及優化都逃不出RTT有一個大的波動的話,很難被發現,所以需要綜合考慮。
-
公式:
SRTT = SRTT + α (RTT – SRTT) —— 計算平滑RTT
DevRTT = (1-β)DevRTT + β(|RTT-SRTT|) ——計算平滑RTT和真實的差距(加權移動平均)
RTO= µ * SRTT + ∂ *DevRTT —— 神一樣的公式
# 在Linux下,α = 0.125,β = 0.25, μ = 1,∂ = 4 —— nobody knows why, it just work.
2 滑動窗口
2.1 概述
- 第一節說過TCP可靠性的保證之一就是流量控制(Flow Control)。
- TCP需要知道現在網絡的數據處理速度,才能更好防止丟包,而流量控制就是為了測量現在的網絡數據處理速度的。
- TCP報頭有一個字段:窗口,該字段是接收端告知發送端自己的緩沖空間,防止發送端發送太快緩沖區溢出。
2.2 緩沖空間
- 其實類似java的NIO中的ByteBuffer
- 發送端:
- LastByteWritten:上層應用可寫入的位置
- LastByteSent:正在發送的位置
- LastByteAcked:已經收到ACK的位置
- LastByteAcked ~ LastByteSent區間:表示已經發送但是未收到ACK的數據
- LastByteSent ~ LastByteWritten區間:表示未發送出去的數據
- 接收端:
- LastByteRead:TCP緩沖區中讀到的位置
- NextByteExpected:收到的連續包的最后一個位置
- LastByteRcved:收到的包的最后一個位置
- NextByteExpected ~ LastByteRcved區間:未到達的數據區間
- LastByteRead ~ NextByteExpected區間:已收到的數據區間
- 接收端回復:
- ACK中會匯報自己的Window = MaxRcvBuffer – LastByteRcvd – 1(只剩下這么多的空間能裝新的數據)
- 發送方會根據窗口來控制發送數據的大小,以保證接收方可以處理
2.3 滑動窗口
(1)發送方滑動窗口示意圖:
- #1 已收到ACK確認的數據
- #2 已發送到未收到ACK確認的數據
- #3 在窗口中未發出的(接收方還有空間)
- #4 窗口以外的數據(接收方沒有空間)
- 其中#2 + #3的黑框就是滑動窗口
(2)發送方滑動后的滑動窗口示意圖:
- 紅色是已經新收到ACK的數據
- 綠色是新加入滑動窗口的數據
(3)接收端控制發送端的圖示:
(4)Zero Window(堅持定時器實現):
- 上圖一個處理緩慢的Server將Client的TCP 滑動窗口給降到0.
- 降到0之后,發送端發送Zero Window Probe包(ZWP)給接收方,讓接收方ACK它的Window尺寸,一般發送3次,每次30~60秒。
- 連續3次為0,有些TCP Client將發送RST把連接斷開。
(5)Silly Window Syndrome(糊塗窗口綜合症)
-
由於處理緩慢的Server把TCP接收方的滑動窗口不斷降低,導致滑動窗口很少(如:4個字節),此時發送端仍然義無反顧的發送。
-
MSS默認為536,有效數據才4個字節,帶寬利用率1%不到,造成了巨大的浪費。
-
窗口是為了控制傳輸過程中的速度。而MSS是為了控制TCP報文段大小。
-
MTU = MSS + TCP頭(20字節) +IP頭(20字節)。
-
解決方案:
- 問題由Receiver引起,那么Receiver收到的數據導致window size(rwnd,receiver window)小於某個值,可以直接ack window size為0給Sender,這樣就把window關閉了,也阻止Sender再發數據過來,等到Receiver處理了一些數據后window size大於等於MSS時,或者Receiver Buffer有一般為空(具體策略有很多),可以把window 打開讓Sender發送數據過來。
- 問題由Sender引起,使用延時處理,禁止大量小包發送。(打開之后無法使用telnet或者SSH這種交互性比較強的程序)
- 等到Window Size >= MSS 或者 Data Size >= MSS
- 收到之前發送數據的ACK包,它才會發送數據,否者繼續積攢數據
3 阻塞處理
3.1 概述
- 前面說過網絡波動時,TCP報文超時,引起重傳,但是重傳導致網絡負擔更大,可能導致網絡更加繁忙。
- 所以TCP不會這么自私,它在發現阻塞時,會主動讓路(並不是直接停止發送TCP報文)。
- 阻塞控制算法:
- 慢啟動
- 阻塞避免
- 阻塞發生時快速重傳(上一節說過)
- 快速恢復
3.2 慢啟動算法
- 程序剛剛加入網絡時,一點一點提升速度。
- 算法流程:
- 連接建好的開始先初始化cwnd = 1(窗口),表明可以傳一個MSS大小的數據
- 每當收到一個ACK,cwnd++,線性上升
- 每當過了一個RTT,cwnd = cwnd*2, 呈指數上升。
- ssthresh(slow start threshold),是一個上限,當cwnd >= ssthresh時,就會進入“擁塞避免算法”
3.3 阻塞避免算法
- 一般來說ssthresh的值是65535,單位是字節,當cwnd達到這個值時后
- 算法流程:
- 收到一個ACK時,cwnd = cwnd + 1/cwnd
- 每過一個RTT時,cwnd = cwnd + 1
3.4 阻塞狀態時的算法
當丟包的時候,有兩種情況
-
等到RTO超時,重傳數據包,TCP認為該情況太糟糕了
- sshthreash = swnd / 2
- cwnd 重置為1
- 進入慢啟動算法
-
快速重傳算法,即收到3個重復的ACK就開始重傳,無需等待RTO超時
- TCP Tahoe(代表版本)的實現和RTO超時一樣。
- TCP Reno的實現:
- cwnd = cwnd / 2
- sshthresh = cwnd
- 進入快速恢復算法——Fast Recovery
3.5 快速恢復算法(TCP Reno)
-
這里就講解TCP Reno版本的快速恢復算法,想了解更多請參考:TCP 的那些事兒(下)
-
算法流程:
- cwnd = sshthresh + 3 * MSS(3的意思是確認有3個數據包被收到了)
- 重傳Duplicated ACKs指定的數據包
- 如果再收到 Duplicated ACKs,那么cwnd = cwnd +1
- 如果收到了新的Ack,那么,cwnd = sshthresh ,然后就進入了擁塞避免的算法了。
-
缺點:由於3個重復的ACKs,並不代表只丟了一個數據包;如果丟了多個數據包,將導致RTO。