下图展示了TCP从建立连接(三次握手)、数据传输到断开连接(四次挥手)的完整过程。
| 状态 | 描述 | 触发条件 | 下一个状态 |
|---|---|---|---|
| CLOSED | 初始状态,连接不存在。 | 客户端应用发起连接请求。 | SYN_SENT |
| LISTEN | 服务器端等待连接请求。 | 服务器应用启动并监听端口。 | LISTEN |
| SYN_SENT | 客户端已发送SYN报文,等待服务器确认。 | 发送SYN报文。 | SYN_RCVD (收到SYN+ACK) / CLOSED (超时) |
| SYN_RCVD | 服务器已收到SYN并发送了SYN+ACK,等待客户端确认。 | 收到客户端的SYN报文。 | ESTABLISHED (收到ACK) / CLOSED (超时) |
| ESTABLISHED | 连接已建立,可以进行数据传输。 | 完成三次握手。 | FIN_WAIT_1 / CLOSE_WAIT |
| 状态 | 描述 | 触发条件 | 下一个状态 |
|---|---|---|---|
| ESTABLISHED | 连接处于活动状态。 | 应用请求关闭连接 (主动关闭方)。 | FIN_WAIT_1 |
| FIN_WAIT_1 | 主动关闭方,已发送FIN,等待对方ACK。 | 发送FIN报文。 | FIN_WAIT_2 (收到ACK) / CLOSING (收到FIN) |
| FIN_WAIT_2 | 主动关闭方,已收到对方ACK,等待对方FIN。 | 收到对己方FIN的ACK。 | TIME_WAIT (收到FIN) |
| CLOSE_WAIT | 被动关闭方,已收到FIN,等待应用层关闭。 | 收到对端的FIN报文。 | LAST_ACK |
| CLOSING | 双方同时关闭连接的特殊情况。 | 在FIN_WAIT_1状态下收到了FIN报文。 | TIME_WAIT |
| LAST_ACK | 被动关闭方,已发送FIN,等待最后一个ACK。 | 应用层关闭后,发送FIN报文。 | CLOSED |
| TIME_WAIT | 主动关闭方,等待2*MSL时间,确保连接正常关闭。 | 收到对方的FIN并发送了ACK。 | CLOSED (等待2*MSL超时) |
| CLOSED | 连接已完全关闭。 | 从LAST_ACK或TIME_WAIT转换而来。 | - |
现象描述:
服务器端存在大量处于
SYN_RCVD
状态的连接。攻击者伪造源IP地址发送大量SYN包,服务器回应SYN+ACK后,由于源IP是假的,永远等不到客户端的ACK确认,导致服务器的半连接队列被占满,无法处理正常的连接请求。
排查方法 (Linux):
# 查看SYN_RCVD状态的连接数量
netstat -n -p TCP | grep SYN_RCVD | wc -l
# 或者使用ss命令,效率更高
ss -n state syn-recv | wc -l
# 查看哪些IP在大量连接
netstat -nt | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -nr | head
解决方法:
调整内核参数进行防御(需要修改
/etc/sysctl.conf 文件并执行
sysctl -p 使其生效)。
# 1. 启用SYN Cookies,有效防御SYN-Flood攻击
net.ipv4.tcp_syncookies = 1
# 2. 增大半连接队列 (SYN Backlog)
# 默认值可能较小,如1024。在高并发场景下可以适当增大
net.ipv4.tcp_max_syn_backlog = 2048
# 3. 减少重试次数
# 减少SYN+ACK的重传次数,尽快释放资源
net.ipv4.tcp_synack_retries = 2
现象描述:
客户端连接超时(Connection Timed Out),但服务端端口处于LISTEN状态。这是因为三次握手已经完成,但服务端的accept队列(全连接队列)已满,导致内核无法将已完成的连接移交给应用程序处理。内核会默认丢弃这些连接。
排查方法 (Linux):
# 查看全连接队列溢出情况
# 如果 `ListenDrops` 或 `ListenOverflows` 列的数值持续增长,说明队列满了
netstat -s | grep "listen drops"
# 或
nstat -az | grep ListenDrops
# 查看当前队列大小和最大值
ss -lnt
# Recv-Q: 当前全连接队列中的连接数
# Send-Q: 全连接队列的最大容量
解决方法:
1. 增大内核参数: 修改
/etc/sysctl.conf 并执行
sysctl -p。
# 增大somaxconn参数,这是全连接队列的全局最大值
net.core.somaxconn = 65535
2. 修改应用程序: 在调用
listen() 函数时,传入更大的
backlog 参数。注意,这个值不能超过
net.core.somaxconn 的限制。
3. 优化应用性能: 检查应用程序为何处理连接如此之慢,是否CPU、I/O或锁等存在瓶颈。
现象描述:
产生原因:
TIME_WAIT 状态是由**主动关闭**连接的一方进入的。在TCP四次挥手过程中,主动关闭方在发送完最后一个ACK报文后,会进入TIME_WAIT状态。它不会立即关闭连接,而是会“等待”一段时间。
状态作用 (为何需要等待):
这个等待期(2*MSL,通常为1-4分钟)至关重要,主要有两个目的:
1. 确保连接可靠关闭: 如果主动方发送的最后一个ACK报文在网络中丢失,被动关闭方会因为没有收到确认而重发FIN报文。由于主动方仍处于TIME_WAIT状态,它可以重新发送ACK,从而使对方能够正常关闭,防止其长时间停留在LAST_ACK状态。
2. 防止旧连接的“迷途报文”干扰新连接: 等待2*MSL可以确保本次连接中产生的所有报文都已在网络中消失。这样,即使稍后在同一个端口上立即建立一个新连接,也不会收到上一次连接遗留下来的、迟到的数据包,避免了数据混淆。
在高并发短连接的场景(如HTTP服务器),由于频繁地主动关闭连接,就可能积压大量 TIME_WAIT 状态的连接,占用端口和内存资源。
排查方法 (Linux):
# 查看TIME_WAIT状态的连接数量
netstat -n | grep TIME_WAIT | wc -l
# 或
ss -n state time-wait | wc -l
解决方法 (内核参数优化):
修改 /etc/sysctl.conf 并执行
sysctl -p。
# 1. 开启TIME_WAIT状态连接的复用
# 允许将TIME_WAIT sockets重新用于新的TCP连接,这个参数是推荐开启的
net.ipv4.tcp_tw_reuse = 1
# 2. 开启TIME_WAIT快速回收 (慎用!)
# 在NAT环境下可能导致问题,不推荐在公网服务器上开启
# net.ipv4.tcp_tw_recycle = 1 (Linux 4.12后已移除)
# 3. 缩短TIME_WAIT超时时间 (不推荐)
# 这样做违反TCP/IP协议,可能导致数据包在网络中迷路后被新连接接收
# net.ipv4.tcp_fin_timeout = 30 (这是FIN_WAIT_2的超时,不是TIME_WAIT)
最佳实践: 通常开启
tcp_tw_reuse
是最安全且有效的方法。同时,考虑使用HTTP
Keep-Alive(长连接)来减少频繁的建连和断连。
现象描述:
CLOSE_WAIT
状态出现在被动关闭方。当收到对方的FIN后,TCP层会回应ACK并进入CLOSE_WAIT状态,此时需要等待应用程序调用
close()
来发送自己的FIN。如果应用层代码有Bug(如忘记关闭socket),连接就会一直停留在CLOSE_WAIT,最终耗尽文件描述符。
排查方法 (Linux):
# 查看CLOSE_WAIT状态的连接数量和对应的进程
netstat -antp | grep CLOSE_WAIT
# 或
ss -antp | grep CLOSE_WAIT
解决方法:
这是应用程序的Bug,不是内核参数问题!
1. 定位问题代码: 使用
netstat 或 ss 命令找到处于
CLOSE_WAIT 状态的连接属于哪个进程 (PID)。
2.
审查代码:
检查该进程的代码逻辑,确保在所有分支(包括异常处理)中,使用完毕的socket都被正确关闭了。例如,在Java中,要确保
socket.close() 在
finally 块中被调用。
3. 资源管理: 确保代码没有文件描述符泄漏的问题。