TCP 综合实践与排障
Wireshark 深度分析
完整通信流分析
场景:访问 https://www.example.com
Wireshark 过滤表达式
tcp.port == 443 # 443 端口的 TCP 流量
tcp.stream eq 0 # 第 0 条 TCP 流
tcp.flags.syn == 1 # SYN 报文
tcp.flags.fin == 1 # FIN 报文
tcp.analysis.retransmission # 重传报文
tcp.analysis.duplicate_ack # 重复 ACK
tcp.options.sack # 带 SACK 的报文
tcp.window_size_value < 1000 # 小窗口通告
常见 TCP 排障场景
场景 1:连接建立失败
现象:telnet target 443 卡住,或立即返回 Connection Refused
排查:
1. ping target → 通?网络层正常
2. telnet target 443 → Connection Refused?端口未监听
3. telnet target 443 → 卡住?防火墙拦截或路由问题
4. Wireshark 抓包:
- 看到 SYN 无回复?防火墙 DROP 或路由不可达
- 看到 SYN + RST?端口未监听
- 看到 SYN + SYN-ACK 无 ACK?客户端问题
场景 2:连接建立后传输极慢
现象:能 ping 通,但网页加载慢、下载速度低
排查:
1. 检查 RTT:ping target,RTT 是否异常高?
2. 检查丢包:mtr target,哪一跳丢包?
3. 检查窗口:Wireshark 看 Window Size,是否太小?
4. 检查重传:Wireshark 过滤 tcp.analysis.retransmission,重传率是否高?
5. 检查拥塞:ss -ti 看 cwnd 和 ssthresh
场景 3:大量 CLOSE_WAIT
现象:ss -tan | grep CLOSE-WAIT | wc -l 数值很大
原因:服务端收到客户端 FIN 后,应用层没有及时 close()
排查:
1. ss -tp | grep CLOSE-WAIT → 找到对应进程
2. 检查应用代码:是否在读取完数据后忘记关闭连接?
3. 检查线程池:是否线程耗尽导致无法处理关闭?
场景 4:TIME_WAIT 过多
现象:端口耗尽,无法建立新连接
排查:
1. ss -tan | grep TIME-WAIT | wc -l
2. 确认是服务端还是客户端主动关闭
3. 优化:Keep-Alive / 连接池 / 让客户端主动关闭
Socket 编程关键 API
// 服务端
int listen(int sockfd, int backlog); // backlog: 全连接队列长度
int accept(int sockfd, ...); // 从全连接队列取连接
// 客户端
int connect(int sockfd, ...); // 触发三次握手
// 数据收发
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
// 关闭
int close(int sockfd); // 完全关闭(四次挥手)
int shutdown(int sockfd, int how); // 半关闭
// SHUT_RD: 关闭读
// SHUT_WR: 关闭写(发送 FIN)
// SHUT_RDWR: 双向关闭
// 选项设置
setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)); // 禁用 Nagle
setsockopt(sockfd, IPPROTO_TCP, TCP_QUICKACK, &on, sizeof(on)); // 禁用延迟 ACK
性能测试工具
# iperf3: 测试 TCP 吞吐量
# 服务端
iperf3 -s
# 客户端
iperf3 -c <server_ip> -t 30 -i 1
# nc (netcat): 简单 TCP 测试
nc -vz target_ip port # 端口连通性测试
nc -l 8080 # 监听 8080
nc target_ip 8080 # 连接并发送数据
# hping3: 构造自定义 TCP 报文
hping3 -S -p 80 target_ip # SYN 扫描
hping3 -A -p 80 target_ip # ACK 扫描
hping3 -S -p 80 --flood target_ip # SYN Flood(测试环境!)
# ss: 查看连接状态
ss -s # 统计摘要
ss -tan # 所有 TCP 连接
ss -ti # 详细信息(含 cwnd、rtt)
ss -tp # 显示进程
# tc: 模拟网络条件
sudo tc qdisc add dev eth0 root netem delay 100ms loss 1%
sudo tc qdisc del dev eth0 root
核心参数速查表
| 参数 | 查看命令 | 典型调优 |
|---|---|---|
| 拥塞控制算法 | sysctl net.ipv4.tcp_congestion_control | cubic / bbr |
| SYN Cookie | sysctl net.ipv4.tcp_syncookies | 1 |
| SACK | sysctl net.ipv4.tcp_sack | 1 |
| 窗口缩放 | sysctl net.ipv4.tcp_window_scaling | 1 |
| 时间戳 | sysctl net.ipv4.tcp_timestamps | 1 |
| ECN | sysctl net.ipv4.tcp_ecn | 2 |
| TFO | sysctl net.ipv4.tcp_fastopen | 3 |
| tw_reuse | sysctl net.ipv4.tcp_tw_reuse | 1 |
| fin_timeout | sysctl net.ipv4.tcp_fin_timeout | 30 |
| somaxconn | sysctl net.core.somaxconn | 65535 |
| max_syn_backlog | sysctl net.ipv4.tcp_max_syn_backlog | 65536 |
本篇小结
- Wireshark 过滤:
tcp.stream、tcp.analysis.retransmission、tcp.flags.syn - 排障分层:物理 → 链路 → 网络(ping)→ 传输(telnet/Wireshark)→ 应用
- CLOSE_WAIT:应用层未 close(),检查代码
- TIME_WAIT:主动关闭方问题,用 Keep-Alive/连接池解决
- 工具链:iperf3 / nc / hping3 / ss / tc
- 核心参数:拥塞控制算法、SYN Cookie、窗口缩放、TFO
动手实践
用 Wireshark 抓取一次完整 HTTPS 访问,标记:
- 三次握手的 SYN/SYN-ACK/ACK
- TLS ClientHello/ServerHello
- 数据传输的 PSH+ACK
- 四次挥手的 FIN/ACK
用 iperf3 测试本机到路由器的 TCP 吞吐:
# 服务端 iperf3 -s # 客户端 iperf3 -c 192.168.1.1 -t 30用 tc 模拟 100ms 延迟 + 1% 丢包,观察对 TCP 吞吐的影响:
sudo tc qdisc add dev eth0 root netem delay 100ms loss 1% iperf3 -c <server> -t 30 sudo tc qdisc del dev eth0 root查看本机所有 TCP 内核参数:
sysctl -a | grep tcp思考:为什么
ss -ti显示的 cwnd 值在不同连接中差异很大?cwnd 与 RTT、带宽之间有什么关系?