大家好,我是小林。
昨天收到个读者的问题,他在面试鹅厂的时候,被面试官搞懵了,因为面试官问了他这么一个网络问题:
不得不说,鹅厂真的很喜欢问网络问题,而且爱问异常情况下的网络问题,之前也有篇另外一个读者面试鹅厂的网络问题:「被鹅厂面怕了!」」。我们来看看这位读者的问题,这个问题其实是在问:客户端(主动关闭方)在 TIME_WAIT 状态下,如果收到服务端的数据包时,会怎么处理?
这个问题迷惑性在于,客户端是使用了 shutdown 函数关闭了写方向,而读方向并没有关闭,也就是说客户端在挥手过程中,如果收到服务端的数据包,那么应用程序在调用 read 函数时,是可以读取到数据的。如果问题中的数据包没有被网络延迟,在第三次挥手之前被客户端收到了,那么客户端就可以在应用程序读取到该数据。所以疑惑就在于,数据包被延迟了,客户端在进入 TIME_WAIT 状态后,收到了服务端的数据包,应用程序还能读取到该数据吗?我先说结论吧,客户端进入 TIME_WAIT 状态后,如果收到了服务端的数据包,客户端的内核会发送该数据包的 ACK 确认报文,然后丢掉该数据包。
我为什么知道呢?因为我去看了下内核的源码,我在这里也带大家分析一遍。Linux 内核在收到 TCP 报文后,会执行 tcp_v4_rcv
函数,在该函数和 TIME_WAIT 状态相关的主要代码如下所示:该代码的过程:
- 接收到SKb包后,会调用__inet_lookup_skb()查找对应的sock结构;
- 如果连接的状态是 TIME_WAIT,会跳转到 do_time_wait 处理;
- 由 tcp_timewait_state_process() 函数来处理 SKB 包,处理后根据返回值来做相应的处理。
在看 tcp_timewait_state_process() 函数中的处理之前,需要先看一看不同的返回值会对应什么样的处理:
- 如果返回值是 TCP_TW_SYN,则说明接收到的是一个「合法」的SYN包(也就是说这个 SYN 包可以接受),这时会首先查找内核中是否有对应的监听套接字,如果存在相应的监听套接字,则会释放TIME_WAIT状态的传输控制结构,跳转到 process 处开始处理,开始建立一个新的连接。如果没有找到监听套接字会执行到 TCP_TW_ACK 分支。
- 如果返回值是TCP_TW_ACK,则会调用 tcp_v4_timewait_ack() 发送ACK,然后跳转到 discard_it,丢掉数据包。
- 如果返回值是TCP_TW_RST,则会调用 tcp_v4_send_reset() 给对端发送 RST 包,然后丢掉数据包。
- 如果返回值是TCP_TW_SUCCESS,则会直接丢掉数据包。
所以,关键的地方就在于,当连接在 TIME_WAIT 状态时,收到了 TCP 数据包,tcp_timewait_state_process() 函数的返回值是什么。tcp_timewait_state_process() 函数处理过程非常的多,我这里就不详细介绍里面的具体处理了,我这里直接说结论。主动关闭方在 TIME_WAIT 状态下,收到了 TCP 数据包的话,tcp_timewait_state_process() 函数会返回 TCP_TW_ACK,所以接下来会调用 tcp_v4_timewait_ack() 发送 ACK,然后跳转到 discard_it,丢掉数据包。可能有的同学会好奇,都要丢弃数据包了,为什么还要回 ACK?我觉得可能是避免对方因为没有收到数据包的确认报文,而触发超时重传包,毕竟已经在挥手的最末尾阶段了,就安安稳稳的结束吧。在 TIME_WAIT 状态下,还有个意思的事情是,当收到「合法」的 SYN 包,并且开启了 sysctl_tcp_tw_reuse 内核参数,就会直接释放掉 TIME_WAIT 的资源,然后进行新建连接的处理。
这次就说到这啦,下次见!