TCP连接的终止----主动关闭

在正常情况下,TCP连接的关闭需要连接的两端进行四次分组交换,具体过程是:执行主动关闭的一端(A端)会首先发送FIN包给对端(B端),B端收到FIN包后会发送一个ACK包给A段;B段执行关闭操作,发送FIN给A端,A端发送一个ACK给B端,连接彻底关闭。分组交换和状态迁移如下图所示:

  

  通常情况下,只有执行主动关闭的一端会进入TIME_WAIT状态,还有一种情况会导致连接的两端都进入TIME_WAIT状态。当TCP的两端同时给对端发送FIN包,两端的TCP状态均从ESTABLISHED变为FIN_WAIT_1,在FIN_WAIT_1状态下接收到FIN包后,状态会由FIN_WAIT_1迁移到CLOSING,并发送最后的ACK。收到最后的ACK后,状态变迁为TIME_WAIT状态,如下图所示:


  上述的两种情况只是TCP连接关闭情况的一部分,在其他情况下,内核可能给对端发送的不是FIN包,而是RST包。在这种情况下,有可能是应用层序的问题,也可能是内核资源短缺造成的,如何来查找和确定这些异常情况的原因,需要对内核的实现有一个比较全面的了解。

  在应用层要关闭一个连接非常简单,只需要指定要关闭的连接对应的套接字即可。内核中处理TCP连接关闭的系统调用是sys_close(),该函数做的事情不多,主要的关闭操作是由tcp_close()函数来完成的。tcp_close()函数的定义中比sys_close()多了一个timeout参数,不难看出这个timeout肯定是一个超时时间,第一次看到这个参数也是比较疑惑。在调用close()的时候并没有指定超时参数,那这个timeout的值怎么来的呢?这个值是在tcp_close()的上层函数inet_release()中计算出来的,其计算方式如下所示:

[cpp]  view plain copy
  1. int inet_release(struct socket *sock)  
  2. {  
  3.          ......  
  4.         timeout = 0;  
  5.         if (sock_flag(sk, SOCK_LINGER) &&  
  6.             !(current->flags & PF_EXITING))  
  7.             timeout = sk->sk_lingertime;  
  8.         ......  
  9. }  
  我们看到timeout的值和SOCK_LINGER选项有关,这个选项可以通过setsockopt()来设置,该选项表示在关闭连接时需要等待的时间,设置的事件值保存在sk_lingertime中,这个值可以为负值。这个选项通常不会设置,所以在我们的讨论上不关注这个选选项,认为timeout的值为0.

  接下来我们从tcp_close()开始,看内核中如何来执行应用层发出的关闭连接请求。

  tcp_close()中首先调用lock_sock()来获取访问sock实例的互斥锁,获取锁后将sk_shutdown设置为SHUTDOWN_MASK。sk_shutdown可以设置的值有RCV_SHUTDOWN(值为1)、SEND_SHUTDOWN(值为2)、SHUTDOWN_MASK(值为3),分别代表关闭接收通道、关闭发送通道、完全关闭,所以这里是要同时关闭发送和接收通道。接下来是检查套接的状态是否是LISTEN状态,我们这里要看的ESTABLISHED状态下套接字的关闭,所以这个部分直接跳过。

  如果此时接收队列中不为空,即接收到的数据没有被上层读取,这时需要将接受队列中的SKB包全都释放掉,释放的数据长度存储在局部变量data_was_unread(它的初始值为0)中,如下所示:

