從上一篇示例中我們可以看到在TCP中有一個重要的過程就是決定何時進行超時重傳,也就是RTO的計算更新。由於網絡狀況可能會受到路由變化、網絡負載等因素的影響,因此RTO也必須跟隨網絡狀況動態更新。如果TCP過早重傳,則可能會向網絡中注入很多重復報文,如果過晚重傳,則在丟包時候則會影響滑窗前行可能會降低網絡利用率。因為TCP在接收到數據后會發送累計的ACK number,因此TCP發送某個系列號的報文后,在接收到覆蓋此系列號的ACK報文的時候,測量發送和接收之間的時間,這個測量就叫做RTT采樣(RTT sample)。TCP對於每個連接都會根據RTT采樣來維護跟新RTO,同時還會維護一個RTO超時的定時器。注意是對每個連接維護一個超時定時器,而不是對每個發出去的TCP報文。當TCP發出數據報文(或者SYN、FIN報文)的時候,如果之前沒有等待ACK的報文則會設置這個連接的RTO定時器,如果之前有等待ACK的報文,則並不會重啟RTO定時器。當TCP同時有多個報文發出且沒有等到ACK的時候,則會先重傳第一個報文,第一個報文重傳成功收到ACK后,再設置RTO定時器然后重傳第二個報文。
本篇先來介紹一下協議中更新計算RTO的方法。協議中主要有兩種方法來計算RTO一種是RFC793的經典方法(classic method),另一種是RFC6298的標准方法(standard method)。
一、經典方法
在原始的RFC793中關於RTO更新的介紹只有半頁文字的樣子,它首先讓TCP使用下面的公式更新一個平滑RTT估計(smoothed RTT estimator、簡稱SRTT):
SRTT = ( ALPHA * SRTT ) + ((1-ALPHA) * RTT)
其中RTT是之前介紹的一個RTT采樣值,ALPHA則是一個平滑因子(smoothing factor),協議給出的示例范圍是0.8--0.9之間。這個過程也叫做指數加權移動平均(exponentially weighted moving average、簡稱EWMA)。可以看到這個計算過程只需要保留一個RTT采樣值就行了,而不需要保留過多的歷史RTT采樣。
接着在按照下面公式計算出RTO:
RTO = min[UBOUND,max[LBOUND,(BETA*SRTT)]]
其中BETA是一個延遲因子,協議給出的示例范圍是1.3--2.0。UBOUND是一個RTO上限,LBOUND是一個RTO下限,UBOUND和LBOUND協議給出的示例范圍分別是1分鍾和1秒,顯然這兩個值對於現代TCP網絡可能並不合適。
二、標准方法
RFC1122指出上面介紹的計算RTT的經典方法中存在兩個問題,一個是在發生TCP重傳的時候,RTT采樣的精確測量非常困難,第二個問題是經典方法認為RTT是比較平穩的狀態,變化比較小,因此SRTT的計算是不合適的。實際上比如在低速網絡中,TCP的數據包的不同大小會導致不同的傳輸耗時,進而可能就會導致RTT采樣值差距比較大。對於上面兩個問題第一個問題由karn算法解決,karn算法我們后續進行介紹。第二個問題由Jacobson算法解決,該算法在RTO估計中添加了一個用於反映RTT波動的RTTVAR變量。而RFC6298就是一個基於Jacobson算法的RTO計算文檔,我們把這種RTO計算方法稱為標准方法。
1、RTO計算及更新
為了計算當前的RTO,TCP發送端維護兩個狀態變量一個是SRTT(smoothed round-trip time)一個是RTTVAR (round-trip time variation),另外還有一個TCP時鍾粒度G。
-
1)、在沒有測量到有效的RTT采樣之前,設置RTO=1s;
-
2)、在第一個有效的RTT采樣測量出來后,假設采樣值為R,則進行如下初始化過程
SRTT = R
RTTVAR = R/2
RTO = SRTT + max(G,K*RTTVAR)
其中K = 4;
-
3)、當隨后的RTT采樣R’測量到以后,按照如下更新:
RTTVAR = (1 - beta) * RTTVAR + beta * |SRTT - R'|
SRTT = (1 - alpha) * SRTT + alpha * R'
注意用於計算RTTVAR的公式中的SRTT是本次更新前的值,也就是說這兩個公式的計算順序不能顛倒。其中alpha=1/8、 beta=1/4,alpha和beta的選值允許計算機通過移位做除法的快速運算。
計算出SRTT和RTTVAR后,RTO仍舊按照如下更新:
RTO = SRTT + max (G, K*RTTVAR)
-
4)、當RTO計算出來后,如果RTO小於1s,RTO則應該設置為1s。雖然給出的是1s的下限,但是協議允許使用更低的下限。
-
5)、也可以對RTO設置一個上限,協議建議上限至少為60s。
關於上面的RTT采樣,協議要求使用karn算法進行采樣,同時要求至少在一個RTT里面采樣一次(除非因為karn算法導致不可能在一個RTT里面采樣一次)。協議指出對於每個TCP報文進行RTT采樣測量不一定會得到更好的RTT估計值。
2、RTO定時器的管理
協議對於RTO定時器的建議管理方法如下:
-
1)、每次一個包含數據的TCP報文發送出去的時候(包括重傳),如果RTO定時器沒有運行,則重啟RTO定時器,並設置定時時間為RTO。
-
2)、當所有發出的數據報文都被ACK后,關閉這個RTO定時器。
-
3)、當一個新的ack number到達的時候(新的ack number是指ack了新數據),如果還有未被ACK的數據,則重啟RTO定時器,並設置定時時間為當前RTO。
當RTO定時器觸發的時候(即所設置的定時時間到達的時候)
-
4)、在還沒有ACK的報文里面重傳最早發出去的報文。
-
5)、設置RTO = RTO * 2,這也就是我們經常說的指數回退。也可以和上面RTO更新過程一樣添加一個同樣的上限來限制RTO大小。
-
6)、重啟RTO定時器,設置定時時間為RTO(這里的RTO是已經回退過的RTO)。
-
7)、如果RTO定時器是因為等待SYN報文的ACK而超時,如果實現上使用的RTO值小於3s,這個RTO定時器必須被重新初始化為3s。
在重傳完成后,新的RTT采樣可能會將RTO設置為與原來比較接近的值,從而消除指數回退對於RTO的影響。另外在多次指數回退過程中,TCP實現可能會清空SRTT和RTTVAR的值,一旦這兩個被清空,則需要使用上面RTO計算及更新中的第2)步來初始化SRTT和RTTVAR。
三、RTO定時器管理示例
下面的測試示例關閉了TLP功能,相關介紹見后面文章
1、RTO超時重傳成功恢復后發送的新數據又RTO超時。對於這種場景我們從下圖可以看到新發送的數據在RTO超時后,超時時間大約為1.5s,也就是說RTO定時器在之前指數回退后現在已經恢復。
2、RTO超時重傳collapse后,只回復部分ACK確認包。如下圖所示,在數據包No6超時重傳的時候,把No7數據包collapse在一起了,也就是說No8重傳包同時包含了No6和No7數據包的內容,從圖中的len字段也可以看出來,如果這時候client只是ACK確認了No6報文的內容(No12確認包Ack=1740381487正好是No7數據包的Seq),No11重傳包只有收到Ack>=1740381493時候才會取消RTO定時器,這時候部分ACK並不能取消server在發送No11時候的定時器,最終RTO超時超時重傳No13數據包,重傳的時候被部分ACK確認的數據不會在重傳,因為No13只是重傳了No7數據包。(關於重傳collapse的內容我們后面內容在進行介紹)。
3、RTO超時重傳成功后還有待重傳數據。如下圖所示,server端發出了No6和No7兩個數據包,RTO超時后重傳No6報文,No6重傳成功后server端發現還有一個No7報文等待重傳,接着在收到No11確認包后立即進行了一個快速重傳。(嚴格的說應該叫做慢啟動重傳SlowStartRetrans,但是實際上走的是快速重傳的流程,快速重傳請參考后面的內容)。快速重傳的同時會初始化一個RTO定時器,如果快速重傳失敗,接着進行RTO超時重傳(No13-No17)。這里可以看到server端在發出No12報文時候初始化的RTO定時器定時時間為1.5s。也就是之前RTO指數回退的作用已經消除了,可以看到這里與協議是存在一些差異的。
4、SACK reneging下RTO超時重傳的示例請參考后面SACK和FACK相關的內容。



