type
Post
status
Published
date
Oct 12, 2025
slug
network1
summary
tags
技术探索
category
icon
password
引言本文不仅为你梳理清晰的状态流转图,更带你深挖 TCP 协议设计的核心逻辑,从根本上理解这些机制为什么不可或缺。
一、 三次握手:不仅仅是“建立连接”
TCP 建立连接的过程被称为三次握手(Three-way Handshake)。在此之前,服务端需要处于
LISTEN 状态,随时准备接受客户端的请求。1. 状态流转与交互图
先来看一张完整的交互时序图:
Code snippet
步骤解析:
- 第一次握手: 客户端发送
SYN报文,携带自己的初始序列号seq=x,进入SYN_SENT状态。
- 第二次握手: 服务端收到后,回复
SYN + ACK报文,其中携带服务端的初始序列号seq=y,并确认收到客户端的序号ack=x+1,进入SYN_RCVD状态。
- 第三次握手: 客户端收到后,再次发送
ACK报文确认ack=y+1,随后双方进入ESTABLISHED状态,连接建立。
2. 核心拷问:为什么必须是三次?两次不行吗?
很多人认为三次握手是为了“确认双方都有收发消息的能力”,这只看到了表象。根据 RFC 793,三次握手的核心设计目的有两个:
- 核心原因一:防止历史连接初始化(最关键的防线)
想象一下,如果网络拥塞,客户端发了一个旧的
SYN(例如因超时重发的第一次SYN),随后又发了一个新的SYN。 如果只有两次握手,服务端一旦收到旧的SYN,就会立刻建立连接并分配资源。这就会导致状态混乱和资源浪费。 而有了三次握手,当服务端回传SYN+ACK时,客户端会校验确认号(ACK Number)。如果发现是历史遗留的、错误的响应,客户端会立即发送RST报文终止该连接,从而保证了连接的正确性。
- 核心原因二:可靠地同步双方的初始序列号(ISN)
TCP 是可靠传输,依赖序列号(Sequence Number)来保证数据的顺序和去重。双方必须相互告知并确认彼此的初始序列号。两次握手只能保证客户端发过去的序列号被服务端确认,但服务端发给客户端的序列号得不到确认。必须通过第三次握手的
ACK来形成闭环。
二、 四次挥手:全双工的“优雅退场”
由于 TCP 是全双工通信(双方可以同时收发数据),关闭连接时不能一脚踢开,必须双方各自完成“不再发送数据”的声明。因此,断开连接的过程比建立连接更加复杂。
1. 状态流转与交互图
假设由客户端主动发起关闭请求:
Code snippet
步骤解析:
- 第一次挥手: 客户端发送
FIN报文,表示“我没有数据要发送了”,进入FIN_WAIT_1状态。
- 第二次挥手: 服务端收到后,回复
ACK确认,进入CLOSE_WAIT状态。此时客户端进入FIN_WAIT_2。(此时连接处于半关闭状态,服务端仍可向客户端发送数据)。
- 第三次挥手: 服务端把剩下的数据处理完并发送完毕后,也会发送一个
FIN报文,进入LAST_ACK状态。
- 第四次挥手: 客户端收到服务端的
FIN,回复ACK确认,进入TIME_WAIT状态。服务端收到后立刻CLOSED。客户端等待 2MSL 后也自动CLOSED。
2. 核心拷问:为什么挥手需要四次?三次不行吗?
之所以需要四次,是由 TCP 的 半关闭(Half-Close) 特性决定的。
当客户端发送
FIN 时,仅仅代表客户端不再发送数据,但它依然可以接收数据。
服务端收到 FIN 必须立刻回复 ACK 以免客户端重发,但服务端此时可能还有数据尚未处理或发送完毕(应用程序缓冲区里还有货)。因此,服务端不能立刻发送自己的 FIN 报文。
服务端必须等自己真正没数据发了,才会调用 close() 发送自己的 FIN。所以,中间的 ACK 和 FIN 是分开两步走的。(注:如果开启了 TCP 延迟确认机制(Delayed ACK),且服务端刚好没有任何剩余数据要发,此时
ACK 和 FIN 确实有可能被合并成一个报文发送,这就成了事实上的“三次挥手”。)三、 灵魂机制:TIME_WAIT 状态的深意
在四次挥手的最后阶段,主动关闭方(通常是客户端)在发送完最后一个
ACK 后,并不会立刻释放连接进入 CLOSED,而是进入了一个特殊的等待状态:TIME_WAIT。并且需要等待 2MSL(Maximum Segment Lifetime,报文最大生存时间)。为什么要等这么久?
1. 保证连接可靠关闭(兜底最后一次 ACK 丢失)
网络是不可靠的,如果客户端发送的第四次挥手的
ACK 报文丢失了怎么办?
此时,服务端会因为超时收不到确认,而重新发送第三次挥手的 FIN 报文。
如果客户端没有 TIME_WAIT 状态直接关闭了,收到重发的 FIN 时就会一脸懵逼,回复一个 RST 错误报文,导致服务端非正常关闭。
等待 2MSL(1个 MSL 留给 ACK 去服务端的时间,1个 MSL 留给 FIN 重传回来的时间)能确保即便最后一次 ACK 丢失,客户端也有机会进行重传,让对方优雅关闭。2. 防止“旧连接的化身”干扰新连接
网络中常常会有被路由器缓存的、因延迟而缓慢到达的数据包(迷走报文)。
等待 2MSL 可以保证本次连接产生的所有报文都在网络中自然消亡。如果不等待,立即在相同的 IP 和端口组合(五元组)上建立新的连接,那么旧连接中姗姗来迟的延迟报文就极有可能会混入新的连接,造成不可预知的数据错乱。
四、 生产环境延伸思考
从理论延伸到实际生产环境遇到的问题:
- 三次握手的隐患 —— SYN Flood 攻击:
如果在公网上,攻击者伪造大量不同的 IP 发送
SYN报文,但故意不完成第三次握手。服务端会维持大量的半连接状态,最终耗尽半连接队列导致正常服务瘫痪。 应对策略: 通常可以通过开启 Linux 系统层面的tcp_syncookies机制来防御,或者在必要时调大tcp_max_syn_backlog参数。
- 四次挥手与并发瓶颈 —— 高并发下的 TIME_WAIT 积压:
如果是一个高并发的“短连接”服务(比如频繁请求第三方 API 的网关),并且总是由服务端主动关闭连接,就会导致服务器上堆积海量的
TIME_WAIT状态。由于端口号有限(通常是几万个),这会导致可用端口耗尽,无法再与下游建立新连接。 应对策略: 可以通过修改内核参数,开启net.ipv4.tcp_tw_reuse(允许重用 TIME_WAIT 的端口),这依赖于 TCP 的时间戳选项(Timestamps)来保证安全性。
结语 TCP 的设计充满了权衡与哲理。它的每一次状态扭转,都不是为了折腾开发者,而是为了在不可靠的物理网络中,建立起一座坚固可靠的通信桥梁。理解这些“为什么”,远比背下“是什么”有价值得多。
分享
