Lwip 保活機制 2

文章轉自老衲五木的新浪博客

這節討論TCP的定時處理函數。在前面的討論中,我們看到了與TCP的各種定時器,包括重傳定時器、持續定時器和保活定時器,此外TCP中還有幾個定時器我們還未涉及。這裏總的來看看TCP中的各個定時器。TCP爲每條連接總共建立了七個定時器,依次爲:

1 )“連接建立(connection establishment)”定時器在發送SYN報文段建立一條新連接時啓動。如果在75秒內沒有收到響應,連接建立將中止。

2 )“重傳(retransmission)”定時器在TCP發送某個數據段時設定。如果該定時器超時而對端的確認還未到達,TCP將重傳該數據段。重傳定時器的值 (即TCP等待對端確認的時間)是動態計算的,與RTT的估計值密切相關,且還取決於該報文段已被重傳的次數。

3 )“延遲ACK(delayed ACK)”定時器在TCP收到必須被確認但無需馬上發出確認的數據時設定。如果在200ms內,有數據要在該連接上發送,延遲的ACK響應就可隨着數據一起發送回對端,稱爲捎帶確認。如果200ms後,該確認未能被捎帶出去,則定時器超時,此時需要發送一個立即確認。

4 )“持續 (persist)”定時器在連接對端通告接收窗口爲0,阻止TCP繼續發送數據時設定。由於連接對端發送的窗口通告不可靠(只有數據纔會被確認,ACK不會被確認),允許TCP繼續發送數據的後續窗口更新有可能丟失。因此,如果TCP有數據要發送,但對端通告接收窗口爲0,則持續定時器啓動,超時後向對端發送 1字節的數據,判定對端接收窗口是否已打開。

5 )“保活(keep alive)”定時器在TCP控制塊的so_options 字段設置了SOF_KEEPALIVE選項時生效。如果連接的連續空閒時間超過2小時,則保活定時器超時,此時應向對端發送連接探測報文段,強迫對端響應。如果收到了期待的響應, TCP可確定對端主機工作正常,在該連接再次空閒超過 2小時之前,TCP不會再進行保活測試。如果收到的是RST復位響應, TCP可確定對端主機已重啓。如果連續若干次保活測試都未收到響應, TCP就假定對端主機已崩潰,但它無法區分是主機故障還是連接故障。

6) FIN_WAIT_2定時器,當某個連接從FIN_WAIT_1狀態變遷到FIN_WAIT_2狀態並且不能再接收任何新數據時,FIN_WAIT_2定時器啓動,設爲10分鐘。定時器超時後,重新設爲75秒,第二次超時後連接被關閉。加入這個定時器的目的是爲了避免如果對端一直不發送 FIN,某個連接會永遠滯留在FIN _ WAIT_ 2狀態(假設TCP不選用半打開功能)。

7) TIME_WAIT定時器,一般也稱爲2MSL定時器。2MSL指兩倍的MSL,即最大報文段生存時間。當連接轉移到TIME_WAIT狀態,即連接主動關閉時,定時器啓動。狀態轉換圖那一節中已經詳細說明了需要2MSL等待狀態的原因。連接進入TIME_WAIT狀態時,定時器設定爲1分鐘,超時後,TCP控制塊被刪除,端口號可重新使用。

前面的7個定時器中,重傳定時器使用rtime字段計數,持續定時器使用persist_cnt字段計數,其他五個定時器除延遲ACK定時器外都使用rtime字段計數,從上面的描述中可以看出,這四個定時器是TCP處於四種不同的狀態時使用的,因此四個定時器完全獨立的使用rtime字段而不會互相影響。延遲ACK定時器使用系統250ms週期性定時來完成的。

LWIP中包括兩個定時器函數:一個函數每 250 ms調用一次(快速定時器);另一個函數每500ms調用一次(慢速定時器)。延遲ACK定時器與其他6個定時器有所不同:如果某個連接上設定了延遲ACK定時器,那麼下一次250ms定時器超時後,延遲的ACK必須被髮送(實際的ACK延遲時間在0~250ms之間)。其他的6個定時器每500 ms增加1,當計數值超過某些閾值時,則相應的動作被觸發。

先看簡單的快速定時器處理函數:

void tcp_fasttmr(void)

{

struct tcp_pcb *pcb;

for(pcb = tcp_active_pcbs; pcb != NULL; pcb = pcb->next) { //遍歷整個active鏈表

if (pcb->refused_data != NULL) { // 如果某個控制塊還沒有數據未接收

err_t err;

TCP_EVENT_RECV(pcb, pcb->refused_data, ERR_OK, err); //調用上層函數接收數據

if (err == ERR_OK) {

pcb->refused_data = NULL; // 成功接收則復位指針

}

}

if (pcb->flags & TF_ACK_DELAY) {//若控制塊開啓了延遲ACK定時器

tcp_ack_now(pcb);    // 發送一個立即確認

pcb->flags &= ~(TF_ACK_DELAY | TF_ACK_NOW); //清除標誌位

}

}// if

}// for

