4、传输层排错(TCP/UDP):TCP三次握手与四次挥手抓包分析、TCP窗口与重传机制、端口不通与服务监听问题、UDP无连接特性导致的丢包排查

传输层排错,说白了就是跟端口、连接状态、丢包重传打交道。我干了这么多年网络排错,至少有一半的线上故障最终都定位在传输层。TCP 和 UDP 虽然都是传输层协议,但排错思路完全不同——一个面向连接,一个无连接,踩的坑也天差地别。

核心要点:传输层排错的关键在于理解状态机。TCP 的每个状态转换都对应一个具体事件,UDP 的每个丢包都可能是因为你假设了它“有连接”。

传输层排错知识体系 传输层排错知识体系 TCP 排错 三次握手抓包 四次挥手抓包 窗口与重传 端口与服务监听 UDP 排错 无连接特性导致的丢包排查 MTU 分片丢包 缓冲区溢出 排错核心逻辑 1. TCP:先看握手是否完成 → 再看窗口是否满 → 最后查重传率 2. UDP:先确认对端是否在监听 → 再看 MTU 是否分片 → 最后查缓冲区 3. 端口不通:先 telnet/ss 确认 → 再查防火墙 → 最后看服务进程 4. 抓包工具:tcpdump + Wireshark 是标配,别只用 netstat 5. 我个人的习惯:先看两端状态机,再查中间设备,最后才怀疑应用层

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 堆积到几万个,最后把端口池耗尽了。

标准挥手流程:

  1. 主动方发送 FIN(seq=u)
  2. 被动方回复 ACK(ack=u+1)
  3. 被动方发送 FIN(seq=v)
  4. 主动方回复 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 端口不通与服务监听问题

端口不通是传输层排错里最基础也最容易误判的问题。很多人第一反应就是防火墙,其实原因可能很简单——服务根本没起来。

排查步骤:

  1. 确认服务监听:ss -tlnp 或 netstat -tlnp,看端口是否在 LISTEN 状态
  2. 本地测试:telnet 127.0.0.1 端口,排除网络问题
  3. 远程测试:telnet 目标IP 端口,确认防火墙是否放行
  4. 抓包确认: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 排错三板斧:

  1. tcpdump -i any -nn 'udp port 53' -w udp.pcap 抓包,看报文是否完整
  2. netstat -s -u 看 UDP 统计,关注丢包计数
  3. ss -uapn 看 UDP socket 的接收缓冲区是否满

嗯,传输层排错说到底就是两件事:TCP 看状态机,UDP 看丢包点。掌握了这些,大部分传输层问题都能快速定位。记住,别一上来就怀疑应用层,先看看传输层是不是已经出问题了。