close wait 状态的随想

  今天在新入职的公司处理waf 的问题时,突然看到了一个tcp状态close-wait

  想一想 close-wait 是怎样产生的???? 被动收到FIN 关闭请求,协议栈主动发出ACK, 等待 本端主动发出 FIN,但是本端一直没有执行CLOSE。也就是在被动关闭连接情况下,在已经接收到FIN,但是还没有发送自己的FIN的时刻,连接处于CLOSE_WAIT状态。按道理此状态一般非常短暂,

  出现大量close_wait的现象,主要原因是某种情况下对方关闭了socket链接,但是我方忙与读或者写,没有关闭连接。代码需要判断socket,一旦读到0,断开连接,read返回负,检查一下errno,如果不是AGAIN,就断开连接。

 

那么怎么解决此问题:??

也就是要检测出对方已经关闭的socket,然后我们去close掉它!!!

1、代码需要判断socket,一旦read返回0,断开连接,read返回负,检查一下errno,如果不是AGAIN,EINTR等 也需要断开连接

2、给每一个socket设置一个时间戳last_update,每接收或者是发送成功数据,就用当前时间更新这个时间戳。定期检查所有的时间戳,如果时间戳与当前时间差值超过一定的阈值,就关闭这个socket。http server 经常这样处理。

3、设置SO_KEEPALIVE选项,使用setsockopt修改socket参数,参考man 7 socket。

但是有个问题:我们是使用read fd 返回0 认为是收到Fin;

但是有没有存在一种可能:Fin和数据包报文一起发送????Nagle's算法会累积TCP包,如果最后的数据包和FIN包被Nagle's算法合并呢??read 那端使用ET触发!!

 

之前有一篇文章写得正常情况下 而接收fin

---参考:https://www.cnblogs.com/codestack/p/11117451.html

当接收端收到了未按序到达的FIN报文时的行为? 怎么处理?

 目前看下 在FIN2 状态下, 收到乱序fin报文的处理:

 

  • 会判断该报文有没有 FIN 标志,如果有的话就会调用 tcp_fin 函数,这个函数负责将 FIN_WAIT_2 状态转换为 TIME_WAIT。
  • 接着还会看乱序队列有没有数据,如果有的话会调用 tcp_ofo_queue 函数,这个函数负责检查乱序队列中是否有数据包可用,即能不能在乱序队列找到与当前数据包保持序列号连续的数据包。

  而当收到的报文的序列号不是我们预期的,也就是乱序的话,则调用 tcp_data_queue_ofo 函数,将报文加入到乱序队列,这个队列的数据结构是红黑树。

如果客户端收到的 FIN 报文实际上是一个乱序的报文,因此此时并不会调用 tcp_fin 函数进行状态转换,而是将报文通过 tcp_data_queue_ofo 函数加入到乱序队列。

然后当客户端收到被网络延迟的数据包后,此时因为该数据包的序列号是期望的,然后又因为上一次收到的乱序 FIN 报文被加入到了乱序队列,表明乱序队列是有数据的,于是就会调用 tcp_ofo_queue 函数。

在上面的 tcp_ofo_queue 函数里,在乱序队列中找到能与当前报文的序列号保持的顺序的报文后,会看该报文是否有 FIN 标志,如果有的话,就会调用 tcp_fin() 函数。此时看正常逻辑就行

相关代码分析见:https://www.cnblogs.com/codestack/p/11919403.html

  会清除乱序报文, 保存正常顺序的pkt,同时sk的rcv 关闭,回复ack 然后 sock_def_wakeup唤醒进程

同时调用poll/epoll 返回时其事件掩码会包含POLLRDHUP 表示事件挂起

所以:我们是使用read fd 返回0 认为是收到Fin;如果sk的 sk_receive_queue 队列中有数据包包含了的 fin,在调用tcp_rcvmsg 函数时,会检测sock的状态

sk收到fin时会 设置为sock_done状态,同时tcp_recvmsg  在读取数据时 会根据 sock_done 、fin 标志 等做出处理;原则是尽可能的copy 数据到user中

 

last = skb_peek_tail(&sk->sk_receive_queue);
        skb_queue_walk(&sk->sk_receive_queue, skb) {
            last = skb;
            /* Now that we have two receive queues this
             * shouldn't happen.
             */
            if (WARN(before(*seq, TCP_SKB_CB(skb)->seq),
                 "recvmsg bug: copied %X seq %X rcvnxt %X fl %X\n",
                 *seq, TCP_SKB_CB(skb)->seq, tp->rcv_nxt,
                 flags))
                break;

            offset = *seq - TCP_SKB_CB(skb)->seq;
            if (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_SYN)
                offset--;
            if (offset < skb->len)
                goto found_ok_skb;
            if (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN)
                goto found_fin_ok;
            WARN(!(flags & MSG_PEEK),
                 "recvmsg bug 2: copied %X seq %X rcvnxt %X fl %X\n",
                 *seq, TCP_SKB_CB(skb)->seq, tp->rcv_nxt, flags);
        }
    --------------------
if (copied) {
            if (sk->sk_err ||
                sk->sk_state == TCP_CLOSE ||
                (sk->sk_shutdown & RCV_SHUTDOWN) ||// 已经读到数据  但是 rcv被关闭了 则break 返回
                !timeo ||
                signal_pending(current))
                break;
        } else {
            if (sock_flag(sk, SOCK_DONE))//sock 为done (收到fin)---跳出返回
                break;

            if (sk->sk_err) {
                copied = sock_error(sk);
                break;
            }
      

        if (sk->sk_shutdown & RCV_SHUTDOWN)但是 rcv被关闭了 则break 返回

            break;

--------------------------------
}

 

 由于目前很多epoll 中将POLLRDHUP  POLLHUP  POLLERR 等字段合并成了POLLIN  POLLOUT信息处理;所以最后就变成了recv fd 一直读到返回0 或者读到返回-1 但是errno == EAGAIN || errno == ENOTCONN;表示数据读取完毕;也就是如果fin和数据包在一起第一次recv 会返回数据长度n 继续读就回返回0 表示关闭

可以参考一下:

https://stackoverflow.com/questions/21111003/epoll-tcp-edge-triggered-necessity-of-last-read2-call

https://man7.org/linux/man-pages/man7/epoll.7.html 

 

----------------------------------------------------------???---------------------------------

 

posted @ 2020-08-28 23:39  codestacklinuxer  阅读(171)  评论(0编辑  收藏  举报