正常情況下主動關閉連接的一端在連接正常終止後,會進入TIME_WAIT狀態,存在這個狀態有以下兩個原因(參考《Unix網絡編程》):
《UNIX網絡編程.卷2:進程間通信(第2版)》[PDF]下載 http://www.linuxidc.com/Linux/2013-01/77936.htm
1、保證TCP連接關閉的可靠性。如果最終發送的ACK丟失,被動關閉的一端會重傳最終的FIN包,如果執行主動關閉的一端沒有維護這個連接的狀態信息,會發送RST包響應,導致連接不正常關閉。
2、允許老的重復分組在網絡中消逝。假設在一個連接關閉後,發起建立連接的一端(客戶端)立即重用原來的端口、IP地址和服務端建立新的連接。老的連接上的分組可能在新的連接建立後到達服務端,TCP必須防止來自某個連接的老的重復分組在連接終止後再現,從而被誤解為同一個連接的化身。要實現這種功能,TCP不能給處於TIME_WAIT狀態的連接啟動新的連接。TIME_WAIT的持續時間是2MSL,保證在建立新的連接之前老的重復分組在網絡中消逝。這個規則有一個例外:如果到達的SYN的序列號大於前一個連接的結束序列號,源自Berkeley的實現將給當前處於TIME_WAIT狀態的連接啟動新的化身。
最初在看《Unix網絡編程》 的時候看到這個狀態,但是在項目中發現對這個狀態的理解有誤,特別是第二個理由。原本認為在TIME_WAIT狀態下肯定不會再使用相同的五元組(協議類型,源目的IP、源目的端口號)建立一個新的連接,看書還是不認真啊!為了加深理解,決定結合內核代碼,好好來看下內核在TIME_WAIT狀態下的處理。其實TIME_WAIT存在的第二個原因的解釋更多的是從被動關閉一方的角度來說明的。如果是執行主動關閉的是客戶端,客戶端戶進入TIME_WAIT狀態,假設客戶端重用端口號來和服務器建立連接,內核會不會允許客戶端來建立連接?內核如何來處理這種情況?書本中不會對這些點講的那麼詳細,要從內核源碼中來找答案。
我們先來看服務器段進入TIME_WAIT後內核的處理,即服務器主動關閉連接。TCP層的接收函數是tcp_v4_rcv(),和TIME_WAIT狀態相關的主要代碼如下所示:
int tcp_v4_rcv(struct sk_buff *skb)
{
......
sk = __inet_lookup_skb(&tcp_hashinfo, skb, th->source, th->dest);
if (!sk)
goto no_tcp_socket;
process:
if (sk->sk_state == TCP_TIME_WAIT)
goto do_time_wait;
......
discard_it:
/* Discard frame. */
kfree_skb(skb);
return 0;
......
do_time_wait:
......
switch (tcp_timewait_state_process(inet_twsk(sk), skb, th)) {
case TCP_TW_SYN: {
struct sock *sk2 = inet_lookup_listener(dev_net(skb->dev),
&tcp_hashinfo,
iph->daddr, th->dest,
inet_iif(skb));
if (sk2) {
inet_twsk_deschedule(inet_twsk(sk), &tcp_death_row);
inet_twsk_put(inet_twsk(sk));
sk = sk2;
goto process;
}
/* Fall through to ACK */
}
case TCP_TW_ACK:
tcp_v4_timewait_ack(sk, skb);
break;
case TCP_TW_RST:
goto no_tcp_socket;
case TCP_TW_SUCCESS:;
}
goto discard_it;
}