在正常情况下,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()中计算出来的,其计算方式如下所示:
int inet_release(struct socket *sock) { ...... timeout = 0; if (sock_flag(sk, SOCK_LINGER) && !(current->flags & PF_EXITING)) timeout = sk->sk_lingertime; ...... }我们看到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)中,如下所示:
while ((skb = __skb_dequeue(&sk->sk_receive_queue)) != NULL) { u32 len = TCP_SKB_CB(skb)->end_seq - TCP_SKB_CB(skb)->seq - tcp_hdr(skb)->fin; data_was_unread += len; __kfree_skb(skb); }局部变量data_was_unread中存储的值并不是内核关心的,内核关心只是是不是有数据未被读取。如果套接字的接收队列中有数据未被读取,这时内核发送的不是FIN包给对端,而是RST,如下所示:
if (data_was_unread) { /* Unread data was tossed, zap the connection. */ NET_INC_STATS_USER(sock_net(sk), LINUX_MIB_TCPABORTONCLOSE); tcp_set_state(sk, TCP_CLOSE); 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()的代码如下:
/* Send a fin. The caller locks the socket for us. This cannot be * allowed to fail queueing a FIN frame under any circumstances. */ void tcp_send_fin(struct sock *sk) { struct tcp_sock *tp = tcp_sk(sk); struct sk_buff *skb = tcp_write_queue_tail(sk); int mss_now; /* Optimization, tack on the FIN if we have a queue of * unsent frames. But be careful about outgoing SACKS * and IP options. */ mss_now = tcp_current_mss(sk); if (tcp_send_head(sk) != NULL) { TCP_SKB_CB(skb)->flags |= TCPCB_FLAG_FIN; TCP_SKB_CB(skb)->end_seq++; tp->write_seq++; } else { /* Socket is locked, keep trying until memory is available. */ for (;;) { skb = alloc_skb_fclone(MAX_TCP_HEADER, sk->sk_allocation); if (skb) break; yield(); } /* Reserve space for headers and prepare control bits. */ skb_reserve(skb, MAX_TCP_HEADER); /* FIN eats a sequence byte, write_seq advanced by tcp_queue_skb(). */ tcp_init_nondata_skb(skb, tp->write_seq, TCPCB_FLAG_ACK | TCPCB_FLAG_FIN); tcp_queue_skb(sk, skb); } __tcp_push_pending_frames(sk, mss_now, TCP_NAGLE_OFF); }在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,在这个假设前提下,我们会进入到下面这段代码的处理中:
if (sk->sk_state != TCP_CLOSE) { int orphan_count = percpu_counter_read_positive( sk->sk_prot->orphan_count); sk_mem_reclaim(sk); if (tcp_too_many_orphans(sk, orphan_count)) { if (net_ratelimit()) printk(KERN_INFO "TCP: too many of orphaned " "sockets\n"); tcp_set_state(sk, TCP_CLOSE); tcp_send_active_reset(sk, GFP_ATOMIC); NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPABORTONMEMORY); } }如果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状态的代码如下所示:
/* * This function implements the receiving procedure of RFC 793 for * all states except ESTABLISHED and TIME_WAIT. * It's called from both tcp_v4_rcv and tcp_v6_rcv and should be * address independent. */ int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb, struct tcphdr *th, unsigned len) { struct tcp_sock *tp = tcp_sk(sk); struct inet_connection_sock *icsk = inet_csk(sk); int queued = 0; int res; tp->rx_opt.saw_tstamp = 0; ...... res = tcp_validate_incoming(sk, skb, th, 0); if (res <= 0) return -res; /* step 5: check the ACK field */ if (th->ack) { int acceptable = tcp_ack(sk, skb, FLAG_SLOWPATH) > 0; switch (sk->sk_state) { ...... case TCP_FIN_WAIT1: if (tp->snd_una == tp->write_seq) { tcp_set_state(sk, TCP_FIN_WAIT2); sk->sk_shutdown |= SEND_SHUTDOWN; dst_confirm(sk->sk_dst_cache); if (!sock_flag(sk, SOCK_DEAD)) /* Wake up lingering close() */ sk->sk_state_change(sk); else { int tmo; if (tp->linger2 < 0 || (TCP_SKB_CB(skb)->end_seq != TCP_SKB_CB(skb)->seq && after(TCP_SKB_CB(skb)->end_seq - th->fin, tp->rcv_nxt))) { tcp_done(sk); NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPABORTONDATA); return 1; } tmo = tcp_fin_time(sk); if (tmo > TCP_TIMEWAIT_LEN) { inet_csk_reset_keepalive_timer(sk, tmo - TCP_TIMEWAIT_LEN); } else if (th->fin || sock_owned_by_user(sk)) { /* Bad case. We could lose such FIN otherwise. * It is not a big problem, but it looks confusing * and not so rare event. We still can lose it now, * if it spins in bh_lock_sock(), but it is really * marginal case. */ inet_csk_reset_keepalive_timer(sk, tmo); } else { tcp_time_wait(sk, TCP_FIN_WAIT2, tmo); goto discard; } } } break; ...... } } else goto discard; /* step 6: check the URG bit */ tcp_urg(sk, skb, th); /* step 7: process the segment text */ switch (sk->sk_state) { ...... case TCP_FIN_WAIT1: case TCP_FIN_WAIT2: /* RFC 793 says to queue data in these states, * RFC 1122 says we MUST send a reset. * BSD 4.4 also does reset. */ if (sk->sk_shutdown & RCV_SHUTDOWN) { if (TCP_SKB_CB(skb)->end_seq != TCP_SKB_CB(skb)->seq && after(TCP_SKB_CB(skb)->end_seq - th->fin, tp->rcv_nxt)) { NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPABORTONDATA); tcp_reset(sk); return 1; } } /* Fall through */ case TCP_ESTABLISHED: tcp_data_queue(sk, skb); queued = 1; break; } /* tcp_data could move socket to TIME-WAIT */ if (sk->sk_state != TCP_CLOSE) { tcp_data_snd_check(sk); tcp_ack_snd_check(sk); } if (!queued) { discard: __kfree_skb(skb); } return 0; }如果数据包中带ACK标志,则会调用tcp_ack()函数来处理对端的确认,处理后会跳转到第30行处的FIN_WAIT_1分支继续处理,这个处理是否进行取决于第31行的判断,如下所示:
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控制块。
if (TCP_SKB_CB(skb)->end_seq != TCP_SKB_CB(skb)->seq && 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给对端,关键代码如下所示:
/* The socket must have it's spinlock held when we get * here. * * We have a potential double-lock case here, so even when * doing backlog processing we use the BH locking scheme. * This is because we cannot sleep with the original spinlock * held. */ int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb) { ...... if (tcp_rcv_state_process(sk, skb, tcp_hdr(skb), skb->len)) { rsk = sk; goto reset; } TCP_CHECK_TIMER(sk); return 0; reset: tcp_v4_send_reset(rsk, skb); discard: kfree_skb(skb); /* Be careful here. If this function gets more complicated and * gcc suffers from register pressure on the x86, sk (in %ebx) * might be destroyed here. This current version compiles correctly, * but you have been warned. */ return 0; ...... }如果此时的数据包中恰好是对端发送过来的FIN包,那这个FIN会在第96行调用的tcp_data_queue()中处理。在tcp_data_queue()中会,会首先检查这个包是否是一个合法的,并且是可以接收的SKB包。通过检查后如果skb包中包含FIN标志,则调用tcp_fin()来处理,我们来看tcp_fin()中如何处理这个FIN包,相关的代码如下所示:
/* * Process the FIN bit. This now behaves as it is supposed to work * and the FIN takes effect when it is validly part of sequence * space. Not before when we get holes. * * If we are ESTABLISHED, a received fin moves us to CLOSE-WAIT * (and thence onto LAST-ACK and finally, CLOSE, we never enter * TIME-WAIT) * * If we are in FINWAIT-1, a received FIN indicates simultaneous * close and we go into CLOSING (and later onto TIME-WAIT) * * If we are in FINWAIT-2, a received FIN moves us to TIME-WAIT. */ static void tcp_fin(struct sk_buff *skb, struct sock *sk, struct tcphdr *th) { struct tcp_sock *tp = tcp_sk(sk); sk->sk_shutdown |= RCV_SHUTDOWN; sock_set_flag(sk, SOCK_DONE); switch (sk->sk_state) { ...... case TCP_FIN_WAIT2: /* Received a FIN -- send ACK and enter TIME_WAIT. */ tcp_send_ack(sk); tcp_time_wait(sk, TCP_TIME_WAIT, 0); break; ...... } /* It _is_ possible, that we have something out-of-order _after_ FIN. * Probably, we should reset in this case. For now drop them. */ __skb_queue_purge(&tp->out_of_order_queue); if (tcp_is_sack(tp)) tcp_sack_reset(&tp->rx_opt); sk_mem_reclaim(sk); ...... }在这里有一个地方需要注意下,在接收到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状态下的内核处理的文章。