TCPIP-------超時與重傳
TCP是可靠的傳輸層協議,但這並不意味著傳送端傳送的資料一定可以到達接收端,因為傳輸過程中遇到的情況是不可控的,在TCP兩端互動過程中,資料和確認的報文都有可能丟失,因此在傳送端引入超時和重傳機制可以很好的解決報文丟失問題。其基本原理:TCP通過在傳送端為每個傳送出去的報文設定一個超時定時器,當定時器溢位時還沒有收到確認報文,它就重傳該資料。對任何TCP協議實現而言,怎樣決定超時間隔和如何確定重傳的方式是提高TCP效能的關鍵,如何設定這個定時器的時間(RTO),從而保證對網路資源最小的浪費。因為若RTO太小,可能有些報文只是遇到擁堵或網路不好延遲較大而已,這樣就會造成不必要的重傳。太大的話,使傳送端需要等待過長的時間才能發現資料丟失,影響網路傳輸效率。由於不同的網路情況不一樣,不可能設定一樣的RTO,實際中RTO是根據網路中的RTT(傳輸往返時間)來自適應調整的,而這些都與往返時間估計密切相關。往返時間(RTT)代表了某位元組資料傳送出去到對應確認返回期間的時間間隔。
TCP超時與重傳中最重要的部分就是對一個給定連線的往返時間(RTT)的測量。由於路由器和網路流量均會變化,因此我們認為這個時間可能經常會發生變化,TCP應該跟蹤這些變化並相應地改變其超時時間。TCP必須測量在傳送一個帶有特別序號的位元組和接收到包含該位元組的確認之間的RTT,同時應注意,發出去的資料段與返回的確認之間並沒有一一對應的關係。在往返時間變化起伏很大時,基於均值和方差來計算RTO能提供更好的響應,下面這個演算法是Jacobson提出的,它說明如何平滑RTT,如何使用RTT來設定RTO,目前廣泛應用到了TCP協議中。
Err = M-A (1)
A = A+ g* Err (2)
D =D + h * (|Err|-D) (3)
RTO = A+4*D (4)
其中M表示某次測量的RTT的值,A表示測得的RTT的平均值,A值的更新如第二式所示,D值為RTT的估計的方差,其更新如第三式所示。二式和三式中g和h都為常數,一般g取1/8,h取1/4。這樣取值是為了便於計算,從後面可以看出,通過簡單的移位操作就可以完成上述計算了。RTO的計算如第四式所示,初始時,RTO取值為6,即3s,A值為0,D值為6。
通過圖來了解超時重傳機制:
從圖可以知道,傳送方連續傳送3個資料包,其中第二個丟失,沒有被接收到,因此不會返回對應的ACK,每傳送一個資料包,就啟動一個定時器,當第二個包的定時器溢位了還沒有收到ack,這時就進行重傳。
下面分析一下LWIP協議棧中超時重傳機制的實現方法:
在LWIP協議棧中TCP控制塊(tcp_pcb)內部與超時重傳相關欄位有rtime、rttest、rtseq、sa、sv、rto、nrtx,rtime表示重傳定時器,它的值每500ms被TCPIP核心定時器加1,當該值超過RTO時,報文段會進行重傳,rttest用於記錄報文段傳送時間,用於計算報文段在兩天主機之間的往返時間,tcp根據往返時間動態設定報文段的超時時間RTO,rtseq表示正在進行往返時間估算的報文段,sa、sv用於計算RTO、rto超時重傳時間間隔,nrtx表示報文段被重傳的次數,這個值與RTO密切相關。
先看看超時重傳在LWIP協議棧中是怎麼實現的,在來看RTT估計。tcp_output從unsent佇列上取下第一個資料段,並呼叫函式tcp_output_segment將資料段傳送出去,傳送完畢後,tcp_output將該資料段掛接到unacked佇列上,至於掛在unacked佇列上的什麼位置,後面再講。tcp_output_segment負責將資料段傳送出去,傳送出去後它要做的工作如下面的程式碼所示:
static void tcp_output_segment(...)
{
if(pcb->rtime == -1)
pcb->rtime = 0;
if (pcb->rttest == 0)
{
pcb->rttest = tcp_ticks;
pcb->rtseq = ntohl(seg->tcphdr->seqno);
}
}
rtime用於重傳定時器的計數,當其值為-1時表示計數器未被使能;當值為非0時表示計數器使能,在這種情況下,rtime的值每500ms被核心加1,當rtime超過rto的值時,在unacked佇列上的所有資料段將被重傳。lwip簡化了重傳設計,並沒有為每個報文段都設定一個獨立的定時器,而是未被確認的報文段共用一個。rttest用於記錄報文段傳送時間,,當rttest為0時表示RTT估計未啟動,報文段傳送之後,rttest的值為當前系統時間tcp_ticks,並用rtseq記錄當前RTT估計的報文段編號。當接收到對方確認的rtseq編號後,就可以過根據當前時間及傳送時間計出RTT。
TCP慢定時器(tcp_slowtmr)每500ms被系統呼叫一次,重傳定時器啟動則加1,當rttime大於RTO時,則重傳未被確認的報文段,程式碼如下:
void tcp_slowtmr(void)
{
......
if(pcb->rtime >= 0)
{
//重傳定時器啟動,計數值加1
++pcb->rtime;
}
......
//有資料未被確認且超時發生
if(pcb->unacked != NULL && pcb->rtime >= pcb->rto)
{
if (pcb->state != SYN_SENT)
{
//動態設定RTO,與重傳次數和RTT的值有關,進行避讓處理
pcb->rto = ((pcb->sa >> 3) + pcb->sv) << tcp_backoff[pcb->nrtx];
}
//清空重傳定時器
pcb->rtime = 0;
......
//重傳報文段
tcp_rexmit_rto(pcb);
}
}
}
......
}
tcp_rexmit_rto函式實現重傳的機制很簡單,它將unacked連結串列上的所有資料段插入到unsent佇列的前端,並將控制塊重傳次數字段nrtx加1,最後呼叫tcp_output重發資料包。LWIP通過視窗的控制以及收到確認號後遍歷unsent佇列這兩種方式使得unacked連結串列上的整個報文段被重傳可能性降到了最小。
在資料接收上,tcp_receive函式提取收到的資料段中的ackno,並用該ackno來處理unacked佇列,即當該ackno確認了某個資料段中的所有資料,則將該資料段從unacked佇列中移除,並釋放資料段佔用的空間。同時,函式要檢查unacked佇列,如果unacked佇列中沒有被需要確認的資料段了,此時需要停止重傳定時器,否則要復位重傳定時器。很簡單,用下面的程式碼:
static void tcp_receive(struct tcp_pcb *pcb)
{
......
if(pcb->unacked == NULL)
pcb->rtime = -1;
else
pcb->rtime = 0;
......
}
根據上面講的RTT演算法,現在我們來進行RTT估計講解,我們將上面的四個表示式做簡單的變化,就得到了LWIP中計算RTT的表示式:
Err = M-A
A = A+ Err/8= A+(M-A)/8 ——> 8A = 8A + M-A
D =D + (|Err |-D)/4= D + (|M-A |-D)/4 ——> 4D = 4D + (|M-A |-D)
RTO = A+4D
令sa = 8A,sv = 4D,這就是TCP控制塊中的兩個欄位。帶入上面變換後的表示式,得到:
sa = sa + M - sa>>3
sv = sv + (|M - sa>>3 |-sv>>2)
RTO = sa>>3 + sv
我們就得到了最關心的RTO值,M表示某次測量的RTT的值,在LWIP中它就是系統當前tcp_ticks值減去資料包被髮送出去時的tcp_ticks值,tcp_ticks也是在核心的500ms週期性中斷處理中被加1。來看看原始碼是怎樣進行RTT估算的,這也是在函式tcp_receive中進行的。
static void tcp_receive(struct tcp_pcb *pcb)
{
......
// 有RTT正在進行且該資料段被確認
if (pcb->rttest && TCP_SEQ_LT(pcb->rtseq, ackno))
{
m = (s16_t)(tcp_ticks - pcb->rttest); //計算M值
m = m - (pcb->sa >> 3); // M - sa>>3
pcb->sa += m; //更新sa
if (m < 0)
{
m = -m; // |M - sa>>3|
}
m = m - (pcb->sv >> 2); // (|M - sa>>3 |-sv>>2)
pcb->sv += m; // 更新sv
pcb->rto = (pcb->sa >> 3) + pcb->sv; //計算rto
pcb->rttest = 0; // 停止RTT估計
}
......
}
當以某個RTO為超時值傳送資料包後,在RTO時間後未收到對該資料段的確認,則該資料包被重發,若重發後仍收不到關於該資料包的確認,這種情況下,則接下來的重發包必須按照2的指數避讓,即將RTO值設定為前一次的2倍,當重發超過一定次數後,不再對資料包進行重發。這是在500ms定時處理函式tcp_slowtmr中完成的,看看原始碼(見上面慢定時器重傳程式碼),注意:一是對處於SYN_SENT狀態的控制塊不進行超時時間的避讓,可能是由於考慮到SYN_SENT狀態一般傳送出去的是SYN握手包,二是避讓使用一個陣列tcp_backoff通過移位的方式實現,tcp_backoff定義如下:
const u8_t tcp_backoff[13] = { 1, 2, 3, 4, 5, 6, 7, 7, 7, 7, 7, 7, 7};
在這裡面,當重傳次數多於6次時,RTO值將不再進行避讓。最後一點是函式tcp_rexmit_rto,該函式真正完成資料包的重傳工作:
void tcp_rexmit_rto(struct tcp_pcb *pcb)
{
......
// 將unacked佇列全部放到
for (seg = pcb->unacked; seg->next != NULL; seg = seg->next);
// 將重傳報文段加入到unsent佇列前端
seg->next = pcb->unsent;
pcb->unsent = pcb->unacked;
pcb->unacked = NULL;
// 下一個要傳送的資料編號指向佇列
pcb->snd_nxt = ntohl(pcb->unsent->tcphdr->seqno);
// 重傳次數加1
++pcb->nrtx;
// 重發資料包期間不進行RTT估計
pcb->rttest = 0;
// 傳送一個資料包
tcp_output(pcb);
......
}
從這個程式碼和上面的避讓演算法裡你可以很清楚的看到欄位nrtx的作用了,它是多次重傳時設定rto值的重要變數。另外注意,在重傳期間不應該進行RTT估計,因為這種情況下的估計值往往是不準確的。這就是傳說中的Karn演算法,Karn演算法認為由於某報文即將重傳,則對該報文的計時也就失去了意義。即使收到了ACK,也無法區分它是對第一次報文,還是對第二次報文的確認。
文件內容參考了TCPIP詳解、老衲五木(朱升林)的微博。
相關文章
- NIO中如何實現超時重傳?
- Curator 重連策略與超時
- [轉帖]【tcp】關於tcp 超時重傳次數TCP
- Spring Cloud OpenFeign 超時與重試SpringCloud
- axios超時重發iOS
- Spring Cloud Config Client 超時與重試SpringCloudclient
- 計算機網路之傳輸層TCP與UDP對比、流量控制、擁塞控制、超時重傳時間的選擇、可靠傳輸計算機網路TCPUDP
- TCP 可靠傳輸的實現-02超時重傳時間的選擇/03選擇確認 SACKTCP
- zuul超時及重試配置Zuul
- JMicro微服務之超時&重試微服務
- 上傳檔案超時問題
- 超時重試思考-非冪等請求
- Java傳送郵件必帶超時時間配置Java
- nginx限制上傳大小和超時時間設定說明/php限制上傳大小NginxPHP
- ??TCP協議:超時重傳、流量控制、keep-alive和埠號,你真的瞭解嗎?TCP協議Keep-Alive
- 你瞭解微服務的超時傳遞嗎?微服務
- HTTP呼叫超時咋辦?重複請求又如何?HTTP
- GOLANG使用Context實現傳值、超時和取消GolangContext
- 節點重配置後IPC傳送超時問題
- 檔案上傳與下載(免去重學煩惱)
- 淺談TCP(1):狀態機與重傳機制TCP
- 超融合架構與傳統IT架構的區別架構
- 使用SpringBoot實現微服務超時重試模式 - VinsguruSpring Boot微服務模式
- 重寫ajax實現session超時跳轉到登陸頁面Session
- 詳說TCP重傳問題的排查思路與實踐TCP
- context裡的超時時間是怎麼在微服務之間傳遞的Context微服務
- 定時任務與feign超時的糾葛,該咋優化?優化
- MSSQL資料庫超時的原因與解決方法SQL資料庫
- go-zero的全鏈路追蹤與超時Go
- 使用Envoy 作Sidecar Proxy的微服務模式-2.超時和重試IDE微服務模式
- [譯] 分散式系統如何從故障中恢復?— 重試、超時和退避分散式
- 怎樣實現一個非阻塞的超時重試任務佇列佇列
- 超文字傳輸協議協議
- Element頁面內多個上傳元件 超時使用abort取消請求元件
- go get 超時Go
- 一站式學習Wireshark(四):網路效能排查之TCP重傳與重複ACKTCP
- nofollow權重說明 nofollow是否傳遞權重?
- 重寫equals()方法時,需要同時重寫hashCode()方法