4. 链路层故障诊断实战
传输层排错,说白了就是跟端口、连接状态、丢包重传打交道。我干了这么多年网络排错,至少有一半的线上故障最终都定位在传输层。TCP 和 UDP 虽然都是传输层协议,但排错思路完全不同——一个面向连接,一个无连接,踩的坑也天差地别。
核心要点:传输层排错的关键在于理解状态机。TCP 的每个状态转换都对应一个具体事件,UDP 的每个丢包都可能是因为你假设了它“有连接”。
4.1 TCP 三次握手抓包分析
三次握手是 TCP 排错的第一道关卡。我见过太多人一上来就查应用日志,结果发现是握手阶段就卡住了。你想想看,如果连连接都建不起来,应用层怎么可能正常工作?
标准的三次握手流程:
- 第一次握手(SYN):客户端发送 SYN,seq=x
- 第二次握手(SYN+ACK):服务端回复 SYN+ACK,seq=y,ack=x+1
- 第三次握手(ACK):客户端发送 ACK,seq=x+1,ack=y+1
抓包技巧:我个人习惯用 tcpdump -i any -nn 'tcp port 80' -w handshake.pcap 先抓包,再用 Wireshark 打开。重点关注 tcp.flags 字段,如果看到 SYN 发出去后没有 SYN+ACK 回来,基本就是服务端没监听或者防火墙拦截了。
我曾经遇到过一个案例:客户端一直报连接超时,抓包发现 SYN 包发出去了,但服务端回了 RST。查了半天,原来是服务端的 iptables 规则把源端口 1024 以上的包全丢了。嗯,这种坑防不胜防。
4.2 TCP 四次挥手抓包分析
四次挥手排错比握手更隐蔽。很多问题不是建连失败,而是断连异常导致连接泄漏。我见过一个 Java 应用,每次请求后 TIME_WAIT 堆积到几万个,最后把端口池耗尽了。
标准挥手流程:
- 主动方发送 FIN(seq=u)
- 被动方回复 ACK(ack=u+1)
- 被动方发送 FIN(seq=v)
- 主动方回复 ACK(ack=v+1)
| 状态 | 含义 | 排错关注点 |
|---|---|---|
| TIME_WAIT | 主动关闭方等待 2MSL | 数量过多说明短连接频繁,考虑连接复用 |
| CLOSE_WAIT | 被动关闭方等待应用关闭 | 应用没调用 close(),典型代码 bug |
| FIN_WAIT2 | 主动方等待被动方 FIN | 对端可能卡住了,检查应用逻辑 |
注意:CLOSE_WAIT 是排错中最容易忽略的状态。如果服务端大量 CLOSE_WAIT,说明你的应用代码没有正确关闭 socket。我曾经帮一个团队排查,发现他们的 HTTP 客户端在读取响应后忘了关闭连接,导致 2000 多个 CLOSE_WAIT 堆积,服务直接挂掉。
4.3 TCP 窗口与重传机制
窗口和重传是 TCP 性能排错的核心。说白了,窗口大小决定了你能发多快,重传率决定了网络质量。
抓包时重点关注:
- Window Size:如果持续为 0,说明接收端缓冲区满了,应用层没及时读取
- TCP Retransmission:重传率超过 2% 就要警惕了
- Duplicate ACK:连续 3 个重复 ACK 触发快速重传
实战经验:我排查过一个跨机房同步延迟问题。抓包发现窗口经常缩到 0,但服务端 CPU 和内存都正常。最后定位到是接收端的 socket 接收缓冲区太小(默认 64KB),改成 256KB 后问题解决。你想想看,缓冲区太小,应用读得慢,窗口自然就满了。
4.4 端口不通与服务监听问题
端口不通是传输层排错里最基础也最容易误判的问题。很多人第一反应就是防火墙,其实原因可能很简单——服务根本没起来。
排查步骤:
- 确认服务监听:
ss -tlnp或netstat -tlnp,看端口是否在 LISTEN 状态 - 本地测试:
telnet 127.0.0.1 端口,排除网络问题 - 远程测试:
telnet 目标IP 端口,确认防火墙是否放行 - 抓包确认:tcpdump 看是否有 SYN 到达,是否有 SYN+ACK 回复
| 现象 | 可能原因 | 排查命令 |
|---|---|---|
| telnet 卡住 | 防火墙拦截 SYN | tcpdump 看是否有 SYN 发出 |
| telnet 返回 Connection refused | 端口没监听 | ss -tlnp | grep 端口 |
| telnet 能连但应用无响应 | 应用层问题 | 检查应用日志 |
避坑指南:我曾经遇到过一个问题,ss -tlnp 显示端口在监听,但远程就是连不上。最后发现服务绑定了 127.0.0.1 而不是 0.0.0.0。所以记得看 ss 输出的 Local Address 列,确认是 *:端口 还是 127.0.0.1:端口。
4.5 UDP 无连接特性导致的丢包排查
UDP 排错跟 TCP 完全是两码事。没有握手、没有窗口、没有重传——说白了,发出去就听天由命了。所以 UDP 丢包排查,核心思路是“假设它一定会丢,然后找原因”。
常见丢包原因:
- MTU 分片丢失:UDP 报文超过 MTU 时会被分片,只要一个分片丢了,整个报文就丢了
- 接收缓冲区溢出:UDP 没有流量控制,接收端读得慢,缓冲区满了就直接丢
- 防火墙/iptables:很多防火墙默认丢弃 UDP 分片包
- 应用程序没及时 recvfrom:UDP 是逐包读取,读慢了就丢
注意:UDP 丢包在抓包里很难直接看到“丢包”字样。你需要通过 netstat -s 看 UDP 段的统计信息,重点关注 packet receive errors 和 buffer errors。如果这两个值在增长,说明接收端有问题。
我排查过一个 DNS 解析超时的问题。客户端发 DNS 查询,有时候有响应有时候没有。抓包发现 DNS 响应报文超过了 1500 字节,被分片了。中间路由器丢弃了分片包,导致整个响应丢失。解决方案很简单——在 DNS 服务器上启用 EDNS0 并设置 truncation 标志,让客户端改用 TCP 重查。
UDP 排错三板斧:
tcpdump -i any -nn 'udp port 53' -w udp.pcap抓包,看报文是否完整netstat -s -u看 UDP 统计,关注丢包计数ss -uapn看 UDP socket 的接收缓冲区是否满
嗯,传输层排错说到底就是两件事:TCP 看状态机,UDP 看丢包点。掌握了这些,大部分传输层问题都能快速定位。记住,别一上来就怀疑应用层,先看看传输层是不是已经出问题了。