2、网络基础回顾:TCP三次握手与四次挥手,连接是如何建立的?

聊到网络问题,尤其是像“Remote end closed connection without response”这种错误,我们绕不开TCP。说白了,TCP就是互联网的“交通规则”。你想想看,数据包在网络里乱窜,如果没有一套可靠的建立和拆除连接的机制,那通信就乱套了。

我个人习惯,在排查这类连接异常问题时,第一步就是回顾TCP的握手与挥手过程。这不是掉书袋,而是因为很多诡异的问题,根源就出在这几个看似简单的步骤里。

2.1 三次握手:连接是怎么“谈妥”的?

三次握手,就是客户端和服务端在正式传输数据前,互相确认“你准备好了吗?”“我准备好了!”的过程。我把它理解成一次电话通话前的“喂,能听到吗?”

核心目的: 确认双方的接收和发送能力都正常,并同步初始序列号(ISN)。

具体流程是这样的:

  1. 第一次握手(SYN): 客户端发送一个SYN包(同步序列号),并进入SYN_SENT状态。这相当于说:“喂,我要找你聊天,这是我的初始序号X。”
  2. 第二次握手(SYN+ACK): 服务端收到后,如果同意建立连接,就回复一个SYN+ACK包。它既确认了客户端的SYN(ACK=X+1),也发送了自己的SYN(序号Y)。服务端进入SYN_RCVD状态。这相当于说:“收到你的请求了,这是我的序号Y,我也准备好了。”
  3. 第三次握手(ACK): 客户端收到服务端的SYN+ACK后,再发送一个ACK包(ACK=Y+1)。此时,客户端进入ESTABLISHED状态。服务端收到这个ACK后,也进入ESTABLISHED状态。这相当于说:“好,确认收到你的序号,我们开始聊吧。”

嗯,这里要注意:第三次握手是可以携带数据的。而前两次不行。为什么?因为前两次握手还没确认对方的接收能力,贸然发数据可能会丢。

避坑指南: 我曾经遇到过一个线上问题,客户端频繁报“Connection timed out”。抓包一看,服务端只收到了SYN,却没回复SYN+ACK。后来发现是服务端的半连接队列(syn backlog)被占满了。这就是典型的“第二次握手失败”。

2.2 四次挥手:连接是怎么“体面分手”的?

连接建立后,总得有个结束。四次挥手就是双方优雅地关闭连接的过程。它不像握手那么“爽快”,因为TCP连接是全双工的,两边都得独立地关闭自己的通道。

核心目的: 确保双方都没有数据要发送了,再彻底断开连接,避免数据丢失。

流程如下:

  1. 第一次挥手(FIN): 主动关闭方(比如客户端)发送一个FIN包,表示“我这边数据发完了,我要关闭连接了”。客户端进入FIN_WAIT_1状态。
  2. 第二次挥手(ACK): 被动关闭方(服务端)收到FIN后,回复一个ACK包,表示“知道了,但我可能还有数据要发”。服务端进入CLOSE_WAIT状态。客户端收到ACK后,进入FIN_WAIT_2状态。
  3. 第三次挥手(FIN): 当被动关闭方(服务端)的数据也发完了,它发送一个FIN包,表示“我这边也发完了,可以关了”。服务端进入LAST_ACK状态。
  4. 第四次挥手(ACK): 主动关闭方(客户端)收到FIN后,回复一个ACK包,并进入TIME_WAIT状态。服务端收到ACK后,关闭连接。客户端在TIME_WAIT状态等待2MSL(最大报文段生存时间)后,才彻底关闭。

注意: 你可能会问,为什么客户端要等2MSL?说白了,就是为了确保最后一个ACK能到达服务端。如果ACK丢了,服务端会重发FIN,客户端得能再次响应。我见过不少因为TIME_WAIT过多导致端口耗尽的案例,尤其是在高并发的短连接场景下。

2.3 状态变迁与核心逻辑

为了让你更直观地理解,我画了一张流程图。这张图我每次排查网络问题时都会在脑子里过一遍,非常有用。

客户端 服务端 CLOSED SYN_SENT ESTABLISHED FIN_WAIT_1 TIME_WAIT LISTEN SYN_RCVD ESTABLISHED CLOSE_WAIT LAST_ACK ① SYN (seq=x) ② SYN+ACK (seq=y, ack=x+1) ③ ACK (ack=y+1) ④ FIN (seq=u) ⑤ ACK (ack=u+1) ⑥ FIN (seq=v) ⑦ ACK (ack=v+1)

2.4 常见异常与排查思路

理解了上面的流程,我们再回头看“Remote end closed connection without response”这个错误。它通常意味着:连接在建立后,或者数据传输过程中,被远端突然关闭了,而且没有发送任何FIN或RST包。

我总结了几种常见场景:

异常现象 可能原因 排查方向
连接建立后立即断开 服务端应用层主动关闭,或防火墙/负载均衡器拦截 检查服务端日志、抓包分析RST包来源
数据传输中突然断开 服务端进程崩溃、Keep-Alive超时、网络中间设备重置 查看服务端核心转储、调整TCP Keep-Alive参数
大量TIME_WAIT 短连接场景下,主动关闭方频繁创建和销毁连接 考虑使用连接池、调整tcp_tw_reuse(注意内核版本)
大量CLOSE_WAIT 被动关闭方(通常是服务端)没有正确调用close() 检查代码中socket的关闭逻辑,是否存在资源泄漏

我的经验: 有一次排查一个微服务间的调用超时问题,发现服务端一直处于CLOSE_WAIT状态。最后定位到是开发同学在捕获异常后,忘记在finally块里关闭socket。你看,一个简单的疏忽,就能导致连接池被耗尽。

好了,TCP的握手和挥手就回顾到这里。这些基础概念,是理解后面所有高级排查技巧的基石。你想想看,如果连连接是怎么建立和关闭的都不清楚,那遇到“Remote end closed”这种错误,就只能瞎猜了。

可可精品资料库