從上面可以看出,快速定時器處理函數主要做了兩方面的工作,一是向上層遞交上層一直未接收的數據,二是發送該連接上的立即確認數據段。與快速定時器相比,慢速定時器處理函數就顯得相當的龐大了,這裏我們只列出和各個定時器相關的部分,而對其他部分採用僞代碼的方式加以描述,當然,這裏所謂的其他部分就是我們在前面已經講解過的部分了。

void tcp_slowtmr(void)

{

++tcp_ticks;

pcb = tcp_active_pcbs;

while (pcb != NULL) {

pcb_remove = 0;

該控制塊的零窗口探查處理;

該控制塊的超時重傳處理;

if (pcb->state == FIN_WAIT_2) {  // FIN_WAIT_2定時器超時

if ((u32_t)(tcp_ticks - pcb->tmr) >

TCP_FIN_WAIT_TIMEOUT / TCP_SLOW_INTERVAL) {

++pcb_remove;

}

}

該控制塊的超時保活處理;

#if TCP_QUEUE_OOSEQ

if (pcb->ooseq != NULL &&  // 丟棄在ooseq隊列中長時間未被處理的數據

(u32_t)tcp_ticks - pcb->tmr >= pcb->rto * TCP_OOSEQ_TIMEOUT) {

tcp_segs_free(pcb->ooseq);

pcb->ooseq = NULL;

}

#endif

if (pcb->state == SYN_RCVD) { // SYN_RCVD狀態超時

if ((u32_t)(tcp_ticks - pcb->tmr) >

TCP_SYN_RCVD_TIMEOUT / TCP_SLOW_INTERVAL) {

++pcb_remove;

}

}

if (pcb->state == LAST_ACK) { // LAST_ACK定時器超時

if ((u32_t)(tcp_ticks - pcb->tmr) > 2 * TCP_MSL / TCP_SLOW_INTERVAL) {

++pcb_remove;

}

}

若有超時則刪除控制塊;

無超時則週期性的外發數據包(poll);

取得active鏈表上的下一個控制塊;

}// while

pcb = tcp_tw_pcbs;  //處理TIME-WAIT鏈表

while (pcb != NULL) {

pcb_remove = 0;   // TIME-WAIT定時器超時

if ((u32_t)(tcp_ticks - pcb->tmr) > 2 * TCP_MSL / TCP_SLOW_INTERVAL) {

++pcb_remove;

}

若超時則刪除控制塊;

取得TIME-WAIT鏈表上的下一個控制塊;

}// while

}//函數尾

可以看出各個定時器的實現都是利用全局變量tcp_ticks與tmr字段的差值來實現的。當TCP進入某個狀態時,就會將相應tmr字段設置爲當前的全局時鐘tcp_ticks的值,所以上面的差值可以有效表示出TCP處於某個狀態的時間。各個定時器超時後的處理也很相似,即將變量pcb_remove加1,pcb_remove變量是超時處理中最核心的變量了,當針對某個PCB控制塊作完超時判斷之後,函數通過判斷pcb_remove的值來處理TCP控制塊,當pcb_remove值大於1時,則表示該控制塊上有超時事件發生,該控制塊或被刪除或被掛起。注意僞代碼中的重傳定時器超時並不會影響pcb_remove的值。如果細心,你還可以看到,上面的代碼多了兩個超時事件,即SYN_RCVD狀態超時和ooseq隊列數據超時,當然,這兩個超時事件並不影響協議棧功能的實現。

最後來看看系統爲每個超時時間設置的超時時間,從上面的代碼中可以看出,它們是在各個宏定義裏面實現的。

#define TCP_TMR_INTERVAL       250   //250ms

#define TCP_FAST_INTERVAL      TCP_TMR_INTERVAL  // 快速定時器

#define TCP_SLOW_INTERVAL      (2*TCP_TMR_INTERVAL) // 慢速定時器

 

#define TCP_FIN_WAIT_TIMEOUT   20000  // FIN_WAIT_2狀態超時時間

#define TCP_SYN_RCVD_TIMEOUT  20000  // SYN_RCVD狀態超時時間

 

#define TCP_OOSEQ_TIMEOUT   6U   //ooseq隊列中數據等待的rto週期數

#define TCP_MSL  60000U   // MSL

到這裏,我們的PCB控制塊中的各個字段基本都已涉及到了,除了polltmr和pollinterval。這兩個字段用於週期性調用函數tcp_output,用以發送控制塊上殘留的未發送數據段。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章