9.9 FIN_WAIT2定時器

9.9.1 Why

        如果應用進程調用close系統調用關閉socket,但此時socket與對端的通信尚未完成,則這個socket被稱爲“孤兒socket”。如果孤兒socekt進入FIN_WAIT2狀態(或socket進入FIN_WAIT2狀態後再成爲孤兒socket),會等待對端發送FIN以徹底結束連接。但如果對端一直不發送FIN則這個孤兒socekt會一直存在,從而一直佔用系統資源。爲了解決這個問題,需要在孤兒socket進入FIN_WAIT2狀態時設置FIN_WAIT2定時器,如果定時器超時時仍然沒有收到FIN,則關閉socket。

9.9.2 When

        開啓FIN_WAIT2定時器的函數也是inet_csk_reset_keepalive_timer設置FIN_WAIT2定時器的時機主要有兩個:
(1)應用進程調用close系統調用而socekt正處於TCP_FIN_WAIT2狀態時:

 2059 void tcp_close(struct sock *sk, long timeout)
2060 {
...
2183     if (sk->sk_state == TCP_FIN_WAIT2) {
2184         struct tcp_sock *tp = tcp_sk(sk);
2185         if (tp->linger2 < 0) {
...
2190         } else {
2191             const int tmo = tcp_fin_time(sk);
2192
2193             if (tmo > TCP_TIMEWAIT_LEN) {    //等待對端發FIN的時間長度大於TIME_WAIT的時間
2194                 inet_csk_reset_keepalive_timer(sk,
2195                         tmo - TCP_TIMEWAIT_LEN);
2196             } else {
...
        然後socket就會成爲孤兒socket。不過FIN_WAIT2定時器會一直“看護”它。

(2)孤兒socket進入FIN_WAIT2狀態時

 5600 int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,
5601               const struct tcphdr *th, unsigned int len)
5602 {
...
5751         case TCP_FIN_WAIT1:
...
5780                 if (!sock_flag(sk, SOCK_DEAD))
5781                     /* Wake up lingering close() */
5782                     sk->sk_state_change(sk);
5783                 else {
5784                     int tmo;
5785
5786                     if (tp->linger2 < 0 ||
5787                         (TCP_SKB_CB(skb)->end_seq != TCP_SKB_CB(skb)->seq &&    //ACK中有數據或FIN標記位
5788                          after(TCP_SKB_CB(skb)->end_seq - th->fin, tp->rcv_nxt))) {    //ACK中有數據,孤兒socket不需要數據,馬上關掉TCP
5789                         tcp_done(sk);
5790                         NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPABORTONDATA);
5791                         return 1;
5792                     }
5793
5794                     tmo = tcp_fin_time(sk);
5795                     if (tmo > TCP_TIMEWAIT_LEN) {
5796                         inet_csk_reset_keepalive_timer(sk, tmo - TCP_TIMEWAIT_LEN);
...
        可見FIN_WAIT2定時器功能開啓的必要條件是應用進程沒有使用TCP_LINGER2 socket選項將tp->linger2的值設爲小於0。

        與Keepalive定時器一樣,FIN_WAIT2定時器只能用SO_KEEPALIVE socket選項拆除。

        FIN_WAIT2定時器的超時時間爲tcp_fin_time減去TCP_TIMEWAIT_LEN的值:

1132 static inline int tcp_fin_time(const struct sock *sk)
1133 {
1134     int fin_timeout = tcp_sk(sk)->linger2 ? : sysctl_tcp_fin_timeout;
1135     const int rto = inet_csk(sk)->icsk_rto;
1136 
1137     if (fin_timeout < (rto << 2) - (rto >> 1))
1138         fin_timeout = (rto << 2) - (rto >> 1);
1139         
1140     return fin_timeout;
1141 }
        其中tcp_sk(sk)->linger2由TCP_LINGER2 socket選項賦值,sysctl_tcp_fin_timeout的值由net.ipv4.tcp_fin_timeout內核選項決定,默認與TCP_TIMEWAIT_LEN一樣(60s)。

9.9.3 What

         FIN_WAIT2定時器的超時tcp_keepalive_timer

 558 static void tcp_keepalive_timer (unsigned long data)
559 {
560     struct sock *sk = (struct sock *) data;
561     struct inet_connection_sock *icsk = inet_csk(sk);
562     struct tcp_sock *tp = tcp_sk(sk);
563     u32 elapsed;
...
578     if (sk->sk_state == TCP_FIN_WAIT2 && sock_flag(sk, SOCK_DEAD)) {    //socket爲孤兒socket且處於FIN_WAIT2狀態
579         if (tp->linger2 >= 0) {
580             const int tmo = tcp_fin_time(sk) - TCP_TIMEWAIT_LEN;
581
582             if (tmo > 0) {
583                 tcp_time_wait(sk, TCP_FIN_WAIT2, tmo); //進入TIME_WAIT狀態
584                 goto out;
585             }
586         }
587         tcp_send_active_reset(sk, GFP_ATOMIC);//發送RST
588         goto death;
589     }
...
635 death:
636     tcp_done(sk);    //關閉本地TCP
637
638 out:
639     bh_unlock_sock(sk);
640     sock_put(sk);
641 }
        FIN_WAIT2定時器的超時動作有兩種:進入TIME_WAIT狀態或發送RST報文然後關閉TCP。

發佈了79 篇原創文章 · 獲贊 46 · 訪問量 22萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章