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()中计算出来的,其计算方式如下所示:
/* * 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状态下的内核处理的文章。