TCP协议-TCP超时重传机制
一、前言
在TCP通信中,既要保證在網絡正常的情況下提供可靠的交付服務,又要保證在網絡異常的情況下也提供可靠的交付服務。而TCP的超時重傳機制就是解決在網絡異常情況下的可靠傳輸問題的。
二、通過序列號和確認應答提供可靠性
在TCP通信中,當發送端的數據到達接收端時,接收端會返回一個已收到消息的通知。這個通知消息叫做確認應答(ACK)。
ACK(Positive Acknowledgement) 意指已經接收。
?2.1 正常數據傳輸過程
正常數據傳輸?可以看到,TCP通過確認應答機制實現可靠的數據傳輸服務。
?2.2 異常數據傳輸過程
當發送端將數據發出后,會等待對端的確認應答。如果有確認應答,說明數據已經成功到達對端。否則,數據可能已經丟失。
在一定時間內沒有等到確認應答,發送端會認為數據已經丟失,并進行重發。這樣,即使有丟包,仍能保證數據達到對端,實現可靠傳輸。
數據丟失的情況發送端未收到確認應答,不一定是數據丟失。也可能是對端已經收到數據,只是返回的確認應答在途中丟失。這種情況也會導致發送端因沒有收到確認應答,而認為數據沒有到達目的地,從而進行重發。
確認應答丟失的情況此外,也有可能是因為一些其他原因導致確認應答延遲到達,在發送端重發數據后才收到。此時,源發送方只要按照重發機制重發數據即可,但是對于接收方來說,它會反復收到相同的數據。而為了對上層應用提供可靠的傳輸,必須得放棄重復的數據報文段。
每一次傳輸數據時,TCP報文段的首部都會標明本報文段的起始序列號,以便對方確認。當接收方收到數據后,會去查詢數據報文段首部中的序列號和數據的長度,將自己下一步應該接收的起始序號作為確認應答回復給發送方。就這樣,通過序列號和確認應答號,TCP就可以實現可靠傳輸。
?序列號
序列號是按順序給TCP報文段的數據部分的每一個字節都標上號碼的編號。
序列號的初始值并非是從0開始的,而是在建立連接時又隨機數生成的。而后面的序號值計算則是對每一字節加1。
?確認號
TCP首部有一個確認字段,該字段實現TCP傳輸過程中的確認功能,該字段有兩個作用:
1)表示確認字段之前的序列號的字節流均已被成功接收。
2)表示期望收到的下一個報文段的序列號,即下一個報文段的數據部分的第一個字節的序號。
三、TCP超時重傳
超時重傳指的是在發送數據報文段后開始計時,到等待確認應答到來的那個時間間隔。如果超過這個時間間隔,仍未收到確認應答,發送端將進行數據重傳。這個等待時間稱為RTO(Retransmission Time-Out,超時重傳時間)。
還有一個時間叫RTT(Round Trip Time,報文段的往返時間),這個時間間隔是指數據報文段發出的時間戳與收到確認應答的時間戳的時間之差。
3.1 超時重傳時間的確定
超時重傳的概念很簡單,但是重傳時間的選擇卻是TCP最復雜的問題之一。因為網絡環境的不同,時間會有差異。如果把超時重傳的時間間隔設置得太短,就會引起很多報文段的不必要的重傳,使網絡負荷增大;如果把超時重傳設置得多長,則又使網絡的空閑時間增大,降低了傳輸效率。
那么,TCP的超時計時器的超時重傳時間究竟應設置為多大呢?
TCP采用了一種自適應的算法,它記錄一個報文段發出的時間戳,以及收到相應的確認應答的時間戳。這兩個時間戳之差就是該報文段的往返時間RTT。
算法步驟如下:
(1)計算一個加權平均往返時間RTTs(這也稱為平滑的往返時間,s 表示 smoothed。因為進行的是加權平均,因此得到的結果更加平滑)。
第1次測量得到的RTT樣本值,作為RTTs的初始值。從第2次開始,新的RTTs值使用如下的公式計算得到:
新的RTTs = (1 - α) × (舊的RTTs) + α × (新的RTT樣本)
<說明1> 新的RTT樣本需要重新測量得到。
<說明2> 系數 α 的取值范圍為[0, 1)。
<說明3> RFC 6298文檔推薦的 α 值為 1/8,即 0.125。
(2)基于計算得到的RTTs時間設置超時重傳時間RTO。顯然,超時計時器設置的超時重傳時間RTO應略大于上面得到的加權平均往返時間RTTs。
RFC 6298 建議使用下式計算RTO:
RTO = RTTs + 4 × RTTd
<說明> RTTd 是 RTT的偏差的加權平均值,它與 RTTs 和 新的 RTT 樣本之差有關。
RFC 6298 建議這樣計算RTTd。當第一次測量時,RTTd的值取為測量到的RTT樣本值的一半。在以后的測量中,則使用下式計算加權平均的RTTd:
新的RTTd = (1 - β) × (舊的RTTd) + β × |RTTs - 新的RTT樣本值|
<說明1> 系數 β 的取值范圍為[0, 1),它的推薦值為 1/4,即 0.25。
3.2 RTT樣本的測量
上面所說的RTT樣本的測量,實現起來相當復雜。示例如下:
如下圖所示,發送方發出一個報文段后,設定的重傳時間到了,還沒有收到確認應答。于是重傳報文段。經過一段時間后,收到了確認報文段。現在的問題是:如何判定此確認報文段是對先發送的報文段的確認還是對后來重傳的報文段的確認?由于重傳的報文段和原來的報文段完全一樣,因此源主機在收到確認后,就無法做出正確的判斷,而正確的判斷對確定加權平均RTTs的值關系很大。
?若收到的確認是對重傳報文段的確認,但卻被源主機當成是對原來的報文段的確認,則這樣計算出的RTTs和超時重傳時間RTO就會偏大。若后面再發送的報文段又是經過重傳后才收到確認報文段,則按照此方法得出的超時重傳時間RTO就會越來越長。
同樣的道理,若收到的確認是對原來的報文段的確認,但被當成是對重傳報文段的確認,則由此計算出的RTTs和RTO都會偏小。這就必然導致報文段過多地重傳。這樣就有可能使RTO的值越來越短。
根據以上所述,Karn 提出了一個算法:在計算加權平均 RTTs 時,只要報文段重傳了,就不采用其往返時間樣本。這樣得出的加權平均 RTTs 和 RTO就比較準確。
但是,這又可能引起新的問題。設想出現這樣的情況:報文段的時延突然增大了很多。因此,在原來得出的重傳時間內,不會收到確認報文段。于是就重傳報文段。但根據 Karn 算法,不考慮重傳的報文段的往返時間樣本。這樣,超時重傳時間就無法更新。
因此需要對 Karn 算法進行修正。方法是:報文段每重傳一次,就把超時重傳時間RTO增大一些。典型的做法是取新的重傳時間為舊的重傳時間的 2倍。當不再發生報文段的重傳時,再根據上面給出的公式計算超時重傳時間RTO的值。實踐證明,這種策略較為合理。
總之,Karn 算法能夠使TCP區分開有效的和無效的往返時間樣本,從而改進了往返時間RTT的估測,使超時重傳時間的計算更加合理。
當然,數據不會被無限、反復地重發。當達到一定重發次數后,如果仍然沒有任何確認應答返回,就會判斷為網絡或對端主機發生了異常,TCP模塊就會強制關閉連接,并且通知上層應用通信異常強行終止。
3.3 Linux系統中與超時重傳相關的內核參數
?Linux系統有兩個重要的內核參數與TCP超時重傳相關:/proc/sys/net/ipv4/tcp_retries1 和 /proc/sys/net/ipv4/tcp_retries2。
?查看這兩個內核參數的默認值:
# sysctl -a |grep -e tcp_retries net.ipv4.tcp_retries1 = 3 net.ipv4.tcp_retries2 = 15前者指定在底層IP接管之前TCP最少執行的重傳次數。默認值是3。
后者指定連接放棄前TCP最多可以執行的重傳次數。默認值是15。(一般對應13~30min)。
四、選擇確認SACK
問題:如果接收方收到的報文段無差錯,只是未按序到達,中間還缺了一些序號的字節數據,那么能否設法只重傳缺少的數據而不重傳已經正確到達接收方的數據呢?
SACK(Selective ACKonwledgement,選擇確認)
答案是可以的,選擇確認就是一種可行的處理方法。下面我們用一個例子來說明選擇確認的工作原理。
當TCP的接收方在接收對方發送過來的數據字節流的序號不連續時,結果就形成了一些不連續的字節塊,如下圖所示。
可以看出,序號 1 ~ 1000 已收到了,但序號 1001 ~ 1500 沒有收到。接下來的字節流又收到了,可是又缺少了 3000 ~ 3500。再后面從 4501 起又沒有收到。也就是說,接收方收到了和前面的字節流不連續的兩個字節塊。如果這些字節塊都在接收窗口之內,那么接收方就先收下這些數據,但要把這些信息準確地告訴發送方,使發送方不要再重復發送這些已收到的數據。
從上圖可以看出,和前后字節不連續的每一個字節塊都有兩個邊界:左邊界和右邊界。因此在圖中用四個指針標記這些邊界。請注意,第一個字節塊的左邊界 L1=1501,而右邊界 R1 = 3001 而不是 3000。這就是說,左邊界指出字節塊的第一個字節的序號,但右邊界減 1 才是字節塊的最后一個字節序號。同理,第二個字節塊的左邊界 L2=3501,而右邊界 R2 = 4501。
我們知道,TCP報文段的首部沒有哪個字段能夠提供上述這些字節塊的邊界信息。RFC 2018 規定,如果要使用選擇確認 SACK 功能,那么在建立TCP連接時,就要在TCP 首部的選項字段(即Options)中加上“允許SACK”的選項,而通信雙方必須都事先約定好。如果使用選擇確認,那么原來首部中的“確認號”字段的用法仍然不變。只是以后在TCP報文段的首部中的“選項”字段中都增加了SACK選項,以便報告收到不連續的字節塊的邊界。
由于TCP首部的“選項”可選字段的長度最多只有 40 字節,而指明一個邊界就要用掉4字節(因為序號有32位,需要使用4個字節表示),因此在選項中最多只能指明4個字節塊的邊界信息。這是因為4個字節塊有8個邊界,因為需要用 8 * 4 =32個字節來描述。另外還需要兩個字節,一個字節用來指明是 SACK 選項,另一個字節是指明這個SACK選項要占用多少字節。如果要報告五個字節塊的邊界信息,那么至少需要 42 個字節。這就超過了選項字段的40字節的上限。具體是如何規定的,可以參考 RFC 2018。示例表示如下:
?kind = 4:占1字節,表示選項類別為 SACK。length:占1字節,表示SACK選項總共占用的字節數。該長度包括了kind字段和length字段占據的2個字節。
kind = 5:占1字節,表示這是SACK實際工作的選項。legnth:N 表示字節塊數量;每個字節塊有兩個邊界信息,因此需要8字節;+2 表示加上kind和length這兩個字節。
在 Linux系統中,有一個內核參數 /proc/sys/net/ipv4/tcp_sack 用來表示SACK選項是否啟用或是關閉。默認值是1,即啟用SACK選項。
查看該內核參數的命令如下:
# sysctl -a | grep tcp_sack net.ipv4.tcp_sack = 1如果需要關閉SACK選項,可以使用如下命令修改:
sysctl -w net.ipv4.tcp_sack = 0或者在 /etc/sysctl.conf 配置文件中永久修改,添加內容如下:
net.ipv4.tcp_sack = 0參考
《圖解TCP_IP(第5版)》第6章 - TCP與UDP
《計算機網絡(第7版-謝希仁)》第5章 - 運輸層
《Linux高性能服務器編程》第3章 - TCP協議詳解
總結
以上是生活随笔為你收集整理的TCP协议-TCP超时重传机制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 11年的macbook还能用吗_致我20
- 下一篇: python基础教程十进制_Python