[cpp]  view plain copy
  1.       while ((skb = __skb_dequeue(&sk->sk_receive_queue)) != NULL) {  
  2. u32 len = TCP_SKB_CB(skb)->end_seq - TCP_SKB_CB(skb)->seq -  
  3.       tcp_hdr(skb)->fin;  
  4. data_was_unread += len;  
  5. __kfree_skb(skb);  
  局部变量data_was_unread中存储的值并不是内核关心的,内核关心只是是不是有数据未被读取。如果套接字的接收队列中有数据未被读取,这时内核发送的不是FIN包给对端,而是RST,如下所示:

[cpp]  view plain copy
  1.   if (data_was_unread) {  
  2. /* Unread data was tossed, zap the connection. */  
  3. NET_INC_STATS_USER(sock_net(sk), LINUX_MIB_TCPABORTONCLOSE);  
  4. tcp_set_state(sk, TCP_CLOSE);  
  5. tcp_send_active_reset(sk, sk->sk_allocation);  
  在发送RST包给对端前会调用tcp_set_state()将套接字的状态设置为TCP_CLOSE,这时没有TIME_WAIT状态,没有FIN_WAIT_1状态。所以在编写应用程序时,在关闭连接前,一定要保证所有接收到的数据被读取,否则连接会不正常关闭。

  假设套接字的接收队列为空,接着会调用tcp_close_state()函数,这个函数根据sock实例的当前状态找出执行关闭时下一个对应的状态,其返回值要么为0,要么为TCP_ACTION_FIN。返回值为TCP_ACTION_FIN时,表示要发送FIN包;为0时,表示不需要发送FIN包。如果连接是在ESTABLISHED状态执行关闭操作,其对应的下一个状态为FIN_WAIT_1,tcp_close_state()的返回值为TCP_ACTION_FIN,所以接下来会调用tcp_send_fin()给对端发送FIN包。在tcp_send_fin()中,如果sock实例的发送队列不为空,则将FIN标志添加到发送队列中最后一个要发送的SKB包中;如果发送队列为空,则会分配一个新的SKB包,添加上FIN标志后加入到发送队列中。FIN标志处理后,内核会调用__tcp_push_pending_frames()将发送队列的skb包发送出去。tcp_send_fin()的代码如下:

[cpp]  view plain copy
  1. /* Send a fin.  The caller locks the socket for us.  This cannot be 
  2.  * allowed to fail queueing a FIN frame under any circumstances. 
  3.  */  
  4. void tcp_send_fin(struct sock *sk)  
  5. {  
  6.     struct tcp_sock *tp = tcp_sk(sk);  
  7.     struct sk_buff *skb = tcp_write_queue_tail(sk);  
  8.     int mss_now;  
  9.   
  10.     /* Optimization, tack on the FIN if we have a queue of 
  11.      * unsent frames.  But be careful about outgoing SACKS 
  12.      * and IP options. 
  13.      */  
  14.     mss_now = tcp_current_mss(sk);  
  15.   
  16.     if (tcp_send_head(sk) != NULL) {  
  17.         TCP_SKB_CB(skb)->flags |= TCPCB_FLAG_FIN;  
  18.         TCP_SKB_CB(skb)->end_seq++;  
  19.         tp->write_seq++;  
  20.     } else {  
  21.         /* Socket is locked, keep trying until memory is available. */  
  22.         for (;;) {  
  23.             skb = alloc_skb_fclone(MAX_TCP_HEADER,  
  24.                            sk->sk_allocation);  
  25.             if (skb)  
  26.                 break;  
  27.             yield();  
  28.         }  
  29.   
  30.         /* Reserve space for headers and prepare control bits. */  
  31.         skb_reserve(skb, MAX_TCP_HEADER);  
  32.         /* FIN eats a sequence byte, write_seq advanced by tcp_queue_skb(). */  
  33.         tcp_init_nondata_skb(skb, tp->write_seq,  
  34.                      TCPCB_FLAG_ACK | TCPCB_FLAG_FIN);  
  35.         tcp_queue_skb(sk, skb);  
  36.     }  
  37.   
  38.     __tcp_push_pending_frames(sk, mss_now, TCP_NAGLE_OFF);  
  39. }  
  在tcp_send_fin()中分配用于发送FIN的SKB包时,如果分配失败,内存会一直循环,知道分配成功。不太理解内核为什么为了发送一个FIN包,而这么的“执着”,知道的朋友可以说一下,拜谢!

  在发送完FIN包后,会将sock实例当前的状态存储在局部变量state中,增加对sock实例的引用,然后调用sock_orphan()将sock实例从socket结构中分离,并且将套接字标志设置为SOCK_DEAD,表示套接字即将关闭。在分离的过程中,有一项是分离等待队列,内核只是简单地将sk_sleep成员设置为NULL。这就有一个疑惑,sk_sleep是一个指针,直接设置为NULL,指向的内存怎么释放?等待的进程怎么处理?其实sk_sleep成员指向的是socket实例的wait成员,所以等待队列的释放会在释放socket结构时释放。sk_sleep的值的设置是在sock_init_data()中进行的。接着调用release_sock()释放sock实例后备队列上的SKB包,如果此时后备队列上的数据包不是刚好确认到FIN所在的序列好,在tcp_rcv_state_process()中处理时也有可能发送RST给对端,这个和tcp_close()中对接收队列的处理类似。这个过程可以在后面看FIN_WAIT_1状态下接收到ACK时的处理时会看到。此时,sock实例要等待释放,所以此时待销毁的sock实例个数加1。

  为了完整的看到TCP连接的整个过程,在我们的讨论中认为在tcp_close()中处理后sock的状态为FIN_WAIT_1,在这个假设前提下,我们会进入到下面这段代码的处理中:

[cpp]  view plain copy
  1.   if (sk->sk_state != TCP_CLOSE) {  
  2. int orphan_count = percpu_counter_read_positive(  
  3.                 sk->sk_prot->orphan_count);  
  4.   
  5. sk_mem_reclaim(sk);  
  6. if (tcp_too_many_orphans(sk, orphan_count)) {  
  7.     if (net_ratelimit())  
  8.         printk(KERN_INFO "TCP: too many of orphaned "  
  9.                "sockets\n");  
  10.     tcp_set_state(sk, TCP_CLOSE);  
  11.     tcp_send_active_reset(sk, GFP_ATOMIC);  
  12.     NET_INC_STATS_BH(sock_net(sk),  
  13.             LINUX_MIB_TCPABORTONMEMORY);  
  14. }  
如果tcp_too_many_orphans()返回true时,这里我们看到内核会发送RST给对端。在以下两种情况下tcp_too_many_orphans()会返回true:

  1、当前待销毁的套接字数量大于系统配置sysctl_tcp_max_orphans变量

   2、如果当前sock实例发送队列中所有报文数据的总长度大于SOCK_MIN_SNDBUF(值为2048),并且TCP层分配内存的状态处于pressure状态

  到这里,tcp_close()的处理基本上结束了,在上面我们已经说过,认为处理后sock实例处于FIN_WAIT_1状态。

  在FIN_WAIT_1状态下,接收到SKB包时会在tcp_rcv_state_process()函数中处理,函数中涉及FIN_WAIT_1状态的代码如下所示:

[cpp]  view plain copy
  1. /* 
  2.  *  This function implements the receiving procedure of RFC 793 for 
  3.  *  all states except ESTABLISHED and TIME_WAIT. 
  4.  *  It's called from both tcp_v4_rcv and tcp_v6_rcv and should be 
  5.  *  address independent. 
  6.  */  
  7. int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,  
  8.               struct tcphdr *th, unsigned len)  
  9. {  
  10.     struct tcp_sock *tp = tcp_sk(sk);  
  11.     struct inet_connection_sock *icsk = inet_csk(sk);  
  12.     int queued = 0;  
  13.     int res;  
  14.   
  15.     tp->rx_opt.saw_tstamp = 0;  
  16.       
  17.     ......  
  18.   
  19.     res = tcp_validate_incoming(sk, skb, th, 0);  
  20.     if (res <= 0)  
  21.         return -res;  
  22.   
  23.     /* step 5: check the ACK field */  
  24.     if (th->ack) {  
  25.         int acceptable = tcp_ack(sk, skb, FLAG_SLOWPATH) > 0;  
  26.   
  27.         switch (sk->sk_state) {  
  28.         ......  
  29.   
  30.         case TCP_FIN_WAIT1:  
  31.             if (tp->snd_una == tp->write_seq) {  
  32.                 tcp_set_state(sk, TCP_FIN_WAIT2);  
  33.                 sk->sk_shutdown |= SEND_SHUTDOWN;  
  34.                 dst_confirm(sk->sk_dst_cache);  
  35.   
  36.                 if (!sock_flag(sk, SOCK_DEAD))  
  37.                     /* Wake up lingering close() */  
  38.                     sk->sk_state_change(sk);  
  39.                 else {  
  40.                     int tmo;  
  41.                     if (tp->linger2 < 0 ||  
  42.                         (TCP_SKB_CB(skb)->end_seq != TCP_SKB_CB(skb)->seq &&  
  43.                          after(TCP_SKB_CB(skb)->end_seq - th->fin, tp->rcv_nxt))) {  
  44.                         tcp_done(sk);  
  45.                         NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPABORTONDATA);  
  46.                         return 1;  
  47.                     }  
  48.   
  49.                     tmo = tcp_fin_time(sk);  
  50.                     if (tmo > TCP_TIMEWAIT_LEN) {  
  51.                         inet_csk_reset_keepalive_timer(sk, tmo - TCP_TIMEWAIT_LEN);  
  52.                     } else if (th->fin || sock_owned_by_user(sk)) {  
  53.                         /* Bad case. We could lose such FIN otherwise. 
  54.                          * It is not a big problem, but it looks confusing 
  55.                          * and not so rare event. We still can lose it now, 
  56.                          * if it spins in bh_lock_sock(), but it is really 
  57.                          * marginal case. 
  58.                          */  
  59.                         inet_csk_reset_keepalive_timer(sk, tmo);  
  60.                     } else {  
  61.                         tcp_time_wait(sk, TCP_FIN_WAIT2, tmo);  
  62.                         goto discard;  
  63.                     }  
  64.                 }  
  65.             }  
  66.             break;  
  67.               
  68.             ......  
  69.         }  
  70.     } else  
  71.         goto discard;  
  72.   
  73.     /* step 6: check the URG bit */  
  74.     tcp_urg(sk, skb, th);  
  75.   
  76.     /* step 7: process the segment text */  
  77.     switch (sk->sk_state) {  
  78.     ......  
  79.       
  80.     case TCP_FIN_WAIT1:  
  81.     case TCP_FIN_WAIT2:  
  82.         /* RFC 793 says to queue data in these states, 
  83.          * RFC 1122 says we MUST send a reset. 
  84.          * BSD 4.4 also does reset. 
  85.          */  
  86.         if (sk->sk_shutdown & RCV_SHUTDOWN) {  
  87.             if (TCP_SKB_CB(skb)->end_seq != TCP_SKB_CB(skb)->seq &&  
  88.                 after(TCP_SKB_CB(skb)->end_seq - th->fin, tp->rcv_nxt)) {  
  89.                 NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPABORTONDATA);  
  90.                 tcp_reset(sk);  
  91.                 return 1;  
  92.             }  
  93.         }  
  94.         /* Fall through */  
  95.     case TCP_ESTABLISHED:  
  96.         tcp_data_queue(sk, skb);  
  97.         queued = 1;  
  98.         break;  
  99.     }  
  100.   
  101.     /* tcp_data could move socket to TIME-WAIT */  
  102.     if (sk->sk_state != TCP_CLOSE) {  
  103.         tcp_data_snd_check(sk);  
  104.         tcp_ack_snd_check(sk);  
  105.     }  
  106.   
  107.     if (!queued) {  
  108. discard:  
  109.         __kfree_skb(skb);  
  110.     }  
  111.     return 0;  
  112. }  
  如果数据包中带ACK标志,则会调用tcp_ack()函数来处理对端的确认,处理后会跳转到第30行处的FIN_WAIT_1分支继续处理,这个处理是否进行取决于第31行的判断,如下所示:

