前言
TCP 三次握手和四次挥手是网络基础知识中的基础,面试时能背出来的人很多,但真正遇到生产环境问题需要排查时,很多人就傻眼了。比如:连接建立后客户端马上断开是怎么回事?为什么 TIME_WAIT 状态的连接这么多?服务端为什么会产生大量 CLOSE_WAIT?RST 报文什么时候会出现?
这些问题靠背答案是无济于事的,必须理解 TCP 状态转换的每个细节、以及内核参数对 TCP 行为的影响,才能在实际场景中快速定位问题。
本文从实际运维角度出发,详细解析 TCP 状态转换、握手挥手的每个阶段、内核参数的影响、以及常见问题的排查方法。
1 TCP 协议基础回顾
1.1 TCP 头部结构
理解 TCP 状态机之前,先看一下 TCP 头部的关键字段:
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Source Port | Destination Port | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Sequence Number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Acknowledgment Number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Offset| Reserved |Flags| Window | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Checksum | Urgent Pointer | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Options | Padding | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | data | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
关键字段说明:
Sequence Number(序列号):本报文的第一个数据字节的序号。用于保证数据按序到达。
Acknowledgment Number(确认号):期望收到的下一个字节的序号。表示确认收到对方的数据。
Flags(标志位):SYN、ACK、FIN、RST、PSH、URG
Window(窗口大小):接收端能接收的数据量,用于流量控制
1.2 TCP 状态一览
客户端状态: 服务端状态: CLOSED LISTEN | | | ---- SYN ----> | | <---- SYN-ACK ---- | | <---- ACK ----> | SYN_SENT SYN_RCVD | | | | |============== 数据传输 ==============> | | | ---- FIN ----> | | <---- ACK ---- | FIN_WAIT_1 CLOSE_WAIT | | | <---- ACK ---- | FIN_WAIT_2 LAST_ACK | | | <---- FIN ---- | | ---- ACK ----> | TIME_WAIT (消失) | | | 等待 2MSL | | | CLOSED CLOSED
各状态详细说明:
| 状态 | 含义 | 常见场景 |
|---|---|---|
| CLOSED | 连接关闭 | 初始状态或连接完全关闭 |
| LISTEN | 监听中 | 服务端等待连接请求 |
| SYN_SENT | 已发送 SYN | 客户端已发起连接请求 |
| SYN_RCVD | 已收到 SYN | 服务端已收到并回复 |
| ESTABLISHED | 已建立 | 正常数据传输状态 |
| FIN_WAIT_1 | 已发送 FIN | 主动关闭方等待对方 ACK |
| FIN_WAIT_2 | 已收到 ACK | 主动关闭方等待对方 FIN |
| CLOSE_WAIT | 等待关闭 | 被动关闭方等待应用关闭 |
| CLOSING | 双方同时关闭 | 双方同时发送 FIN |
| LAST_ACK | 最后确认 | 被动关闭方等待最后 ACK |
| TIME_WAIT | 等待超时 | 主动关闭方等待 2MSL |
2 三次握手详解
2.1 握手过程
客户端 服务端 | | | --------- SYN=1, Seq=x --------> | 第一次握手 | | | <------ SYN=1, ACK=1, Seq=y --- | 第二次握手 | Ack=x+1 | | | | ------- ACK=1, Seq=x+1 ------> | 第三次握手 | Ack=y+1 | | | |========= ESTABLISHED ===========| | |
第一次握手:
客户端发送 TCP 报文,SYN=1,Seq=x
SYN=1表示这是一个连接请求
Seq=x是初始序列号(ISN),随机生成
发送后客户端进入SYN_SENT状态
第二次握手:
服务端收到请求后,回复 SYN+ACK
SYN=1, ACK=1
Seq=y是服务端的初始序列号
Ack=x+1表示期望收到的下一个字节是 x+1
服务端进入SYN_RCVD状态
第三次握手:
客户端收到服务端的 SYN+ACK 后,发送 ACK
ACK=1, Seq=x+1, Ack=y+1
此时客户端进入ESTABLISHED状态
服务端收到 ACK 后也进入ESTABLISHED状态
2.2 为什么是三次?
核心原因:TCP 是全双工协议,需要双向确认。
两次握手的问题:
假设客户端发送了第一个 SYN 请求,这个请求在网络中滞留了。客户端等不到响应,又发送了一个 SYN 并完成连接,然后断开。此时第一个延迟的 SYN 到达服务端,如果只有两次握手,服务端会误以为客户端又要建立连接,从而建立无效连接浪费资源。
三次握手时,服务端需要在收到客户端的第三次 ACK 后才真正建立连接,这样延迟的 SYN 不会被当作有效连接请求。
四次握手可以吗?
可以,但没必要。三次已经足够建立可靠连接,四次只是浪费网络资源。第二次握手的 SYN+ACK 可以合并成一次报文发送,所以天然就是三次。
2.3 ISN 为什么要随机?
如果 ISN 是固定的(比如每次都从 0 开始),攻击者就可以伪造 IP 和序列号建立连接。随机 ISN 使得攻击者难以预测下一个连接的序列号,从而防止 TCP Sequence Prediction 攻击。
查看当前连接 ISN:
# 查看当前 TCP 连接的序列号 cat /proc/net/tcp cat /proc/net/tcp6 # 使用 ss 查看详细信息 ss -ti state established # 使用 netstat 查看 netstat -tn | grep ESTABLISHED
2.4 MSS 与窗口缩放
MSS(Maximum Segment Size):最大报文段大小,指 TCP 载荷的最大字节数,不包含 TCP 头部。
MSS = MTU - IP头部 - TCP头部 典型值:MTU=1500, MSS=1460
窗口缩放(Window Scaling):当接收窗口大于 65535 字节时,需要使用窗口缩放因子。RFC 1323 定义了这个扩展。
# 查看窗口缩放因子 cat /proc/sys/net/ipv4/tcp_window_scaling # 查看缓冲设置 cat /proc/sys/net/ipv4/rmem cat /proc/sys/net/ipv4/wmem # 临时调整窗口缩放因子 sysctl -w net.ipv4.tcp_window_scaling=1
3 四次挥手详解
3.1 挥手过程
客户端 服务端 | | |========= ESTABLISHED ===========| | | | ------ FIN=1, Seq=u --------> | 第一次挥手 | <----- ACK=1, Ack=u+1 -------- | FIN_WAIT_1 CLOSE_WAIT | | | <---- ACK=1 ----------------- | FIN_WAIT_2 | | | | <---- FIN=1, Seq=v ----------- | 第二次挥手 | ------ ACK=1, Ack=v+1 ------> | (此时服务端可以继续发送数据) TIME_WAIT LAST_ACK | | | 等待 2MSL | | (确保对方收到最后的 ACK) | | | CLOSED CLOSED
第一次挥手:主动关闭方发送 FIN,进入 FIN_WAIT_1 状态
第二次挥手:被动关闭方收到 FIN 后回复 ACK,进入 CLOSE_WAIT 状态。被动关闭方的应用可能还需要发送剩余数据。
第三次挥手:被动关闭方完成数据发送后,发送 FIN,进入 LAST_ACK 状态
第四次挥手:主动关闭方收到 FIN 后回复 ACK,进入 TIME_WAIT 状态
3.2 TIME_WAIT 状态详解
TIME_WAIT 状态持续时间:2MSL(Maximum Segment Lifetime)
MSL 是 TCP 报文在网络中存在的最大时间,通常是 60 秒(在 Linux 中可以通过net.ipv4.tcp_fin_timeout调整,实际最大值是 60 秒)。
所以 TIME_WAIT 状态持续120 秒(某些系统可能是 60 秒或 30 秒,取决于实现)。
TIME_WAIT 的两个作用:
确保最后的 ACK 能到达被动关闭方:如果第四次挥手的 ACK 丢失,被动关闭方会重发 FIN,主动关闭方在 TIME_WAIT 状态下能响应这个重发的 FIN。
让旧连接的报文在网络中消散:在 2MSL 时间内,同一连接的旧报文会从网络中消失,新连接不会被旧报文干扰。
Linux 内核参数:
# 查看 TIME_WAIT 超时时间(秒) cat /proc/sys/net/ipv4/tcp_fin_timeout # 默认:60 # 查看当前 TIME_WAIT 连接数 ss -tan state time-wait | wc -l netstat -an | grep TIME_WAIT | wc -l # 查看各状态的连接数 ss -s
TIME_WAIT 过多的处理方法:
检查是否为正常现象:高并发短连接服务产生大量 TIME_WAIT 是正常的
启用 tcp_tw_reuse:
# 允许将 TIME_WAIT 状态的 socket 重用于新连接 cat /proc/sys/net/ipv4/tcp_tw_reuse # 默认:0,生产环境建议设置为 1 # 临时开启 sysctl -w net.ipv4.tcp_tw_reuse=1 # 永久生效(写入 /etc/sysctl.conf) echo"net.ipv4.tcp_tw_reuse = 1">> /etc/sysctl.conf sysctl -p
调整 tcp_max_tw_buckets:
# TIME_WAIT 状态连接数的上限 cat /proc/sys/net/ipv4/tcp_max_tw_buckets # 默认:262144 # 超出会直接丢弃而不是放入 TIME_WAIT sysctl -w net.ipv4.tcp_max_tw_buckets=100000
启用 tcp_timestamps:
# tcp_tw_reuse 需要 tcp_timestamps 启用才能工作 cat /proc/sys/net/ipv4/tcp_timestamps # 默认:1 # 确保开启 sysctl -w net.ipv4.tcp_timestamps=1
使用 SO_LINGER 强制关闭(慎用):
# 结构体
struct linger {
int l_onoff; /* non-zero to linger */
int l_linger; /* linger time */
};
# 设置 linger 为 0,等价于 close() 时发送 RST 而不是走完四次挥手
setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &linger, sizeof(linger));
3.3 CLOSE_WAIT 状态详解
CLOSE_WAIT 过多的原因:应用程序没有正确调用 close() 或 shutdown()
# 查看 CLOSE_WAIT 连接
ss -tan state close-wait
# 查看各状态的详细统计
ss -tan | awk'{print $1}'| sort | uniq -c | sort -rn
# 查看哪些进程有 CLOSE_WAIT 连接
ss -tup
常见原因:
代码问题:HTTP 长连接没有设置超时,客户端断开后服务端没有及时关闭 socket
连接池问题:连接池配置不当,连接用完没有归还
异常处理不当:异常发生时跳过了 close() 调用
排查思路:
# 1. 查看 CLOSE_WAIT 的详细信息 ss -tup state close-wait # 2. 查看进程打开的文件描述符 lsof -p# 3. 查看应用的连接配置 # 检查连接池大小、超时设置、close() 是否被正确调用 # 4. 如果是 Java 应用,查看堆外内存和连接状态 jstack # 查看线程状态和锁等待
3.4 RST 报文
RST(Reset)是一种特殊标志,表示连接异常终止,不需要对方确认。
发送 RST 的情况:
目标端口没有监听:服务器收到 SYN 但端口没有进程监听,回复 RST
# 模拟:连接一个未监听的端口 nc -zv 127.0.0.1 9999 # Connection refused,说明收到了 RST
SO_LINGER 设置为 0:调用 close() 时直接发送 RST
收到无法识别的报文:序列号不在接收窗口内
应用程序异常终止:进程被 kill,内核会发送 FIN,但如果发送缓冲区还有数据,可能会发送 RST
查看 RST 报文:
# 抓取 RST 报文 tcpdump -i eth0'tcp[tcpflags] == tcp-rst' # 统计 RST 数量 netstat -s | grep -i"reset" # 查看 TCP 错误统计 cat /proc/net/netstat | grep TcpExt ss -s
4 tcpdump 抓包分析
4.1 基本抓包
# 抓取所有 TCP 流量 tcpdump -i eth0 tcp # 抓取特定端口的流量 tcpdump -i eth0 port 80 tcpdump -i eth0 port 3306 # 抓取特定主机的流量 tcpdump -i eth0 host 192.168.1.100 # 抓取特定端口和主机的组合 tcpdump -i eth0 port 80 and host 192.168.1.100 # 不解析 IP,反显示原始 IP tcpdump -i eth0 -n # 显示完整包内容(十六进制) tcpdump -i eth0 -X # 显示时间戳 tcpdump -i eth0 -tttt # 抓取指定数量的包后退出 tcpdump -i eth0 -c 100 # 保存到文件 tcpdump -i eth0 -w capture.pcap # 读取保存的抓包文件 tcpdump -r capture.pcap # 读取时过滤 tcpdump -r capture.pcap'tcp[13] & 2 != 0'
4.2 TCP 标志位过滤
TCP 头部的第 13 个字节包含 8 个标志位:
|FIN|SYN|RST|PSH|ACK|URG|ECE|CWR| 7 6 5 4 3 2 1 0
# 抓取 SYN 包 tcpdump -i eth0'tcp[13] & 2 != 0' # 抓取 SYN-ACK 包 tcpdump -i eth0'tcp[13] = 18' # 抓取 FIN 包 tcpdump -i eth0'tcp[13] & 1 != 0' # 抓取 RST 包 tcpdump -i eth0'tcp[13] & 4 != 0' # 抓取 ACK 包(排除 SYN) tcpdump -i eth0'tcp[13] & 16 != 0' # 抓取所有握手和挥手包 tcpdump -i eth0'tcp[tcpflags] & (tcp-syn|tcp-fin|tcp-rst) != 0'
4.3 三次握手抓包示例
# 在服务端抓包 tcpdump -i eth0 -nn'port 8080'-w handshake.pcap # 或者实时显示 tcpdump -i eth0 -nn -tttt'port 8080'| grep -E"(SYN|ACK|FIN)"
正常三次握手输出:
1023.456789 IP 192.168.1.10.45678 > 192.168.1.20.8080: Flags [S], seq 1000, win 65535, options [mss 1460], length 0 1023.456792 IP 192.168.1.20.8080 > 192.168.1.10.45678: Flags [S.], seq 2000, ack 1001, win 65535, options [mss 1460], length 0 1023.457001 IP 192.168.1.10.45678 > 192.168.1.20.8080: Flags [.], ack 2001, win 65535, length 0
flags 说明:
[S]:SYN
[S.]:SYN-ACK(. 表示 ACK)
[.]:纯 ACK
[F]:FIN
[R.]:RST-ACK
4.4 四次挥手抓包示例
# 抓取挥手过程 tcpdump -i eth0 -nn'port 8080 and (tcp[tcpflags] & (tcp-fin|tcp-syn|tcp-rst) != 0)'
正常四次挥手输出:
1001.123456 IP 192.168.1.10.45678 > 192.168.1.20.8080: Flags [F.], seq 5000, ack 8000, win 65535, length 0 1001.123500 IP 192.168.1.20.8080 > 192.168.1.10.45678: Flags [.], ack 5001, win 65535, length 0 1001.234567 IP 192.168.1.20.8080 > 192.168.1.10.45678: Flags [F.], seq 8000, ack 5001, win 65535, length 0 1001.234600 IP 192.168.1.10.45678 > 192.168.1.20.8080: Flags [.], ack 8001, win 65535, length 0
5 TCP 半连接与全连接队列
5.1 半连接队列(SYN Queue)
当服务端收到客户端的 SYN 后,进入 SYN_RCVD 状态,此时连接放在半连接队列中。
相关内核参数:
# 半连接队列最大长度 cat /proc/sys/net/ipv4/tcp_max_syn_backlog # 默认:128(实际效果受 somaxconn 限制) # 查看当前半连接队列使用情况 ss -ltn state syn-recv
查看 SYN Flood 攻击:
# 查看是否有 SYN 攻击 netstat -s | grep -i"SYN" ss -ltn state syn-recv | wc -l # 如果半连接队列满,新连接会被丢弃 # 查看丢弃计数 cat /proc/net/netstat | grep -i"SYN"
5.2 全连接队列(Accept Queue)
当三次握手完成后,连接从半连接队列移到全连接队列,等待应用调用 accept()。
相关内核参数:
# 全连接队列最大长度(对应 listen backlog) cat /proc/sys/net/core/somaxconn # 默认:128 # 查看当前全连接队列使用情况 ss -ltn state listen # 观察 Recv-Q 和 Send-Q ss -ltn # Recv-Q:全连接队列当前使用数 # Send-Q:全连接队列最大长度(已接受但未 accept 的连接)
全连接队列溢出的表现:
# 查看溢出统计 netstat -s | grep -i"overflow" # 查看 accept 延迟 ss -ti state established | grep -i"delay" # 抓包观察是否有连接建立后立即 RST tcpdump -i eth0'tcp[tcpflags] & tcp-rst != 0'
5.3 队列大小配置建议
# Nginx 配置(nginx.conf)
server {
listen 8080 backlog=65535;
}
# Golang 配置
ln, _ := net.Listen("tcp",":8080")
// listen backlog 默认是系统 somaxconn
# Java 配置(Tomcat)
# Python 配置
import socket
s = socket.socket()
s.listen(2048) # backlog 参数
生产环境建议:
tcp_max_syn_backlog:至少 2048
somaxconn:至少 2048
应用 listen backlog:至少 1024
# 调整参数 sysctl -w net.ipv4.tcp_max_syn_backlog=2048 sysctl -w net.core.somaxconn=2048
6 生产环境常见问题与排查
6.1 连接建立后立即断开
现象:客户端发起连接后,收到服务端的 RST。
可能原因:
服务端端口没有监听
服务端队列已满,来不及 accept
防火墙拦截
服务端进程崩溃
排查步骤:
# 1. 检查端口是否监听 ss -tlnp | grep 8080 # 2. 检查防火墙规则 iptables -L -n firewall-cmd --list-all # 3. 检查 TCP 状态 ss -ti state established # 4. 抓包分析 tcpdump -i eth0 port 8080 -nn # 5. 检查进程状态 systemctl status nginx ps aux | grep nginx
6.2 服务端产生大量 CLOSE_WAIT
现象:很多连接处于 CLOSE_WAIT 状态。
排查步骤:
# 1. 查看 CLOSE_WAIT 详情 ss -tup state close-wait # 2. 查看对应的进程 ss -tup | grep CLOSE_WAIT # 3. 检查进程的线程/连接处理 # Java: jstack # Python: strace -p # Go: runtime/pprof # 4. 检查连接超时配置 # 确保客户端断开后服务端能检测到 # 检查 keepalive 配置
解决方案:
检查应用代码,确保正确调用 close()
设置合理的 SO_TIMEOUT
启用 TCP keepalive
使用连接池并正确配置归还逻辑
6.3 TIME_WAIT 连接过多
现象:大量连接处于 TIME_WAIT 状态。
排查步骤:
# 1. 查看各状态的数量
ss -s
# 2. 查看 TIME_WAIT 详情
ss -tan state time-wait
# 3. 查看连接来源
ss -tan state time-wait | awk'{print $4}'| cut -d: -f1 | sort | uniq -c | sort -rn
解决方案:
# 1. 启用 tcp_tw_reuse sysctl -w net.ipv4.tcp_tw_reuse=1 # 2. 调整 FIN 超时 sysctl -w net.ipv4.tcp_fin_timeout=30 # 3. 增加 tcp_max_tw_buckets sysctl -w net.ipv4.tcp_max_tw_buckets=100000 # 4. 如果是 HTTP 服务,考虑使用 HTTP keep-alive # 或者升级到 HTTP/2、WebSocket # 5. 客户端使用连接池复用连接
6.4 端口被占满
现象:无法建立新连接,报错 "Cannot assign requested address" 或 "Address already in use"。
原因:客户端作为连接发起方,每次连接会占用一个临时端口(通常是 32768-60999)。
# 查看可用端口范围
cat /proc/sys/net/ipv4/ip_local_port_range
# 默认:32768 60999
# 查看已使用的连接数
ss -tan | awk'{print $4}'| grep -E":[0-9]+$"| wc -l
# 查看每个 IP 的连接数
ss -tan | awk'{print $4}'| cut -d: -f1 | sort | uniq -c | sort -rn | head -20
解决方案:
# 1. 扩大端口范围 sysctl -w net.ipv4.ip_local_port_range="1024 65535" # 2. 启用 TIME_WAIT 复用 sysctl -w net.ipv4.tcp_tw_reuse=1 # 3. 减少 TIME_WAIT 超时 sysctl -w net.ipv4.tcp_fin_timeout=15 # 4. 客户端使用长连接而不是短连接
6.5 半打开连接(Half-Open)
现象:一方崩溃后,另一方不知道连接已失效。
原因:没有心跳机制检测对端存活。
解决方案:启用 TCP Keepalive
# 查看当前 keepalive 设置 cat /proc/sys/net/ipv4/tcp_keepalive_time # 默认:7200 秒(2小时) # 查看 keepalive 探针参数 cat /proc/sys/net/ipv4/tcp_keepalive_intvl # 默认:75 秒 cat /proc/sys/net/ipv4/tcp_keepalive_probes # 默认:9 次 # 调整 keepalive 参数 sysctl -w net.ipv4.tcp_keepalive_time=600 # 10分钟开始探测 sysctl -w net.ipv4.tcp_keepalive_intvl=30 # 每30秒探测一次 sysctl -w net.ipv4.tcp_keepalive_probes=5 # 探测5次失败后断开 # 永久生效 cat >> /etc/sysctl.conf <
应用层也可以实现心跳:
# Python heartbeat 示例 importsocket importtime defheartbeat(sock, interval=30): whileTrue: try: sock.send(b'PING') sock.recv(1024) except: # 连接已断开 reconnect() time.sleep(interval)
7 连接状态监控脚本
7.1 监控 TCP 连接状态统计
#!/bin/bash # filename: tcp_status_monitor.sh # 监控 TCP 连接状态,发现异常及时告警 # 输出统计 echo"=== TCP 连接状态统计 ===" ss -tan | awk'{print $1}'| sort | uniq -c | sort -rn echo"" echo"=== TIME_WAIT 连接数 ===" timewait_count=$(ss -tan state time-wait | wc -l) echo"当前 TIME_WAIT:$timewait_count" if[$timewait_count-gt 50000 ];then echo"警告: TIME_WAIT 连接数过高" fi echo"" echo"=== CLOSE_WAIT 连接数 ===" closewait_count=$(ss -tan state close-wait | wc -l) echo"当前 CLOSE_WAIT:$closewait_count" if[$closewait_count-gt 1000 ];then echo"警告: CLOSE_WAIT 连接数异常" fi echo"" echo"=== SYN_RECVD 连接数(半连接) ===" synrecv_count=$(ss -tan state syn-recv | wc -l) echo"当前 SYN_RECVD:$synrecv_count" if[$synrecv_count-gt 1000 ];then echo"警告: SYN 队列可能正在被攻击" fi echo"" echo"=== Established 连接 TOP 10 来源 IP ===" ss -tan state established | awk'{print $4}'| cut -d: -f1 | sort | uniq -c | sort -rn | head -10 echo"" echo"=== 最后一次抓取的 TCP 错误统计 ===" cat /proc/net/netstat | grep TcpExt | tail -1
7.2 监控端口连接数
#!/bin/bash # filename: port_conn_monitor.sh # 监控指定端口的连接数 PORT=${1:-80} THRESHOLD=${2:-1000} echo"=== 端口$PORT连接状态统计 ===" ss -tunlp | grep":$PORT"| head -5 echo"" echo"=== 端口$PORTEstablished 连接数 ===" est_count=$(ss -tan"sport = :$PORTor dport = :$PORT"state established | wc -l) echo"当前Established:$est_count" if[$est_count-gt$THRESHOLD];then echo"警告: 连接数超过阈值$THRESHOLD" fi echo"" echo"=== 端口$PORT各状态统计 ===" ss -tan"sport = :$PORTor dport = :$PORT"| awk'NR>1 {print $1}'| sort | uniq -c | sort -rn
7.3 抓包分析连接问题
#!/bin/bash # filename: tcp_debug.sh # 抓包分析 TCP 连接问题 OUTPUT_DIR="/tmp/tcpdump" mkdir -p$OUTPUT_DIR TIMESTAMP=$(date +%Y%m%d_%H%M%S) # 抓取指定端口的完整流量 echo"开始抓包,保存到$OUTPUT_DIR/tcp_${TIMESTAMP}.pcap" timeout 60 tcpdump -i eth0 -w"$OUTPUT_DIR/tcp_${TIMESTAMP}.pcap"port 8080 2>/dev/null & # 抓包过程中检查状态 sleep 10 echo"=== 10秒后状态 ===" ss -tunlp | grep 8080 sleep 10 echo"=== 20秒后状态 ===" ss -tunlp | grep 8080 # 分析抓包文件 wait echo"" echo"=== 握手统计 ===" tcpdump -r"$OUTPUT_DIR/tcp_${TIMESTAMP}.pcap"2>/dev/null | grep -E"(SYN|ACK|FIN|RST)"| wc -l echo"" echo"=== 异常统计 ===" tcpdump -r"$OUTPUT_DIR/tcp_${TIMESTAMP}.pcap"2>/dev/null | grep -c"RST" echo" RST 包数量"
8 总结
8.1 核心要点回顾
三次握手:
第一次:客户端发送 SYN,进入 SYN_SENT
第二次:服务端回复 SYN+ACK,进入 SYN_RCVD
第三次:客户端回复 ACK,双方进入 ESTABLISHED
三次是确保双向确认的最小次数
四次挥手:
主动关闭方发送 FIN,进入 FIN_WAIT_1
被动关闭方回复 ACK,进入 CLOSE_WAIT
被动关闭方发送 FIN,进入 LAST_ACK
主动关闭方回复 ACK,进入 TIME_WAIT
TIME_WAIT 等待 2MSL 确保双方都收到最后的 ACK
关键状态:
TIME_WAIT:主动关闭方的最后等待,防止旧报文干扰新连接
CLOSE_WAIT:被动关闭方等待应用关闭连接
SYN_RCVD:服务端等待第三次握手
半连接队列:存放未完成握手的连接
全连接队列:存放已完成握手等待 accept 的连接
8.2 常用排查命令
# 查看连接状态统计 ss -s # 查看各状态连接详情 ss -tan state {state} # 查看监听端口 ss -tlnp # 抓包分析 tcpdump -i eth0'tcp[tcpflags] & (tcp-syn|tcp-fin|tcp-rst) != 0' # 查看内核参数 cat /proc/sys/net/ipv4/tcp_* cat /proc/sys/net/core/somaxconn # 查看 TCP 错误统计 cat /proc/net/netstat | grep TcpExt
8.3 常见问题处理
问题 可能原因 处理方法 连接后立即 RST 端口未监听、队列满、防火墙 检查监听状态、队列配置、防火墙 大量 CLOSE_WAIT 应用未调用 close() 检查代码逻辑、设置超时 大量 TIME_WAIT 短连接过多 启用 tcp_tw_reuse、长连接 端口耗尽 短连接过多、端口范围小 扩大端口范围、连接池复用 半连接队列满 SYN Flood 攻击 增加 tcp_max_syn_backlog 全连接队列满 应用 accept 太慢 优化应用、增加队列大小 8.4 内核参数调优建议
# /etc/sysctl.conf 添加以下内容 # TCP 时间等待复用 net.ipv4.tcp_tw_reuse = 1 # FIN 超时时间 net.ipv4.tcp_fin_timeout = 30 # TIME_WAIT 上限 net.ipv4.tcp_max_tw_buckets = 100000 # 半连接队列大小 net.ipv4.tcp_max_syn_backlog = 2048 # 全连接队列大小 net.core.somaxconn = 2048 # 端口范围 net.ipv4.ip_local_port_range = 1024 65535 # Keepalive net.ipv4.tcp_keepalive_time = 600 net.ipv4.tcp_keepalive_intvl = 30 net.ipv4.tcp_keepalive_probes = 5 # TCP timestamps(tw_reuse 需要) net.ipv4.tcp_timestamps = 1 # 生效配置 sysctl -p
理解 TCP 状态转换是网络排查的基本功。面试能背答案不重要,真正遇到生产环境问题时能快速定位根因才是本事。建议在实际环境中多抓包、多分析、多调试,把理论知识和实际操作结合起来。
-
网络
+关注
关注
14文章
8382浏览量
95702 -
TCP
+关注
关注
8文章
1437浏览量
83891 -
状态机
+关注
关注
2文章
502浏览量
29413
原文标题:TCP 三次握手和四次挥手,面试能答出来不代表你真懂
文章出处:【微信号:magedu-Linux,微信公众号:马哥Linux运维】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
TCP/IP协议工作过程的三次握手和四次挥手
TCP三次握手过程及四次挥手过程说明
TCP三次握手和四次挥手以及11种状态资料下载
如何使用WireShark进行TCP三次握手
TCP建立连接概述及三次握手、四次挥手的流程
TCP三次握手和四次挥手的基础知识
评论