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。