[cpp]  view plain copy
  1. if (tp->snd_una == tp->write_seq) {  
  其中tp->snd_una存储的是输出的段中最早一个未确认段的序号,tp->write_seq存储的加入到发送队列中最后一个字节的序号加1,如果这两个成员相等,则表示所有发出去的段都已确认。我们知道在tcp_close()中最后加入到发送队列的是FIN标志(可能是重新创建一个包,或者是加入到已有包中),所以说明在发送完FIN后,接收到了对端的确认,此时可以从FIN_WAIT_1状态迁移到FIN_WAIT_2状态,状态的更改是通过调用tcp_set_state()函数来完成的。接下来根据内核中设置要求套接字保持在FIN_WAIT_2状态的时间来选择是通过定时器等待对端发送FIN包,还是立即迁移到TIME_WAIT状态。如果是第二种的话,虽然此时状态是TIME_WAIT状态,但还有一个子状态的概念,目的就是要区分当前是FIN_WAIT_2状态还是真正的TIME_WAIT状态,这样做的话会使用专门描述TIME_WAIT状态的sock结构,这样会节省内存,还会提升系统的性能。因为大多数传输控制块主动关闭时都会经历TIME_WAIT状态,如果每个控制块都使用一个定时器来处理,在系统中存在很多短连接的情况下(比如Web服务器),2MSL等待超时时间确实准确了,但由于使用了很多定时器,会严重影响系统的性能。如果TIME_WAIT的等待时间小于2MSL,会将sock结构添加到twcal_row散列表中,有一个定时器会定时清理这个散列表上的timewait控制块。
  接着我们跳转到第80行的FIN_WAIT_1分支的处理。在这里内核会检查sk_shutdown中是否设置了关闭接收通道,在tcp_close()中我们看到设置的是SHUTDOWN_MASK,所以这个的判断是成立的。我们来看这个判断:

[cpp]  view plain copy
  1. if (TCP_SKB_CB(skb)->end_seq != TCP_SKB_CB(skb)->seq &&  
  2.     after(TCP_SKB_CB(skb)->end_seq - th->fin, tp->rcv_nxt))  
第一个条件在SKB的TCP层数据不为空时为true,第二个条件是在SKB包中的数据除了FIN外,还有数据时为true,简单来说就是在SKB包中除了FIN标志外,还有数据,此时这个判断为真,接下来就要对sock结构执行reset操作,并且返回1。如果返回1,在调用tcp_rcv_state_process()的上层函数tcp_v4_do_rcv()中会发送RST给对端,关键代码如下所示:

[cpp]  view plain copy
  1. /* The socket must have it's spinlock held when we get 
  2.  * here. 
  3.  * 
  4.  * We have a potential double-lock case here, so even when 
  5.  * doing backlog processing we use the BH locking scheme. 
  6.  * This is because we cannot sleep with the original spinlock 
  7.  * held. 
  8.  */  
  9. int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)  
  10. {  
  11.     ......  
  12.       
  13.     if (tcp_rcv_state_process(sk, skb, tcp_hdr(skb), skb->len)) {  
  14.         rsk = sk;  
  15.         goto reset;  
  16.     }  
  17.     TCP_CHECK_TIMER(sk);  
  18.     return 0;  
  19.   
  20. reset:  
  21.     tcp_v4_send_reset(rsk, skb);  
  22. discard:  
  23.     kfree_skb(skb);  
  24.     /* Be careful here. If this function gets more complicated and 
  25.      * gcc suffers from register pressure on the x86, sk (in %ebx) 
  26.      * might be destroyed here. This current version compiles correctly, 
  27.      * but you have been warned. 
  28.      */  
  29.     return 0;  
  30.       
  31.     ......  
  32. }  
  如果此时的数据包中恰好是对端发送过来的FIN包,那这个FIN会在第96行调用的tcp_data_queue()中处理。在tcp_data_queue()中会,会首先检查这个包是否是一个合法的,并且是可以接收的SKB包。通过检查后如果skb包中包含FIN标志,则调用tcp_fin()来处理,我们来看tcp_fin()中如何处理这个FIN包,相关的代码如下所示:

