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万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章