TCP是可靠傳輸。可靠之一體現在收到數據后,返回去一個確認。但是不能完全避免的是,數據和確認都可能丟失。解決這個辦法就是,提供一個發送的重傳定時器:如果定時器溢出時還沒收到確認,它就重傳這個報文段。
想法是完美的,關鍵之處在於超時和重傳的策略,即怎么決定超時間隔和如何確定重傳的頻率。
書中舉了一個簡單的超時重傳例子:
如圖:
比如A往B傳,傳了一部分數據后,把B的網線拔了(前邊講過,如果不傳數據的話,雙方沒法知道這個連接已經斷了)。然后開始A再給B發數據,此時tcpdump出來發現,連續重傳了一個報文段:時間間隔分別是,1.013s, 3, 6, 12, 24 和多個64s...最后發了個復位報文段表示我放棄了。。(從第一次開始發這個報文段,到最后發一個復位段的時間差大約是9分鍾,這個9分鍾一般在TCP實現中是不變的)。
往返時間測量:
TCP的超時時間很大程度上是依賴報文段的往返時間。因此測量往返時間顯得尤為重要。
因為鏈路上的網絡流量或者路由器等的存在,往返時間一般不會是一成不變的,可能會經常發生變化。
最初的TCP規范這樣協議:RTT(Round-Trip Time)表示往返時間,用M表示測量到的RTT。
更新過的RTT = 0.9*RTT + 0.1*M 表示,我估計新的往返時間將是,0.9倍的之前的RTT + 0.1倍的新測量的RTT。(0.9叫平滑因子)
得到了新的估計RTT,推薦的重傳超時時間RTO(Retransmission TimeOut)的值應該設置為
RTO = RTT*b (這里的b是一個推薦值為2的時延離散因子)。超時時間就是大約2倍的往返時間。
以上這個計算超時時間的方法存在缺陷,[Jacobson 1988] 作出了詳細分析,當RTT變化范圍比較大的時候,這個方法顯得力不從心了,可能會引起不必要的重傳。這樣當網絡負載比較高的時候,再重傳會火上澆油...
這就又有了新的方法計算重傳超時時間:
前邊說到如果RTT變化范圍較大時,容易發生不必要重傳。學過數學的都會知道,方差可以體現出波動大小。這個方法就是用到了方差來均衡下。
這里有個公式用來計算RTO,懂的原理就好了,這個計算RTO的公式依賴於估計的RTT和均值偏差(逼近與標准差),而最初的方法則使用了被平滑的RTT的一個倍數(b=2)。這塊知道是這么個事就好~
往返時間RTT的測量:
如圖:
左邊的時間軸上有三個括號,它們表明為進行RTT計算對哪些個報文段進行了計時,並不是所有的報文段都被計時。在發送一個報文段時,如果給定連接的重傳定時器已經被使用,則該報文段不被計時。如圖報文段4或者報文段7都沒有參與計時。
對每個連接而言,除了這個滴答計數器,報文段中數據的起始序號也被記錄下來。當收到一個包含這個序號的確認后,該定時器就被關閉。如果ACK到達時數據沒有被重傳,則被平滑的RTT和被平滑的均值偏差將基於這個新測量進行更新。
在每次調用500 ms的TCP的定時器例程時,就增加一個計數器來完成計時。這意味着,如果一個報文段的確認在它發送550 ms后到達,則該報文段的往返時間RTT將是1個滴答(即500 ms)或是2個滴答(即1000 ms)。
如圖RTT測量和時鍾滴答:
-擁塞舉例:
主機slip總是通告窗口大小為4096,而主機vangogh則通告窗口為8192。
如圖:
報文段45丟失了,報文段58是正常接收43的報文段給出的確認,然后接着接收主機連續發了8個ack 6657。可以看出是重發第三次(除了正常確認的中第3個)時,發送主機重傳發送了63報文段。
這收到第三個ack才重傳也是算法中要求的,當收到第3個時,就假定一個報文段已經丟失並重傳自那個序號起的一個報文段。這就是Jacobson的快速重傳算法。
值得注意的是,在重傳后(報文段63),發送方繼續正常的數據傳輸(報文段67、69和71)。TCP不需要等待對方確認重傳。
這里再分析一下接收端是怎么處理的: 當按序收到正常數據(報文段43)后,接收TCP將255個字節的數據交給用戶進程。但下一個收到的報文段(報文段46)是失序的(數據的開始序號 6913 並不是下一個期望的序號 6657)。TCP保存256字節的數據,並返回一個已成功接收數據的最大序號加1(6657)的ACK。被vangogh接收到的后面7個報文段(48, 50, 52, 54, 55, 57和59)也是失序的,接收方TCP保存這些數據並產生重復ACK(TCP實現沒法告訴對方,我就缺某某個報文段,它只能告訴發送方我的確認序號一直是這個)。
當缺少的報文段(報文段 63)到達時,接收方TCP在其接收緩存中組合好第6657~8960字節的數據,並將這2304字節的數據交給用戶進程。所有這些數據在報文段72中進行確認。
值得注意的是,此時該ACK通告窗口大小為5888(8192-2304,原來的通告窗口大小是8192),這是因為用戶進程此時還沒有讀取出這些緩存中的字節。
-擁塞避免:
該算法假定由於分組受到損壞引起的丟失是非常少的(遠小於1%),因此分組丟失就意味着在源主機和目的主機之間的某處網絡上發生了擁塞。
有兩種分組丟失的指示:發生超時和接收到重復的確認(如果使用超時作為擁塞指示,則需要使用一個好的RTT算法)。
前邊講過慢啟動,擁塞避免算法和慢啟動算法是兩個目的不同、獨立的算法。但是當擁塞發生時,我們希望降低分組進入網絡的傳輸速率,於是可以調用慢啟動來作到這一點。在實際中這兩個算法通常在一起實現。
-快速重傳和快速恢復算法:
在前邊擁塞舉例時,觀察到第三個ack過來,發送端才進行重傳。這是因為:由於我們不知道一個重復的ACK是由一個丟失的報文段引起的,還是由於僅僅出現了幾個報文段的重新排序,因此我們等待少量重復的ACK到來。假如這只是一些報文段的重新排序,則在重新排序的報文段被處理並產生一個新的ACK之前,只可能產生1 ~ 2個重復的ACK。 如果一連串收到3個或3個以上的重復ACK,就非常可能是一個報文段丟失了。於是我們就重傳丟失的數據報文段,而無需等待超時定時器溢出。這就是快速重傳算法。
接下來收到重傳的ACK以前,發送了3個新的數據的報文段(報文段67,69和71)。執行的不是慢啟動算法而是擁塞避免算法。這就是快速恢復算法。在這種情況下沒有執行慢啟動的原因是由於收到重復的ACK不僅僅告訴我們一個分組丟失了,而是在收發兩端之間仍然有流動的數據(由於接收方只有在收到另一個報文段時才會產生重復的ACK,而該報文段已經離開了網絡並進入了接收方的緩存),因此我們不想執行慢啟動來突然減少數據流。
重新分組:
當TCP超時並重傳時,它不一定要重傳同樣的報文段。而是TCP允許進行重新分組而發送一個較大的報文段,這將有助於提高性能(當然,這個較大的報文段不能夠超過接收方聲明的MSS)。
如圖:
第3個發送前,斷開網線。開始發送3,此時發生了重傳,在放棄連接前,又鍵入了幾個字節。然后插上網線,發現第8行,是把前邊兩次的分組組裝成了一個分組發過去的。
TCP的超時重傳...end
-