tcp连接关闭时,被动关闭一方,tcp连接终止过程

  tcp连接关闭时,被动关闭一方,tcp连接终止过程

  本文是对《TCP连接的终止----主动关闭》的补充,重点介绍被动封闭连接一端的状态迁移和内核中的处理。被动连接的状态迁移可以参考《TCP连接的终止----主动关闭》中的状态迁移图,这里就不画了。

  被动连接在收到FIN时关闭。如果TCP处于ESTABLISHED状态(我们的讨论假设连接处于这种状态),这个FIN包将在tcp_rcv_established()中进行处理。tcp_rcv_state_process()中的处理分为快速路径和慢速路径。如果TCP报头中的第四个32位字与除保留位之外的预测标志一致,skb包的序列号等于sock结构中下一个要接收的序列号,并且skb包中的确认序列号有效,则skb包将在快速路径中处理。判断sock实例预测标记的位图分布时,通常如下图所示:

  其中s对应tcp报头中的doff成员(TCP报头的长度为4个字节),通常0和1的比特对应ACK标志,snd_wnd是本地发送窗口的大小。

  从上面预测标志的分布来看,如果设置了FIN标志,那么预测标志的检查将失败,因此FIN包将在慢速路径中处理。

  在慢速路径处理中,将首先检查skb包的校验和以及该包是否有效。检查通过后,将调用tcp_ack()来处理ack。在接下来的处理中,真正与FIN相关的操作是在被调用的tcp_data_queue()中处理的,它是通过调用tcp_fin()函数和接收来自初始tcp层的函数tcp_v4_rcv()来处理的。

  在tcp_fin()中,首先调用inet_csk_schedule_ack()设置相关成员,表示需要发送ack;因为收到对端发送的FIN包,表示对端不再发送数据包(可以发送ACK),所以接收通道关闭;还要修改sock结构的标志,以表示连接将要结束;然后会根据sock实例的状态跳转到不同的分支进行处理。如果建立,内核将调用tcp_set_state()修改sock实例的状态,并设置延迟发送ACK的标志,如下所示:

  静态void tcp_fin(struct sk_buff *skb,struct sock *sk,struct tcphdr *th)

  struct TCP _ sock * TP=TCP _ sk(sk);

  inet _ csk _ schedule _ ack(sk);

  sk-sk _ shut down =RCV _关机;

  sock_set_flag(sk,SOCK _ DONE);

  开关(sk- sk_state) {

  案例TCP_SYN_RECV:

  案例TCP _已建立:

  /*移至CLOSE_WAIT */

  tcp_set_state(sk,TCP _ CLOSE _ WAIT);

  inet _ csk(sk)-icsk _ ack . ping pong=1;

  打破;

  .

  }

  tcp_fin()之后,乱序队列可能会被清除,当状态发生变化时,相关进程可能必须被唤醒。这些操作都不是我们关心的,就不做过多解释了。

  虽然在tcp_fin()中设置了发送ACK的相关标志,但是必须有操作触发ACK发送或者提示发送ACK到内核。该操作在上层函数的tcp_rcv_established()函数中执行,通过间接调用__tcp_ack_snd_check()来完成。__tcp_ack_snd_check()将决定当前发送的ack是应该立即发送还是延迟发送。如果是立即发送,调用tcp_send_ack()发送ACK;否则,调用TCP _ SEND _ DELLED _ ACK()延迟发送。代码如下:

  静态void _ _ TCP _ ack _ snd _ check(struct sock * sk,int ofo_possible)

  struct TCP _ sock * TP=TCP _ sk(sk);

  /*接收到一个以上的完整帧.*/

  if((TP-rcv _ NXT-TP-rcv _ wup)inet _ csk(sk)-icsk _ ack . rcv _ MSS

  /* .并且窗口的右边缘前进得足够远。

  *(否则tcp_recvmsg()将发送ACK)。或者.

  _ _ TCP _ select _ window(sk)=TP-rcv _ wnd)

  /*我们确认每个帧,或者.*/

  tcp_in_quickack_mode(sk)

  /*我们有无序的数据。*/

  (ofo _ possible skb _ peek(TP-out _ of _ order _ queue))){

  /*那现在就确认*/

  TCP _ send _ ack(sk);

  }否则{

  /*否则,发送延迟ack。*/

  TCP _ send _ delayed _ ack(sk);

  }

  判断条件是,只要满足以下条件,就会立即发送ACK:

  1.接收窗口中有许多未确认的全尺寸段。

  2.目前处于快速确认模式。

  3.当无序队列被启用时,无序队列中有段。

  这三个条件中,我们能确定的是第二个条件。先来看tcp_in_quickack_mod()函数的实现,这个函数是判断是否处于快速确认模式的函数,如下图:

  静态内联int TCP _ in _ quick ack _ mode(const struct sock * sk)

  const struct inet _ connection _ sock * icsk=inet _ csk(sk);

  return icsk- icsk_ack.quick!icsk-icsk _ ack . ping pong;

  }

  在tcp_FIN()中,如果FIN是在ESTABLISHED状态下接收的,那么pingpong的值会被设置为1,所以可以确定此时不处于快速确认模式。是否立即发送ACK取决于另外两个判断条件。

  我们的下一个讨论是在确认收到的鳍之后开始的。此时TCP连接的关闭已经进行到一半,下一步就是等待本地终端的上层应用调用close()来执行本地终端的关闭操作。这个操作在《TCP连接的终止----主动关闭》中提到过,是由tcp_close()完成的。所以我们还是看tcp_close(),只是这次sock实例的状态不同,此时的状态应该是CLOSE_WAIT。

  在tcp_close()中,我们这次只关注一些状态相关的处理,其他的队列清理,内存回收等。就不介绍了,所以我们只关注下面部分代码:

  void tcp_close(struct sock *sk,长超时)

  struct sk _ buff * skb

  int data _ was _ unread=0;

  int状态;

  .

  if (data_was_unread) {

  .

  } else if (sock_flag(sk,SOCK_LINGER)!sk- sk_lingertime) {

  .

  } else if (tcp_close_state(sk)) {

  TCP _ send _ fin(sk);

  .

  }

  在tcp_close_state()中,sock实例的状态会从CLOSE_WAIT迁移到LAST_ACK状态,返回值为TCP_ACTION_FIN,表示要发送FIN。所以第三个if判断条件为真,那么会调用tcp_send_FIN()向对等体发送FIN。

  本地终端收到TCP连接关闭的最后一个ACK时,由tcp_rcv_state_process()函数处理,相关代码如下:

  int TCP _ rcv _ state _ process(struct sock * sk,struct sk_buff *skb,

  struct tcphdr *th,无符号len)

  struct TCP _ sock * TP=TCP _ sk(sk);

  struct inet _ connection _ sock * icsk=inet _ csk(sk);

  int queued=0;

  int res

  .

  /*步骤5:检查ACK字段*/

  if (th- ack) {

  int acceptable=tcp_ack(sk,skb,FLAG_SLOWPATH)

  开关(sk- sk_state) {

  .

  案例TCP_LAST_ACK:

  if (tp- snd_una==tp- write_seq) {

  TCP _ update _ metrics(sk);

  TCP _ done(sk);

  转到丢弃;

  打破;

  }否则

  转到丢弃;

  .

  开关(sk- sk_state) {

  案例TCP_CLOSE_WAIT:

  案例TCP_CLOSING:

  案例TCP_LAST_ACK:

  如果(!之前(TCP_SKB_CB(skb)- seq,tp- rcv_nxt))

  打破;

  案例TCP_FIN_WAIT1:

  案例TCP_FIN_WAIT2:

  /* RFC 793要求在这些状态下对数据进行排队,

  * RFC 1122说我们必须发送一个复位。

  * BSD 4.4也会重置。

  if(sk-sk _ shut down RCV _关机){

  if (TCP_SKB_CB(skb)- end_seq!=TCP_SKB_CB(skb)- seq

  (TCP_SKB_CB(skb)- end_seq - th- fin,tp- rcv_nxt)之后){

  NET_INC_STATS_BH(sock_net(sk),LINUX _ MIB _ TCPABORTONDATA);

  TCP _ reset(sk);

  返回1;

  /*失败*/

  案例TCP _已建立:

  tcp_data_queue(sk,skb);

  排队=1;

  打破;

  /* tcp_data可以将套接字移动到TIME-WAIT */

  if (sk- sk_state!=TCP_CLOSE) {

  TCP _ data _ snd _ check(sk);

  TCP _ ack _ snd _ check(sk);

  如果(!排队){

  丢弃:

  _ _ kfree _ skb(skb);

  返回0;

  如果它只是预期的ACK包,它将在第19-23行被处理。调用tcp_done()将socket状态设置为TCP_CLOSE,调用inet_csk_destroy_sock()释放sock实例占用的资源,调用sock_put()释放传输控制块(真正的调用sk_free()通常不在这里)。我们知道一般情况下,描述TIME_WAIT状态的sock结构会被timer释放或者放入twcal_row队列等待释放。这些释放方法是显而易见的。还有一种,就是通过inet_csk_destroy_sock()间接完成释放。

  有时可能会收到其他数据包。如果它是一个包含数据的数据包,RST将在44-51过程中被发送给对等体。如果是简单的ACK包,但是确认的序列号是错误的,那么会在tcp_data_queue()中释放。

  至此,TCP连接被动关闭方的处理完成。

tcp连接关闭时,被动关闭一方,tcp连接终止过程