[cpp]  view plain copy
  1. /* 
  2.  *  Process the FIN bit. This now behaves as it is supposed to work 
  3.  *  and the FIN takes effect when it is validly part of sequence 
  4.  *  space. Not before when we get holes. 
  5.  * 
  6.  *  If we are ESTABLISHED, a received fin moves us to CLOSE-WAIT 
  7.  *  (and thence onto LAST-ACK and finally, CLOSE, we never enter 
  8.  *  TIME-WAIT) 
  9.  * 
  10.  *  If we are in FINWAIT-1, a received FIN indicates simultaneous 
  11.  *  close and we go into CLOSING (and later onto TIME-WAIT) 
  12.  * 
  13.  *  If we are in FINWAIT-2, a received FIN moves us to TIME-WAIT. 
  14.  */  
  15. static void tcp_fin(struct sk_buff *skb, struct sock *sk, struct tcphdr *th)  
  16. {  
  17.     struct tcp_sock *tp = tcp_sk(sk);  
  18.   
  19.     sk->sk_shutdown |= RCV_SHUTDOWN;  
  20.     sock_set_flag(sk, SOCK_DONE);  
  21.   
  22.     switch (sk->sk_state) {  
  23.     ......  
  24.       
  25.     case TCP_FIN_WAIT2:  
  26.         /* Received a FIN -- send ACK and enter TIME_WAIT. */  
  27.         tcp_send_ack(sk);  
  28.         tcp_time_wait(sk, TCP_TIME_WAIT, 0);  
  29.         break;  
  30.       
  31.     ......  
  32.     }  
  33.   
  34.     /* It _is_ possible, that we have something out-of-order _after_ FIN. 
  35.      * Probably, we should reset in this case. For now drop them. 
  36.      */  
  37.     __skb_queue_purge(&tp->out_of_order_queue);  
  38.     if (tcp_is_sack(tp))  
  39.         tcp_sack_reset(&tp->rx_opt);  
  40.     sk_mem_reclaim(sk);  
  41.   
  42.     ......  
  43. }  
  在这里有一个地方需要注意下,在接收到FIN后,会调用tcp_set_flag()设置sock的flag为SOCK_DONE,与tcp_close()中设置的SOCK_DEAD是不一样的,注意这里的标志发生了变化,只是提醒下,后面的处理不作过多的说明。接下来就和明显了,调用tcp_send_ack()给对端发送ACK,表示FIN包已收到,接着调用tcp_time_wait()将套接字状态迁移到TIME_WAIT状态了。

  至此,TCP连接终于关闭了。

  当然后续在TIME_WAIT状态下接收到对端的数据包也会做一些处理,这些处理不是本文关注的了,后面会写一篇关于TIME_WAIT状态下的内核处理的文章。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值