有一次线上服务偶发超时,业务日志里全是“正常返回”,数据库和中间件的监控也都稳得很。大家一度怀疑是客户端网络问题,直到我在服务端用Wireshark抓了一把TCP交互数据包,才发现真相:同一秒内有大量TCP重传,而且全部指向同一个目标IP。从那以后,凡是对外联调、排查超时、验证协议细节,我都习惯先抓包再下结论——眼见为实这句话,放在网络世界里再合适不过。
这篇内容就围绕基于Wireshark分析TCP交互数据包展开,从抓包前的过滤准备、三次握手逐字段解读、数据传输阶段的序列号与确认号联动,到四次挥手的状态还原,再到常见异常包的识别思路,一步步帮你建立自己的排查框架。无论你是刚接触网络协议的学生,还是日常需要和网络问题打交道的开发、运维,这套方法都能直接用上。
1. 抓包前的三个关键决策:过滤规则、抓包位置与混杂模式
1.1 捕获过滤器与显示过滤器:别再混用
Wireshark界面里有两个长得差不多的过滤器入口,作用天差地别。顶部绿色那一行是显示过滤器,抓包完成后用来从结果里筛报文;抓包开始前那个弹窗里填的才是捕获过滤器,它在网卡驱动层面就做丢弃,只保留匹配的流量进入Wireshark。
新手最容易犯的错误是:抓包时想着“只要80端口的包”,结果在显示过滤器里输入port 80——这种BPF语法在显示过滤器中完全不生效,最终抓回来的仍然是全量流量。反过来,在捕获过滤器里填tcp.port == 80这种显示过滤器语法,也会直接报错。两种过滤器背后的语法体系完全不同,一个是类C的表达式,一个是Wireshark自研的字段表达式,混着用只会浪费时间。
我实际使用的习惯是:大多数场景不设捕获过滤器,直接全量抓几秒钟,然后靠显示过滤器慢慢筛。因为一旦抓包时过滤得太狠,遇到问题想回溯分析,网卡上已经丢掉了关键报文,没法弥补。只有两种情况例外:一是目标流量确实很大、磁盘很快会被撑满;二是要在低配机器或嵌入式设备上做长时间抓包,CPU扛不住。这时候才用捕获过滤器做粗筛,比如host 10.20.30.40 and tcp port 443。
1.2 高频过滤表达式与显示方向控制
整理一份我在日常排查中反复使用的显示过滤器,直接抄走就能用:
| 分析目标 | 显示过滤器 | 说明 |
|---|---|---|
| 看所有SYN包 | tcp.flags.syn == 1 | 握手探测、半连接观察 |
| 看所有RST包 | tcp.flags.reset == 1 | 异常中断往往是RST收尾 |
| 只看一条TCP连接 | tcp.stream eq 0 | 右键“Follow TCP Stream”会自动带出 |
| 只看重传 | tcp.analysis.retransmission | 定位丢包最直接 |
| 只看零窗口 | tcp.analysis.zero_window | 接收方缓冲区满载的信号 |
| 某个IP的全部流量 | ip.addr == 10.20.30.40 | 不区分源和目的 |
| 某个端口 | tcp.port == 443 | 同样不区分方向 |
这里有个细节容易让人困惑:ip.addr == 10.20.30.40在显示过滤器里是“源IP匹配 或 目的IP匹配”,一个报文只要有一端命中就会被显示。如果想精确观察双向流量,更严谨的写法是用ip.src == ...和ip.dst == ...分开表达。比如只看某台主机主动发起的连接请求,可以写ip.src == 10.20.30.40 and tcp.flags.syn == 1 and tcp.flags.ack == 0,这样就过滤出了纯SYN方向的包。
1.3 环回接口、混杂模式与“到底在抓谁”
本地调试时最常踩的坑是:明明浏览器访问了本地服务,Wireshark却一个包都抓不到。原因很简单——本机到本机的流量根本不会经过物理网卡,走的是Loopback虚拟接口。Windows环境下抓环回流量,需要选择“环回: Npcap Loopback Adapter”这个接口;macOS和Linux则直接选lo接口。
另一个容易被忽略的参数是混杂模式。默认情况下Wireshark会在多数接口上开启混杂模式,把发往非本机MAC地址的帧也收进来。但在交换网络里,你实际能看到的依然只是交换机广播给本机的帧,另外还有本机自己收发的流量。所以别指望抓包能“监听全网”,分析TCP交互数据包的主战场,永远是“自己参与通信的那台主机”。如果真想看网关侧的全量流量,需要配置端口镜像之类的交换机功能,那就是另一套故事了。
2. 三次握手逐字段拆读:SYN、ACK与序号协商
2.1 握手三包的标准样子
建立一个TCP连接,开头三个报文几乎永远是固定的形态。以客户端访问服务端为例:
1号包:客户端 → 服务端,[SYN],Seq=0,Len=0。这是客户端发出的第一次握手,告诉对方“我想建连,我的初始序列号从0开始”。
2号包:服务端 → 客户端,[SYN, ACK],Seq=0,Ack=1。服务端收到SYN后,先确认客户端的序列号,把自己的ACK置为1,同时带着自己的SYN一起回过去。
3号包:客户端 → 服务端,[ACK],Seq=1,Ack=1。客户端确认收到服务端的SYN,双方进入ESTABLISHED状态。
第一次看到包2和包3里的Ack都等于1,很多人会懵。想清楚一个规则就够了:SYN标志位要消耗一个序列号。客户端发出Seq=0的SYN之后,服务端期望的下一个字节序号就是1;同理,服务端自己的SYN从0开始,客户端回复的ACK也要指向1。所以两个“1”确认的是不同方向上的序列号,各算各的账。
2.2 相对序列号与初始序列号:为什么Seq都是0
真实网络里的初始序列号(ISN)是随机生成的,你第一次抓包很可能看到Seq=3904304714这种天文数字,直接头大。Wireshark默认把这些序号做了“相对化”处理:把每条TCP流的第一个发出来的包的Seq显示为0,后续所有序号都基于它做偏移。
这个设计是给人看的,跟随TCP Stream分析时特别方便。比如你想确认有没有连续丢包,看相对序号一眼就能发现缺口。如果哪天你打开抓包文件发现Seq是几十亿、几亿的大数字,多半是有人关掉了相对序列号选项。菜单路径在Edit → Preferences → Protocols → TCP → Relative sequence numbers,不建议关,除非你在对照某些老协议文档做教学推导。
那真正的ISN在报文里长什么样?展开帧详情里的TCP头,能看到Sequence Number (raw),那才是线路上的原始值。排查时一般用相对值就足够,但要知道这两个字段的区别,不然看到网上截图里不一样的序号又会怀疑抓错包了。
2.3 窗口缩放、MSS与SACK:握手时同步的三个关键参数
三次握手除了同步序列号,还有一个隐形任务:把传输参数谈妥。这些参数藏在TCP头的Options字段里,Wireshark会逐项解析出来。
首先是Window scale,也就是窗口缩放因子。TCP头部里的Window字段只有16位,最大只能表示65535字节,对高带宽网络来说远远不够。于是双方在握手时协商一个缩放因子,比如Window scale: 7 (multiply by 128),后续真正的接收窗口 = 窗口字段值 × 128。没有这个机制,带宽稍微大一点的链路,吞吐就会被65535字节的限制卡死。
其次是Maximum segment size,简称MSS。它表示对端愿意接收的最大TCP载荷长度。典型以太网的MTU是1500字节,扣除20字节IP头、20字节TCP头,正好留下1460字节,所以你在绝大多数抓包里看到MSS=1460。如果路径上存在更小的MTU,比如某种隧道封装后链路MTU变成1400,MSS就会被协商成更小的值。MSS直接影响TCP分段策略,TCP会按MSS切分应用层数据,而不是等IP层去分片。
还有SACK Permitted,选择性确认许可。它允许接收方在丢包时单独确认已收到的零散段,而不是要求发送方从头重传一整段。看到握手报文里带SACK选项,说明双方都支持块重传机制,这对大流量链路的丢包恢复非常友好。
2.4 半开连接不等于SYN攻击
排查经验里有个很常见的误判:抓到一堆只有SYN没有ACK的包,就断定被SYN攻击了。实际上,正常的服务器在慢客户端、跨地域网络下,也会出现几十条半开连接。慢客户端发出SYN后迟迟不回ACK,或者中间链路丢包导致SYN-ACK反复重传,都会在抓包里制造“半连接”假象。
判断是不是攻击,不能只数SYN包的数量,还要看更多特征:源IP的分布是否分散、SYN到达速率是否异常高、服务端回SYN-ACK后客户端是否始终不回应、有没有伴随大量ACK抖动。Wireshark的颜色标记里,RST包通常显示为红色系,重传包是黑色系,分析这类场景时先把视线锁定在这两类异常包上,再结合连接数量曲线做判断,比单纯盯着一屏SYN包瞎猜靠谱得多。
3. 数据传输阶段:Seq、Len、Ack的推进规律与窗口计算
3.1 数据包的标志位组合与序号连续性
连接建立后,真正传数据时抓到的报文大多是[PSH, ACK]标志位组合。PSH的意思是“别在缓冲区里攒着了,立刻把数据交给上层”,它是TCP通知接收方应用层及时读取的机制。
每个数据包的Seq表示本段数据的第一个字节序号,Len等于TCP载荷长度(Wireshark里的Length减去IP头、TCP头后剩下的部分)。重点在这里:下一个数据包的Seq,一定等于当前包的Seq加Len。比如:
包4:Seq=1,Len=1448
包5:Seq=1449,Len=1448
包6:Seq=2897,Len=160
每个包的Seq接得严丝合缝,这就是TCP能做到可靠传输和乱序重组的基础。如果抓包时发现Seq出现了跳跃,比如收到Seq=4345之前没看到Seq=2897到4345之间的数据,说明中间有一段确实丢了或者正在路上。Wireshark会根据时间次序标注Out-of-Order,这时就要结合重传标记一起判断了。
3.2 累积确认:Ack不动不代表没收到
TCP的确认机制是累积式的,接收方并不需要每个包都回一个ACK。它可能连续收到多个数据包后,只回一个Ack,指向自己下一个期望的字节序号。
举个例子:接收方收到Seq=1 Len=1448和Seq=1449 Len=1448两个包后,如果数据都完整,它可能只回一个Ack=2897,表示“2897之前的所有字节我都收到了,下一个请发2897”。这是TCP吞吐优化的重要手段,可以减少ACK报文数量。
所以抓包时看到一连串ACK数值不变化,别急着下“对端没收到数据”的结论。先看时间间隔,再看包序号是否连续。只有在接收方发现序列号缺口时,ACK才会反复指向同一个旧序号,那才叫重复ACK,是异常信号。
3.3 窗口字段经过了缩放:算真实窗口的公式
TCP头里的Window字段显示的是接收窗口通告值,但这个值不一定就是真实字节数,还要经过握手阶段协商好的Window scale放大。Wireshark在Info列里通常会帮你算好一个比较直观的窗口值,但看详情面板时要注意区分:Window size value是线路上的原始字段,Calculated window size才是乘以缩放因子后的真实窗口。
计算公式很简单:真实窗口 = 窗口字段值 × 2^缩放因子。比如握手中协商了Window scale: 7 (multiply by 128),传输阶段某个包的Window字段值是1560,那接收方通告的真实接收窗口就是1560×128=199680字节,差不多195KB。
窗口越大,允许在途未确认的数据越多,网络吞吐越高。排查“带宽很大但网速很慢”的问题时,重点看两个方向:一是RTT是不是大到带宽延迟积远超窗口值,导致管道喂不满;二是窗口是不是被对端主动缩到了几十字节,那多半是接收方应用消费不过来,eventually会演变成零窗口。
3.4 用TCP流图看吞吐量与RTT
一个个报文翻着看,很难把握整体趋势。Wireshark自带的Statistics → TCP Stream Graph里,有几个图非常适合做宏观判断。
Time-Sequence (Stevens) 图,横轴是时间,纵轴是序列号,斜率代表发送速率。如果图形是一条平滑上升的直线,说明TCP管道很健康;如果呈阶梯状,每上升一段就停一段时间,大概率是丢包后的重传退避。Round Trip Time图可以直接看RTT波动曲线,哪一段延迟突然飙升,配合重传标记能快速定位拥塞点。Throughput图则直接展示实际吞吐量随时间的变化。
我在排查慢接口时,通常先打开时间序号图看整体形态。如果看到发送窗口在缩小,转去看接收窗口;如果看到一堆重传标记,转去看MSS和路径MTU。链路丢包问题一般到这里就浮出水面了。
3.5 实战验证:MSS协商不一致导致的分片问题
某次联调环境里,一个服务从跨机房返回数据特别慢,抓包发现HTTP响应被切成了很多小于本地MSS的片段,同时伴随大量[TCP segment of a reassembled PDU]标记。进一步看握手包,发现对端MSS写的是1300,而本端MSS是1460。问题不在TCP层本身,而是中间有个隧道设备把链路MTU压到了1340。所有超过这个值的TCP分段在IP层被分片,分片报文一旦丢失,TCP重传时得整段重来,性能自然一落千丈。
后来调整了对端MTU配置,问题消失。这个案例说明,分析TCP交互数据包时,地基永远是MSS和路径MTU。握手包里的选项字段看似只是几个数字,实际上早就把很多隐患写在了里面。
4. 四次挥手的现场还原:FIN时序、TIME_WAIT与CLOSE_WAIT
4.1 挥手的四个包和确认号计算
优雅关闭TCP连接的标准过程由四个报文完成。假设客户端主动关闭:
包A:客户端 → 服务端,[FIN, ACK],表示客户端数据发完了,请求关闭连接。
包B:服务端 → 客户端,[ACK],确认收到FIN。
包C:服务端 → 客户端,[FIN, ACK],表示服务端的数据也发完了。
包D:客户端 → 服务端,[ACK],最终确认。
和SYN一样,FIN标志位也要消耗一个序列号。收到FIN的一方,回复的ACK确认号是对方FIN包的Seq加1。所以包B的Ack等于包A的Seq加1,包D的Ack等于包C的Seq加1。
被动关闭方一般在应用程序主动调用close之后,才会发出包C。这中间如果应用迟迟不释放socket,就会出现CLOSE_WAIT状态堆积。抓包分析时可以用右键 → Follow → TCP Stream把双向报文拼起来看,挥手过程一目了然。
4.2 状态机对应抓包的五个观察点
TCP状态机是很多人的老大难,但对照着抓包看,其实就只有几个关键点:
三次握手完成后,双方进入ESTABLISHED,抓包里表现为正常的数据传输。主动关闭方发出FIN后进入FIN_WAIT_1,收到对方ACK后进入FIN_WAIT_2。被动方收到FIN后进入CLOSE_WAIT,此时如果应用层还没关闭连接,你可能会抓到一些“被动方还在继续发包”的奇怪现象。被动方发出FIN后进入LAST_ACK,主动方收到FIN并回ACK后进入TIME_WAIT,等待2MSL后才彻底消失。
这里要注意一个细节:TIME_WAIT在抓包里并不表现为独立报文,它只是主动关闭方在连接结束后继续保持状态的一个时间窗口。用netstat能看到TIME_WAIT,但抓包只能看到最后一个ACK。很多服务调优文章建议调整TIME_WAIT相关参数,动手之前最好先确认产生TIME_WAIT的一方确实是主动关闭方,方向错了会越调越糟。
4.3 半关闭、FIN重传与RST替代FIN
最常见的挥手异常是“只看到一半”:主动方发出FIN,被动方回了ACK,但被动方迟迟不发FIN。这意味着被动方应用没调用close,CLOSE_WAIT滞留。排查CLOSE_WAIT,抓包能帮你确认“确实是对方没发FIN,而不是FIN被网络丢了”,更深入的原因要看应用日志和句柄数。
另一种异常是FIN重传。发送FIN后一直等不到对端ACK,TCP会像重传数据一样重传FIN。抓包中你会看到同一个Seq的FIN出现两次,中间隔着超时时间。这时候排查重点就变成了往返链路的丢包问题。
还有一种更粗暴的情况:一端直接发RST代替FIN。RST意味着异常终止,常见于应用异常退出、socket设置了SO_LINGER且超时时间为零、或者收到发往不存在端口的报文。抓包看到RST,通常意味着这次连接没有走完正常挥手流程,需要回到应用层找原因。
5. 异常TCP包识别:重传、乱序、重复ACK与零窗口
5.1 超时重传与快速重传的区别
Wireshark用黑色背景标记重传包,Expert Info面板里也会写TCP Retransmission。但重传其实分两种,处理思路完全不同。
超时重传,是发送方超过RTO(重传超时时间)还没收到ACK,整个重发。Wireshark标记为TCP Retransmission。快速重传,是发送方连续收到3个重复ACK后,不等超时立即补发。Wireshark标记为TCP Fast Retransmission。
快速重传说明网络已经把丢包信息及时反馈回来了,链路时延正常;超时重传则意味着要么链路时延异常增大,要么ACK报文也在丢。在公网上偶尔一两次重传不用太紧张,流量波动很正常;但如果重传包占比超过1%,或者同一时间片大面积重传,基本可以判定链路有问题。用tcp.analysis.retransmission过滤出所有重传包,再到时间序号图上画一画,能很快定位重传的爆发时间段。
5.2 乱序与重复ACK:丢包还是多路径
TCP接收方如果发现序列号不连续,会立即回复一个ACK,内容仍指向自己期望的旧序号,于是产生重复ACK。发送方看到连续3个同样的ACK,就会触发快速重传。
乱序包则是另一种情况:接收方先收到了一个序号较高的包,又收到序号较低的包,Wireshark标记为Out-of-Order。乱序不一定代表丢包,很多链路本身就是多路径传输,包到达顺序不同是常态。
区分方法很简单:观察后续的序列号空洞是否被补上。如果后续序号接上了,说明只是乱序;如果迟迟补不上,那其实是丢包加缓慢的重传。现代网络里,乱序和重复ACK经常伴生出现,看的时候要把它们放在一起解读,而不是单独挑一个字段下结论。
5.3 零窗口:接收端喊停的信号
TCP窗口字段是接收方通告的“我还愿意接收多少字节”。当接收方应用读取不及时、缓冲区满了,它会发出窗口字段为0的报文。Wireshark标记为TCP ZeroWindow,颜色很显眼。发送方收到零窗口后会停止发送,并定期发Zero Window Probe探测窗口是否恢复,直到收到非零的窗口更新。
排查这类问题,别上来就怀疑网络,先看接收方应用的消费能力。有一次我排查一个批量任务突然变慢的问题,抓包发现服务端通告窗口一路从64K掉到0,根因是应用进程出现长时间停顿,读线程没有及时消费socket缓冲区,纯属应用层问题,TCP协议本身一切正常。窗口是应用和协议栈之间的镜子,零窗口永远是应用层反馈出来的信号。
5.4 一次慢接口排查的完整证据链
整理一个实际走过的排查链路,方便你感受一下这个分析过程是怎么串起来的。
现象:某个内网服务的接口偶尔要等3到5秒才返回。第一步,在客户端用Wireshark抓包,按http过滤,找到那个慢请求,右键Follow TCP Stream。第二步,发现TCP流中有大量黑色重传包,时间间隔从几十毫秒逐步扩大到几百毫秒,RTT图呈现明显的锯齿状。第三步,看握手包MSS,双方一致;继续往上查链路,发现中间交换机的端口统计里有CRC错误。第四步,顺着错误端口往下查,定位到一条光纤老化导致频繁误码,帧在链路上被损坏后丢弃,TCP层只能靠重传恢复。第五步,更换光纤后重新抓包,周期性锯齿消失,接口恢复稳定。
这套链路说明一个原则:抓包数据只是线索,最终要结合链路设备状态、应用日志一起交叉验证。Wireshark能告诉你怎么了,但未必能直接告诉你为什么,中间那段“为什么”需要你去联动其他信息源。
6. 用Wireshark做TCP分析的个人经验与工具心得
最后分享几个用Wireshark做TCP分析时,不那么容易在文档里看到的心得。
一是尽量抓双端。只抓客户端或只抓服务端,常常只能看到单方向的现象,无法判断丢包发生在去程还是回程。条件允许时两端同时抓,再比对同一时间窗口内的报文,一次就能把问题缩小到半程。
二是小心抓包行为本身改变问题。抓包会占用CPU和内存,在高吞吐环境下,你看到的丢包可能就是抓包工具自己丢的,而非系统问题。线上抓包尽量选短期、小流量窗口,抓到足够证据就停,别开着抓包睡一觉。
三是用好Expert Info。Wireshark左下角的圆点图标集中展示了它对每条TCP流的自动诊断结果,比如重传、零窗口、乱序、断点。分析异常流量时,先看这里往往比手动翻包高效得多。
四是别忽视加密流量。现在绝大多数应用流量都是HTTPS,Wireshark在TCP层看到的只是加密后的报文结构,想看应用层内容,需要导出会话密钥或者抓TLS终止点的明文。TCP层的包结构分析不受影响,但要深入“请求-响应”级问题,配合密钥分析是下一步必修课。
抓包这件事看起来是工具操作,本质上是对TCP协议理解的检验。把序列号、确认号、标志位、窗口这几个核心概念在手上滚熟了,Wireshark就只是一个把这些概念变直观的放大镜。每一张抓包文件都在讲一个故事,读得懂标志位和序号,就读得懂这个故事里谁先开口、谁在沉默、谁又悄悄补发了消息。