TCP三次握手&四次挥手
TCP 三次握手
- 客户端发送 SYN 包请求连接,服务端返回 SYN+ACK 确认并请求连接,客户端再返回 ACK 确认,双方进入
ESTABLISHED
状态。
graph LR
A[客户端] -->|1、SYN=1, Seq=x| B[服务端]
B -->|2、SYN=1, ACK=1, Seq=y, Ack=x+1| A
A -->|3、ACK=1, Seq=x+1, Ack=y+1| B
A -->|4、ESTABLISHED| C[连接建立完成]
B -->|4、ESTABLISHED| C
作用:建立可靠的双向连接,确保通信双方确认彼此的接收和发送能力。 过程:
- 第一次握手(客户端 → 服务端): 客户端发送带有 SYN=1,Seq=x 的报文,进入 SYN_SENT 状态,请求建立连接。
- 第二次握手(服务端 → 客户端): 服务端收到后,发送 SYN=1,ACK=1,Seq=y,Ack=x+1 的报文,进入 SYN_RCVD 状态,确认客户端请求并发起自身连接请求。
- 第三次握手(客户端 → 服务端): 客户端发送 ACK=1,Seq=x+1,Ack=y+1 的报文,进入 ESTABLISHED 状态。服务端收到后也进入 ESTABLISHED 状态,连接建立完成。
TCP 四次挥手
- 主动关闭方先发送 FIN 包请求关闭,被动关闭方返回 ACK 确认(此时仍可发送剩余数据)。
- 被动关闭方数据发送完毕后,再发送 FIN 包请求关闭,主动关闭方返回 ACK 确认,并进入
TIME_WAIT
状态(等待 2MSL 确保最后一个 ACK 到达),被动关闭方收到后立即关闭。
graph LR
D[主动关闭方] -->|1、FIN=1, Seq=m| E[被动关闭方]
E -->|2、ACK=1, Seq=n, Ack=m+1| D
E -->|3、FIN=1, ACK=1, Seq=p, Ack=m+1| D
D -->|4、ACK=1, Seq=m+1, Ack=p+1| E
D -->|5、TIME_WAIT| F[等待超时后关闭]
E -->|5、CLOSED| G[立即关闭]
作用:终止连接,确保双向数据传输都已完成,资源可安全释放。 过程:
- 第一次挥手(主动关闭方 → 被动关闭方): 主动关闭方发送 FIN=1,Seq=m 的报文,进入 FIN_WAIT_1 状态,请求关闭连接。
- 第二次挥手(被动关闭方 → 主动关闭方): 被动关闭方收到后,发送 ACK=1,Seq=n,Ack=m+1 的报文,进入 CLOSE_WAIT 状态,确认收到关闭请求(此时被动关闭方仍可发送剩余数据)。
- 第三次挥手(被动关闭方 → 主动关闭方): 被动关闭方数据发送完毕后,发送 FIN=1,ACK=1,Seq=p,Ack=m+1 的报文,进入 LAST_ACK 状态,请求关闭自身到主动方的连接。
- 第四次挥手(主动关闭方 → 被动关闭方): 主动关闭方收到后,发送 ACK=1,Seq=m+1,Ack=p+1 的报文,进入 TIME_WAIT 状态。被动关闭方收到后进入 CLOSED 状态,连接彻底关闭。
关键点总结
- 三次握手原因:防止失效的连接请求报文段被误认为是有效请求,避免资源浪费。
- 四次挥手原因:被动关闭方可能有未发送完的数据,需分两步确认(先确认收到关闭请求,再发送自身关闭请求)。
- TIME_WAIT 作用:确保最后一次 ACK 报文成功到达,避免因网络延迟导致被动关闭方重发 FIN 报文。
TIME_WAIT状态:原理与解决
1. 原理
TIME_WAIT
是主动关闭方在发送最后一个ACK
后的状态,持续时间为2MSL
(通常是1-4分钟,取决于系统配置)。
作用:
- 确保被动关闭方收到最终
ACK
:如果ACK
丢失,被动关闭方会重发FIN
,TIME_WAIT
状态的主动方可以再次发送ACK
,避免被动方一直处于LAST_ACK
等待。 - 防止旧报文干扰新连接:
2MSL
足够让网络中所有属于该连接的旧报文段过期,避免其被误认为是新连接的报文(新连接可能复用相同的源IP、端口和目的IP、端口)。
2. 问题场景
当主动关闭方(如高频创建短连接的客户端)频繁关闭连接时,会积累大量TIME_WAIT
状态的连接,可能导致:
- 端口耗尽:每个
TIME_WAIT
连接占用一个本地端口,端口数量有限(默认65535),新连接可能因无端口可用而失败。 - 系统资源消耗:过多
TIME_WAIT
连接会占用内存和文件描述符。
3. 解决方法
(1)内核参数调优(Linux)
通过调整TCP内核参数减少TIME_WAIT
的影响:
net.ipv4.tcp_tw_reuse
:允许重用处于TIME_WAIT
状态的端口(需满足一定条件,如新连接的timestamp大于旧连接),适用于客户端(主动关闭方)。echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse
net.ipv4.tcp_max_tw_buckets
:限制TIME_WAIT
连接的最大数量,超过后会被强制清理(可能影响稳定性,需谨慎)。echo 10000 > /proc/sys/net/ipv4/tcp_max_tw_buckets # 默认通常是180000
- (不推荐)
net.ipv4.tcp_tw_recycle
:开启TIME_WAIT
快速回收,但在NAT网络(如多客户端通过同一网关访问服务器)中会导致连接失败(已在Linux 4.12+移除)。
(2)应用层优化
- 使用长连接:通过复用连接(如HTTP Keep-Alive、TCP长连接)减少连接创建/关闭频率,从根源减少
TIME_WAIT
。 - 调整主动关闭方:让服务器作为主动关闭方(需业务允许),服务器通常有更充足的资源和更易调优的内核参数。
CLOSE_WAIT状态:原理与解决
1. 原理
CLOSE_WAIT
是被动关闭方在收到主动方的FIN
并发送ACK
后的状态,直到被动方自身发送FIN
前一直保持。
本质:被动关闭方的应用层未及时调用close()
(或类似关闭接口),导致内核无法发送FIN
,连接卡在CLOSE_WAIT
。
2. 问题场景
CLOSE_WAIT
状态的持续时间完全由应用层决定:如果应用程序存在bug(如忘记关闭连接、异常处理中未释放连接),CLOSE_WAIT
连接会持续积累,最终耗尽系统文件描述符,导致新连接无法建立。
3. 解决方法
CLOSE_WAIT
的核心问题在应用层,需从代码和监控入手:
-
修复代码逻辑:
- 确保连接不再使用时,显式调用关闭接口(如
socket.close()
、conn.Close()
)。 - 异常场景(如超时、错误)中必须释放连接,避免资源泄漏(可使用
try-finally
或defer
机制)。
- 确保连接不再使用时,显式调用关闭接口(如
-
设置连接超时:
- 对长期空闲的连接,通过超时机制(如
SO_TIMEOUT
、应用层心跳检测)主动关闭,避免永久滞留。
- 对长期空闲的连接,通过超时机制(如
-
监控与告警:
- 通过
netstat -an | grep CLOSE_WAIT | wc -l
或ss -ant | grep CLOSE-WAIT | wc -l
监控数量。 - 当
CLOSE_WAIT
超过阈值(如数百)时触发告警,及时排查代码问题。
- 通过
总结
状态 | 所属角色 | 持续时间 | 核心原因 | 解决思路 |
---|---|---|---|---|
TIME_WAIT |
主动关闭方 | 固定2MSL |
确保报文可靠与避免旧连接干扰 | 内核调优、长连接复用 |
CLOSE_WAIT |
被动关闭方 | 由应用层决定 | 应用层未及时关闭连接 | 修复代码、超时控制、监控告警 |
TIME_WAIT
是TCP设计的必要状态,少量存在是正常的;CLOSE_WAIT
则几乎总是异常,需优先排查应用程序逻辑。