如果应用进程调用close系统调用关闭socket,但此时socket与对端的通信尚未完成,则这个socket被称为“孤儿socket”。如果孤儿socekt进入FIN_WAIT2状态(或socket进入FIN_WAIT2状态后再成为孤儿socket),会等待对端发送FIN以彻底结束连接。但如果对端一直不发送FIN则这个孤儿socekt会一直存在,从而一直占用系统资源。为了解决这个问题,需要在孤儿socket进入FIN_WAIT2状态时设置FIN_WAIT2定时器,如果定时器超时时仍然没有收到FIN,则关闭socket。
开启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)。
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。