TCP重傳的的兩種方式
沒有永遠不出錯誤的通信,這句話表明著不管外部條件多么完備,永遠都會有出錯的可能。所以,在 TCP 的正常通信過程中,也會出現(xiàn)錯誤,這種錯誤可能是由于數(shù)據(jù)包丟失引起的,也可能是由于數(shù)據(jù)包重復引起的,甚至可能是由于數(shù)據(jù)包失序引起的。
TCP 的通信過程中,會由 TCP 的接收端返回一系列的確認信息來判斷是否出現(xiàn)錯誤,一旦出現(xiàn)丟包等情況,TCP 就會啟動重傳操作,重傳尚未確認的數(shù)據(jù)。
TCP 的重傳有兩種方式,一種是基于時間,一種是基于確認信息,一般通過確認信息要比通過時間更加高效。
所以從這點就可以看出,TCP 的確認和重傳,都是基于數(shù)據(jù)包是否被確認為前提的。
TCP 在發(fā)送數(shù)據(jù)時會設(shè)置一個定時器,如果在定時器指定的時間內(nèi)未收到確認信息,那么就會觸發(fā)相應(yīng)的超時或者基于計時器的重傳操作,計時器超時通常被稱為重傳超時(RTO)。
但是有另外一種不會引起延遲的方式,這就是快速重傳。
TCP 在每次重傳一次報文后,其重傳時間都會加倍,這種"間隔時間加倍"被稱為二進制指數(shù)補償(binary exponential backoff) 。等到間隔時間加倍到 15.5 min 后,客戶端會顯示
- Connection closed by foreign host.
TCP 擁有兩個閾值來決定如何重傳一個報文段,這兩個閾值被定義在 RFC[RCF1122] 中,第一個閾值是 R1,它表示愿意嘗試重傳的次數(shù),閾值 R2 表示 TCP 應(yīng)該放棄連接的時間。R1 和 R2 應(yīng)至少設(shè)為三次重傳和 100 秒放棄 TCP 連接。
這里需要注意下,對連接建立報文 SYN 來說,它的 R2 至少應(yīng)該設(shè)置為 3 分鐘,但是在不同的系統(tǒng)中,R1 和 R2 值的設(shè)置方式也不同。
在 Linux 系統(tǒng)中,R1 和 R2 的值可以通過應(yīng)用程序來設(shè)置,或者是修改 net.ipv4.tcp_retries1 和 net.ipv4.tcp_retries2 的值來設(shè)置。變量值就是重傳次數(shù)。
tcp_retries2 的默認值是 15,這個重試次數(shù)的耗時大約是 13 - 30 分鐘,這只是一個大概值,最終耗時時間還要取決于 RTO ,也就是重傳超時時間。tcp_retries1 的默認值是 3 。
對于 SYN 段來說,net.ipv4.tcp_syn_retries 和 net.ipv4.tcp_synack_retries 這兩個值限制了 SYN 的重傳次數(shù),默認是 5,大約是 180 秒。
Windows 操作系統(tǒng)下也有 R1 和 R2 變量,它們的值被定義在下方的注冊表中
- HKLM\System\CurrentControlSet\Services\Tcpip\Parameters
- HKLM\System\CurrentControlSet\Services\Tcpip6\Parameters
其中有一個非常重要的變量就是 TcpMaxDataRetransmissions,這個 TcpMaxDataRetransmissions 對應(yīng) Linux 中的 tcp_retries2 變量,默認值是 5。這個值的意思表示的是 TCP 在現(xiàn)有連接上未確認數(shù)據(jù)段的次數(shù)。
快速重傳
我們上面提到了快速重傳,實際上快速重傳機制是基于接收端的反饋信息來觸發(fā)的,它并不受重傳計時器的影響。所以與超時重傳相比,快速重傳能夠有效的修復丟包情況。當 TCP 連接的過程中接收端出現(xiàn)亂序的報文(比如 2 - 4 - 3)到達時,TCP 需要立刻生成確認消息,這種確認消息也被稱為重復 ACK。
當失序報文到達時,重復 ACK 要做到立刻返回,不允許延遲發(fā)送,此舉的目的是要告訴發(fā)送方某段報文失序到達了,希望發(fā)送方指出失序報文段的序列號。
還有一種情況也會導致重復 ACK 發(fā)給發(fā)送方,那就是當前報文段的后續(xù)報文發(fā)送至接收端,由此可以判斷當前發(fā)送方的報文段丟失或者延遲到達。因為這兩種情況導致的后果都是接收方?jīng)]有收到報文,但是我們卻無法判斷到底是報文段丟失還是報文段沒有送達。因此 TCP 發(fā)送端會等待一定數(shù)目的重復 ACK 被接受來決定數(shù)據(jù)是否丟失并觸發(fā)快速重傳。一般這個判斷的數(shù)量是 3,這段文字表述可能無法清晰理解,我們舉個例子。
如上圖所示,報文段 1 成功接收并被確認為 ACK 2,接收端的期待序號為 2,當報文段 2 丟失后,報文段 3。失序到達,但是與接收端的期望不匹配,所以接收端會重復發(fā)送冗余 ACK 2。
這樣,在超時重傳定時器到期之前,接收收到連續(xù)三個相同的 ACK 后,發(fā)送端就知道哪個報文段丟失了,于是發(fā)送方會重發(fā)這個丟失的報文段,這樣就不用等待重傳定時器的到期,大大提高了效率。
SACK
在標準的 TCP 確認機制中,如果發(fā)送方發(fā)送了 0 - 10000 序號之間的數(shù)據(jù),但是接收方只接收到了 0 -1000, 3000 - 10000 之間的數(shù)據(jù),而 1000 - 3000 之間的數(shù)據(jù)沒有到達接收端,此時發(fā)送方會重傳 1000 - 10000 之間的數(shù)據(jù),實際上這是沒有必要的,因為 3000 后面的數(shù)據(jù)已經(jīng)被接收了。但是發(fā)送方無法感知這種情況的存在。
如何避免或者說解決這種問題呢?
為了優(yōu)化這種情況,我們有必要讓客戶端知道更多的消息,在 TCP 報文段中,有一個 SACK 選項字段,這個字段是一種選擇性確認(selective acknowledgment)機制,這個機制能告訴 TCP 客戶端,用我們的俗語來解釋就是:“我這里最多允許接收 1000 之后的報文段,但是我卻收到了 3000 - 10000 的報文段,請給我 1000 - 3000 之間的報文段”。
但是,這個選擇性確認機制的是否開啟還受一個字段的影響,這個字段就是 SACK 允許選項字段,通信雙方在 SYN 段或者 SYN + ACK 段中添加 SACK 允許選項字段來通知對端主機是否支持 SACK,如果雙方都支持的話,后續(xù)在 SYN 段中就可以使用 SACK 選項了。
這里需要注意下:SACK 選項字段只能出現(xiàn)在 SYN 段中。
偽超時和重傳
在某些情況下,即使沒有出現(xiàn)報文段的丟失也可能會引發(fā)報文重傳。這種重傳行為被稱為偽重傳(spurious retransmission) ,這種重傳是沒有必要的,造成這種情況的因素可能是由于偽超時(spurious timeout),偽超時的意思就是過早的判定超時發(fā)生。造成偽超時的因素有很多,比如報文段失序到達,報文段重復,ACK 丟失等情況。
檢測和處理偽超時的方法有很多,這些方法統(tǒng)稱為檢測算法和響應(yīng)算法。檢測算法用于判斷是否出現(xiàn)了超時現(xiàn)象或出現(xiàn)了計時器的重傳現(xiàn)象。一旦出現(xiàn)了超時或者重傳的情況,就會執(zhí)行響應(yīng)算法撤銷或者減輕超時帶來的影響,下面是幾種算法,此篇文章暫不深入這些實現(xiàn)細節(jié):
- 重復 SACK 擴展- DSACK
- Eifel 檢測算法
- 前移 RTO 恢復 - F-RTO
- Eifel 響應(yīng)算法
包失序和包重復
上面我們討論的都是 TCP 如何處理丟包的問題,我們下面來討論一下包失序和包重復的問題。
包失序
數(shù)據(jù)包的失序到達是互聯(lián)網(wǎng)中極其容易出現(xiàn)的一種情況,由于 IP 層并不能保證數(shù)據(jù)包的有序性,每個數(shù)據(jù)包的發(fā)送都可能會選擇當前情況傳輸速度最快的鏈路,所以很有可能出現(xiàn)發(fā)送了 A - > B -> C 的三個數(shù)據(jù)包,到達接收端的數(shù)據(jù)包順序是 C -> A -> B 或者 B -> C -> A 等等。這就是包失序的一種現(xiàn)象。
在包傳輸中,主要分為兩種鏈路:正向鏈路(SYN)和反向鏈路(ACK)。
如果失序發(fā)生在正向鏈路,TCP 是無法正確判斷數(shù)據(jù)包是否丟失的,數(shù)據(jù)的丟失和失序都會導致接收端收到無序的數(shù)據(jù)包,造成數(shù)據(jù)之間的空缺。如果這種空缺不夠大的話,這種情況影響不大;但是如果空缺比較大的話,可能會導致偽重傳。
如果失序發(fā)生在反向鏈路,就會使 TCP 的窗口前移,然后收到重復而應(yīng)該被丟棄的 ACK,導致發(fā)送端出現(xiàn)不必要的流量突發(fā),影響可用網(wǎng)絡(luò)帶寬。
回到我們上面討論的快速重傳,由于快速重傳是根據(jù)重復 ACK 推斷出現(xiàn)丟包而啟動的,它不用等到重傳計時器超時。由于 TCP 接收端會對接收到的失序報文立刻返回 ACK,所以網(wǎng)絡(luò)中任何一個失序到達的報文都可能會造成重復 ACK。假設(shè)一旦收到 ACK,就會啟動快速重傳機制,當 ACK 數(shù)量激增,就會導致大量不必要的重傳發(fā)生,所以快速重傳應(yīng)該達到重復閾值(dupthresh)再觸發(fā)。但是在互聯(lián)網(wǎng)中,嚴重的失序并不常見,因此 dupthresh 的值可以設(shè)置的盡量小,一般來說 3 就能處理絕大部分情況。
包重復
包重復也是互聯(lián)網(wǎng)中出現(xiàn)很少的一種情況,它指的是在網(wǎng)絡(luò)傳輸過程中,包可能會出現(xiàn)傳輸多次的情況,當重傳生成時,TCP 可能會出現(xiàn)混淆。
包的重復可以使接收端生成一系列的重復 ACK,這種情況可以使用 SACK 協(xié)商來解決。