2、网络基础回顾:TCP三次握手与四次挥手,连接是如何建立的?
聊到网络问题,尤其是像“Remote end closed connection without response”这种错误,我们绕不开TCP。说白了,TCP就是互联网的“交通规则”。你想想看,数据包在网络里乱窜,如果没有一套可靠的建立和拆除连接的机制,那通信就乱套了。
我个人习惯,在排查这类连接异常问题时,第一步就是回顾TCP的握手与挥手过程。这不是掉书袋,而是因为很多诡异的问题,根源就出在这几个看似简单的步骤里。
2.1 三次握手:连接是怎么“谈妥”的?
三次握手,就是客户端和服务端在正式传输数据前,互相确认“你准备好了吗?”“我准备好了!”的过程。我把它理解成一次电话通话前的“喂,能听到吗?”
核心目的: 确认双方的接收和发送能力都正常,并同步初始序列号(ISN)。
具体流程是这样的:
- 第一次握手(SYN): 客户端发送一个SYN包(同步序列号),并进入
SYN_SENT状态。这相当于说:“喂,我要找你聊天,这是我的初始序号X。” - 第二次握手(SYN+ACK): 服务端收到后,如果同意建立连接,就回复一个SYN+ACK包。它既确认了客户端的SYN(ACK=X+1),也发送了自己的SYN(序号Y)。服务端进入
SYN_RCVD状态。这相当于说:“收到你的请求了,这是我的序号Y,我也准备好了。” - 第三次握手(ACK): 客户端收到服务端的SYN+ACK后,再发送一个ACK包(ACK=Y+1)。此时,客户端进入
ESTABLISHED状态。服务端收到这个ACK后,也进入ESTABLISHED状态。这相当于说:“好,确认收到你的序号,我们开始聊吧。”
嗯,这里要注意:第三次握手是可以携带数据的。而前两次不行。为什么?因为前两次握手还没确认对方的接收能力,贸然发数据可能会丢。
避坑指南: 我曾经遇到过一个线上问题,客户端频繁报“Connection timed out”。抓包一看,服务端只收到了SYN,却没回复SYN+ACK。后来发现是服务端的半连接队列(syn backlog)被占满了。这就是典型的“第二次握手失败”。
2.2 四次挥手:连接是怎么“体面分手”的?
连接建立后,总得有个结束。四次挥手就是双方优雅地关闭连接的过程。它不像握手那么“爽快”,因为TCP连接是全双工的,两边都得独立地关闭自己的通道。
核心目的: 确保双方都没有数据要发送了,再彻底断开连接,避免数据丢失。
流程如下:
- 第一次挥手(FIN): 主动关闭方(比如客户端)发送一个FIN包,表示“我这边数据发完了,我要关闭连接了”。客户端进入
FIN_WAIT_1状态。 - 第二次挥手(ACK): 被动关闭方(服务端)收到FIN后,回复一个ACK包,表示“知道了,但我可能还有数据要发”。服务端进入
CLOSE_WAIT状态。客户端收到ACK后,进入FIN_WAIT_2状态。 - 第三次挥手(FIN): 当被动关闭方(服务端)的数据也发完了,它发送一个FIN包,表示“我这边也发完了,可以关了”。服务端进入
LAST_ACK状态。 - 第四次挥手(ACK): 主动关闭方(客户端)收到FIN后,回复一个ACK包,并进入
TIME_WAIT状态。服务端收到ACK后,关闭连接。客户端在TIME_WAIT状态等待2MSL(最大报文段生存时间)后,才彻底关闭。
注意: 你可能会问,为什么客户端要等2MSL?说白了,就是为了确保最后一个ACK能到达服务端。如果ACK丢了,服务端会重发FIN,客户端得能再次响应。我见过不少因为TIME_WAIT过多导致端口耗尽的案例,尤其是在高并发的短连接场景下。
2.3 状态变迁与核心逻辑
为了让你更直观地理解,我画了一张流程图。这张图我每次排查网络问题时都会在脑子里过一遍,非常有用。
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”这种错误,就只能瞎猜了。