CLOSE_WAIT 状态还能收到报文吗?

百度公司园区配图

问题:TCP 在 CLOSE_WAIT 状态还能收到报文吗?

先说答案

首先给出答案:能收到报文,而且还能正确地回复 ACK。

从四次挥手看

先从四次挥手说起(不熟悉的话,可以先看《三次握手与四次挥手》),这道面试题比较简单:

我们都知道 TCP 是全双工通信,当客户端发送 FIN 后,服务端回复 ACK 进入 CLOSE_WAIT 状态,这个时候对于客户端来说,发送功能没有了,但是还可以正常接收服务端的数据;对于服务端来说,既可以接收客户端的数据,也可以给客户端发数据。

当被动关闭方在 CLOSE_WAIT 状态下接收到数据时,它依然会遵循 TCP 的协议规范,对接收的数据进行确认,即向对方发送 ACK 报文。这是 TCP 协议确保数据可靠传输的基本行为,确保每一个数据包都被确认接收。

所以如果服务端在 CLOSE_WAIT 状态,客户端有报文过来,服务端是能正常接收并且回复 ACK 确认的,客户端也能收到这个 ACK。

总结来说,在 CLOSE_WAIT 状态下,不仅可以接收数据,而且对接收到的数据会正常回复 ACK 报文。不过,需要注意的是,处于这个状态应当尽快完成数据处理并主动关闭连接,避免资源被长时间占用(大量 CLOSE_WAIT 堆积的排查思路可以看《服务端出现大量 CLOSE_WAIT 的排查思路》)。

用源码证明

上面是我们的理解,我们直接拿源码证明我们的理解:

在 net/ipv4/tcp_input.c 文件有个函数:

int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,
                          struct tcphdr *th, unsigned len)

源码比较多,我们只看和我们有关系的:

/* step 7: process the segment text */
switch (sk->sk_state) {
case TCP_CLOSE_WAIT:
case TCP_CLOSING:
case TCP_LAST_ACK:
    if (!before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt))
        break;
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_CLOSE_WAIT、TCP_CLOSING、TCP_LAST_ACK 这三种状态:如果收到报文的序号在 tp->rcv_nxt 之前——也就是 FIN 之前的报文,比如对端重传的数据、重发的 FIN——就会依次「穿透」到 TCP_FIN_WAIT1、TCP_FIN_WAIT2 分支,最终落入和 TCP_ESTABLISHED 一样的处理流程:
tcp_data_queue(sk, skb);
  • 那两个 break 则是另一层保护:CLOSE_WAIT 这类状态下,若收到的是 FIN 之后才出现的新数据(对端按协议本就不该再发),则直接跳出去走丢弃逻辑;而 TCP_FIN_WAIT1、TCP_FIN_WAIT2 状态下,如果本端已关闭接收(RCV_SHUTDOWN,收到 FIN 时内核就会打上这个标记)却还有 FIN 之后的「新数据」到来,内核甚至会直接发 RST 中止连接,对应计数器 LINUX_MIB_TCPABORTONDATA。

所以,处于 CLOSE_WAIT 的连接收到 FIN 之前的报文时,内核与 ESTABLISHED 状态走完全相同的接收路径,该缓存缓存、该确认确认。源码证明我们上面的理解没有问题。

顺带一提,协议状态相关的更多疑难问题,可以看《TCP 协议疑难杂症全景解析》。


   转载规则


《CLOSE_WAIT 状态还能收到报文吗?》 由 吴杭沉 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
服务端出现大量 CLOSE_WAIT 的排查思路 服务端出现大量 CLOSE_WAIT 的排查思路
问题:服务端出现大量 CLOSE_WAIT 状态,什么原因?有什么危害?应该怎么办? CLOSE_WAIT 是什么时候出现的回答这个问题,首先需要知道 CLOSE_WAIT 状态是什么时候出现的?还是回到经典的四次挥手过程,这个过程得不断
2021-11-02
下一篇 
从一次线上经历谈 TIME_WAIT 从一次线上经历谈 TIME_WAIT
今天来讲一讲 TCP 的 TIME_WAIT 的问题。这个问题尽人皆知,不过,这次遇到的是不太一样的场景,前两天也解决了,正好写篇文章,顺便把 TIME_WAIT 的那些事都说一说。对了,这个场景,跟我开源的探活小工具 EaseProbe
2021-10-19
  目录