有次线上排查应用响应慢,抓完包一百多万个,我盯着报文列表翻了五分钟头皮发麻。后来改了个习惯:抓到 pcap 之后,先看长度。Wireshark 抓包里,“数据包长度”是一组最不起眼的数字,却是最快能把流量分类的钥匙。统计发送方向的数据包长度,能判断链路是在跑满载大包还是碎片小包,能定位是不是 MTU 配置出了岔子,甚至能看出应用层是不是没开 TCP_NODELAY。这篇文章就围绕 Wireshark 抓包中“统计发送的数据包长度”这件事,把字段含义、统计窗口、过滤语法、命令行用法和踩坑经验全部过一遍,适合刚开始学抓包的同学,也适合抓包很多年但没仔细研究过长度字段的老手。
1. 三个“长度”字段先分清:Frame Length、IP Length 和 TCP Length
很多人一打开 Wireshark 就说“看包长度”,但 Wireshark 里跟长度沾边的字段有好几个。统计之前不把这些字段分清,后边全白做。最常见的三个长度概念是 Frame Length、IP Total Length 和 TCP Length,它们的覆盖范围完全不同。
1.1 Frame 里的 “bytes on wire” 和 “bytes captured”
选中任意一个数据包,展开最上层的 Frame 协议,你会看到这样两行:
Frame 1514 bytes on wire (12112 bits), 1514 bytes captured
“bytes on wire” 表示这个以太网帧在线路上占用的总字节数,也就是我们通常说的 frame.len。
“bytes captured” 表示抓包工具实际捕获了多少字节。正常情况下两者相等,但如果你在抓包时设置了 snaplen(抓包长度上限),或者网卡驱动只返回了部分数据,captured 会小于 on wire。
统计长度时,我一般以 bytes on wire 也就是 frame.len 为准,因为它代表的是链路上真实传输的报文大小。frame.len 包含完整的以太网头部,通常是 14 字节:目的 MAC 6 字节、源 MAC 6 字节、EtherType 2 字节。如果报文带了 802.1Q VLAN Tag,还要额外加 4 字节。
1.2 IP 总长度、TCP 载荷长度,一张表说清关系
Frame 之下,IP 层有 ip.len,TCP 层有 tcp.len,UDP 层有 udp.length。它们之间是“包含关系”,但很多新手会把它们混为一谈,尤其是 tcp.len 和 ip.len。
- ip.len:IPv4 报文总长度,包含 IP 头部和 IP 载荷。在 IPv4 头里就是 Total Length 字段。
- tcp.len:Wireshark 计算出的 TCP 段载荷长度,等于这个 TCP 段里“应用数据”的字节数,不包括 TCP 头,也不包括 IP 头。
- udp.length:UDP 头加 UDP 载荷的长度,最小 8 字节。
拿一个最常见的满载荷 TCP 数据段来举例:以太网帧 1514 字节,包含 14 字节以太网头、20 字节 IP 头、20 字节 TCP 头、1460 字节应用数据。各字段值如下:
| 字段 | 代表范围 | 满载荷TCP段典型值 |
|---|---|---|
| frame.len | 以太网帧整体长度 | 1514 |
| ip.len | IP头 + IP载荷 | 1500 |
| tcp.len | 仅TCP载荷 | 1460 |
| udp.length | UDP头 + UDP载荷 | 按业务而定 |
一个纯 ACK 包则是另一个极端:frame.len 大约 54 字节到 66 字节,ip.len 40 字节左右(IP 头 20 + TCP 头 20),而 tcp.len = 0,因为它没有任何应用数据。
1.3 手动计算 TCP 实际载荷的公式
如果你看到某个包的 tcp.len 显示为空或者想自己心算验证,可以用这个公式:
tcp.len = frame.len - 14(以太网头) - 4(如果有VLAN Tag) - ip.hdr_len - tcp.hdr_len
注意,ip.hdr_len 不一定是 20,TCP 头也不一定是 20。IP 带了 Options 时头会变长,TCP 带了时间戳、SACK 等选项时头同样会变长。所以我在实际分析中从不算这个公式,直接在显示过滤器里用 tcp.len 字段,Wireshark 早就帮你算好了。手动算公式的意义只有一个:当你看到长度不是一个“整数感”的值时,心里有数可能是 Options 占的字节。
2. 不用逐包翻:用 Packet Lengths 直方图看整份抓包的长度分布
如果 pcap 文件有几百 MB,你不可能逐个包去看长度。Wireshark 的 Statistics -> Packet Lengths 就是专门给你看“全貌”的。这个功能平时用的人不多,但排查问题时效率极高。
2.1 Packet Lengths 窗口怎么打开,和显示过滤器如何联动
菜单栏路径是 Statistics -> Packet Lengths。打开后,Wireshark 会把当前主界面的显示过滤器结果按照长度区间分组。默认分组的长度范围大致是 40-79、80-159、160-319、320-639、640-1279、1280-2559 这样的倍数区间。每一行会显示该区间内的包数量、百分比、累计数量和累计百分比。
这里有个最关键的技巧:这个统计窗口会同步主界面的显示过滤器。也就是说,你可以在主界面先构造好“只看发送方向”的过滤条件,比如ip.src == 192.168.1.10,再打开 Packet Lengths,它只会统计过滤后的包。过滤条件不同,统计结果完全不同,千万别拿着不过滤的全局统计去看某个主机的发送行为。
2.2 读分布形态:一眼看出流量属于哪种类型
长度分布的形状本身就是诊断结论:
- 如果大量包集中在 40-79 字节区间,说明这份抓包里主要是 TCP 控制包,比如 SYN、纯 ACK、Keep-Alive。连接频繁建立、或者接收方在不停确认数据,都会形成这种形态。
- 如果在 1280-2559 区间有非常集中的柱子,说明有大量接近 MTU 满载的数据段,最常见的就是文件传输、视频流这类大块数据。
- 如果分布很散,中间一堆几百字节的包,那就要小心了。这种情况通常意味着应用层在发送小段数据,或者 TCP 分段本身没有顶满 MSS。
我在实际使用中总结了一个经验:把鼠标放到统计表的长度区间上,双击某一行,可以直接在主界面过滤出对应长度的包,然后结合其他列去确认这些包到底是什么协议、什么行为。这是快速从“统计全貌”切换到“具体报文”的最好方式。
2.3 调整粒度与导出结果
Packet Lengths 窗口下方有一个范围调整滑块,可以改变分组的粒度。默认的分组比较粗,比如 640-1279 一个区间,如果你发现这一段特别多,可以把粒度调细,看看具体是 640-799、800-959 还是 960-1279,进一步锁定规律。窗口支持把统计结果复制出去,方便贴到文档里做分析报告。
另外顺便提一句:如果你想知道“每个协议占了多少字节”,可以用 Statistics -> Protocol Hierarchy。它不是按长度区间,而是按协议栈来统计报文数和字节数。把 Packet Lengths 和 Protocol Hierarchy 结合起来看,能很清楚地知道这份流量到底是 DNS 这种小包为主,还是 TCP 大数据块为主。
3. 精确捞包:按长度范围过滤出你关心的发送报文
统计窗口解决的是“整体分布长什么样”,但更多时候我们要把特定长度的包筛出来细看。Wireshark 的显示过滤器对长度字段支持得很好,下面这些过滤表达式是我平时用得最频繁的。
3.1 按 frame.len / ip.len / tcp.len 过滤的常见场景
| 过滤表达式 | 用途 |
|---|---|
| frame.len > 1514 | 找出超过标准以太网最大帧的包,排查巨型帧或网卡 offload 异常 |
| frame.len < 64 | 找出小于最小以太网帧的包,排查异常短帧 |
| tcp.len == 0 | 筛出所有不含有效载荷的 TCP 包,比如纯 ACK、SYN、FIN |
| tcp.len > 0 && tcp.len < 100 | 找出发送方向数据量极小的 TCP 段,常用于排查“小包碎包”问题 |
| ip.len > 1500 | 排查 IP 层大于 MTU 的报文,通常意味着分片或巨型帧 |
| udp.length > 1400 | 找出接近满载的 UDP 报文,比如大流量 DNS over UDP 场景 |
举个例子,排查 MTU 问题的时候,我习惯用frame.len > 1472 && icmp去筛选 Ping 大包。1472 是标准 MTU 1500 减掉 20 字节 IP 头和 8 字节 ICMP 头之后的值。如果对方设备 MTU 不够,你会看到有去无回、或者看到 ICMP 分片错误消息,长度分布也能明显看到断档。
3.2 把长度字段变成列,滚动列表就能发现异常
Wireshark 默认的报文列表里没有长度列,你可以手动加。在报文列表上方的列名处点右键,选择 Column Preferences,新增两列,字段名分别填 frame.len 和 tcp.len。更快的办法是:展开某个包的 Frame 或 TCP 协议字段,右键对应的长度字段,选择 Apply as Column。
加了这两列之后,整个列表滚动起来非常有信息量。你会看到一串 1514 的满载荷段中间,偶尔插进来几个几十字节的纯 ACK,这是正常现象;但如果你看到大量几百字节的段夹杂在满载荷段之间,就要想想为什么应用数据没有把 MSS 填满。
3.3 组合条件的排障场景
长度字段最常见的用法不是单独用,而是和 IP、协议、时间戳组合。比如:
- 只看本机向上行方向发送的数据段:
ip.src == 192.168.1.10 && tcp.len > 0 - 想看发送方向小于 100 字节的数据段,并排除重传:
ip.src == 192.168.1.10 && tcp.len > 0 && tcp.len < 100 && !tcp.analysis.retransmission - 想找发送间隔超过 200ms 的大包:
tcp.len > 1000 && frame.time_delta > 0.2
第二种场景非常有代表性。如果这类小数据包数量特别多,几乎可以断定应用层在频繁 write 小块数据到 socket。这时候单纯调大 TCP 缓冲区没用,根因在应用侧,要么合并写入,要么开启 TCP_NODELAY 避免 Nagle 算法把数据憋住。
4. 一次完整实操:统计 HTTP 会话中“本机发送方向”的数据包长度
前面都是方法论,这一节走一遍完整流程。目标很具体:用 Wireshark 统计一个 HTTP 访问过程中,本机发送方向的数据包长度分布,并从中判断是否存在异常。
4.1 抓包与识别方向的基本方法
假设本机 IP 是 10.0.0.2,访问的目标服务器是 93.184.216.34。抓完包后,先在过滤器中输入ip.addr == 93.184.216.34,眼前就只剩和目标服务器的交互流量。
要看“发送方向”的严格定义,就是ip.src == 10.0.0.2。如果你觉得 IP 记不清,还有一个更稳妥的方法:Statistics -> Conversations,在 TCP 页签里找到这对 IP 对应的一行,能看到两个方向各自的 Packets 和 Bytes 统计。在 Conversations 窗口双击某一会话,主界面会自动应用过滤,只显示该会话的包。随后你想统计哪个方向,再加一条ip.src == 10.0.0.2即可。
4.2 一个 HTTP 请求中,各关键包的典型长度
为了让你对“长度”有直接体感,这里列出一次非常干净的 HTTP 请求中常见包的典型长度范围。不同操作系统、不同 TCP Options 组合会导致长度略有差异,但量级不会变。
| 包类型 | frame.len 典型值 | tcp.len | 是否出现在本机发送方向 |
|---|---|---|---|
| SYN 第一次握手 | 约 54-70 字节 | 0 | 是,本机主动发起 |
| SYN+ACK 第二次握手 | 约 54-70 字节 | 0 | 否 |
| ACK 第三次握手 | 约 54-70 字节 | 0 | 是 |
| HTTP GET 请求 | 视 Headers 而定,常见 300-600 字节 | 请求体长度 | 是 |
| 服务器回应的满载荷段 | 1514 | 1460 | 否 |
| 服务器回应的最后一小段 | 不定,比如 354 | 剩余不足 1460 | 否 |
| 本机确认收到的纯 ACK | 约 54-70 字节 | 0 | 是 |
从这个表能看到一个很容易被忽略的事实:即使我们是“发送方向”,统计出来的大量短包其实是 ACK,不是应用主动发出去的数据。如果你只关心应用有效数据,应该严格使用ip.src == 10.0.0.2 && tcp.len > 0来过滤。
4.3 用 Packet Lengths 统计发送方向并下结论
实际操作如下:
- 主界面过滤器输入
ip.src == 10.0.0.2。 - 打开 Statistics -> Packet Lengths。
- 看 40-79 区间占多大比例,再看 1280-2559 区间占多大比例。
如果 40-79 区间占了八成,说明这个方向的报文绝大多数是控制包。对于一个下载场景来说这是正常的,因为接收方不会发大量数据,只需要回 ACK。但对于一个上传场景来说就要警惕:如果你本机在传输大文件,发送方向应该是大量 1514 的满载段,而 40-79 区间占比不应该太高。
4.4 结合流量图验证长度规律
长度统计得出的结论可以再用 Statistics -> TCP Stream Graphs -> Time Sequence (Stevens) 来验证。这个图会把 TCP 段的长度画成阶梯状。如果图里大部分阶梯是很矮的横线,说明发送的数据段都很短;如果出现大量近乎垂直的长竖线,说明一段一段的数据都是满负荷 1460 字节。长度分布和图形相互印证之后,结论就比较可信了。
5. 没有图形界面的服务器场景:用 tshark 命令快速统计长度分布
很多时候 pcap 文件在 Linux 服务器上,不想来回传,或者要批量分析几十个文件。这时候 Wireshark 的图形界面用不上,直接用 tshark 命令配合标准文本处理工具,效率非常高。
5.1 导出长度字段序列
tshark 的-T fields -e frame.len可以把指定字段逐行打印出来。结合显示过滤器,可以先限定方向:
# 只看本机发送方向的所有包包长 tshark -r capture.pcap -Y "ip.src==10.0.0.2" -T fields -e frame.len | head # 只看本机发送方向、且包含有效载荷的TCP包 tshark -r capture.pcap -Y "ip.src==10.0.0.2 && tcp.len>0" -T fields -e frame.len | head这两条命令的价值在于:后续任意文本处理都能接上。 awk、sort、uniq、python 都行。
5.2 统计最大值、最小值、平均值
排查“数据包长度异常”最常用的是一条 awk 命令:
tshark -r capture.pcap -Y "ip.src==10.0.0.2" -T fields -e frame.len | \ awk '{sum+=$1; n++; if ($1>max) max=$1; if (n==1 || $1<min) min=$1} \ END {printf "count=%d avg=%.1f min=%d max=%d\n", n, sum/n, min, max}'实际输出类似:
count=18624 avg=682.3 min=54 max=1514avg 682 意味着平均包长处于中等水平,感觉上不像纯控制包流。如果你看到 avg 只有 70 左右,而应用明明在传数据,那就说明数据被拆成了大量小包。
5.3 按长度档位统计频次
想知道哪些长度值出现最多,用 sort 和 uniq -c 组合:
tshark -r capture.pcap -Y "tcp.len>0" -T fields -e frame.len | \ sort -n | uniq -c | sort -rn | head -20输出里第一列是包数量,第二列是帧长度。如果排在前面的全是 1514 和 54 附近的值,属于“满载数据 + 纯控制包”的正常组合。如果排在前面的长度非常分散,比如 283、467、551 这类,说明应用层写入的数据大小没有和 TCP 缓冲区对齐,每条连接都在发送自定义大小的块,这本身不一定有问题,但结合高延迟、低吞吐一起看时,通常能定位到应用侧。
5.4 capinfos 看全局平均长度
如果只是想知道整份抓包的平均包长、报文总数,不需要 tshark 过滤,直接使用 capinfos:
capinfos capture.pcap输出里可以关注这几项:
- Number of packets:报文总数
- Data packet size average:平均数据包大小
- Data byte rate:每秒字节数
- Average packet rate:每秒报文数
capinfos 是 Wireshark 套件自带的工具,和 tshark 在同一个目录,正常情况下装了 Wireshark 就有。
5.5 一条命令完成发送方向长度分布分析
最后分享一个我实际在服务器上排查时常用的完整命令链:
echo "=== 发送方向总览 ===" tshark -r capture.pcap -Y "ip.src==10.0.0.2" -T fields -e frame.len | \ awk '{sum+=$1; n++; if ($1>max) max=$1; if (n==1 || $1<min) min=$1} \ END {printf "count=%d avg=%.1f min=%d max=%d\n", n, sum/n, min, max}' echo "=== 长度TOP20 ===" tshark -r capture.pcap -Y "ip.src==10.0.0.2 && tcp.len>0" -T fields -e frame.len | \ sort -n | uniq -c | sort -rn | head -20一条命令包住了数量、平均值、最大值和最常出现的长度档位,用来快速判断链路状态非常够用。
6. 长度统计最容易踩的五个坑,以及一次真实排障记录
前面的方法都很顺手,但实际用的时候有几个坑我踩过不止一次,每次都能多耗几个小时。这里单独列出来。
6.1 所有大包长度卡在同一个数字:snaplen 截断
如果抓包时设置了抓包长度限制,超过限制的包会被截断。最常见的结果是:大量包的 frame.cap_len 都等于同一个值,比如 96、128、512。更迷惑的是截断后 frame.cap_len 和 frame.len 在 Wireshark 里的显示是一样的,因为 Wireshark 只拿到了截断后的数据,无法知道原始长度。
所以当你看到大批包的长度恰好是 96、128、512 这类整数,且包内应用层数据明显不完整,第一反应应该是“抓包长度被限制了”。解决方法是重新抓包时不加长度限制,tcpdump 用-s 0,Wireshark 的抓包选项里把“Limit each packet to”调成 default 或最大。顺带一提,过滤器frame.cap_len < frame.len可以找出已经被明确标记截断的包,但截断模式下两者相等的情况更多,靠这个过滤并不保险。
6.2 网卡 offload 打开后出现超大帧和坏校验
TSO、GRO、GSO 这类网卡加速会把多个报文合并成一个大块再交给抓包接口,Wireshark 里会出现几万字节的以太网帧,并伴随 TCP checksum 显示为 bad。这种长度统计完全失真,不能拿来做分析。Linux 下排查时需要确认网卡 offload 状态,必要的时候关闭:
ethtool -k eth0 | grep -E "tcp-segmentation-offload|generic-receive-offload"关闭命令:
ethtool -K eth0 gro off gso off tso offWindows 下的 Wireshark 则可以去网卡驱动属性里关闭 Large Send Offload 和 Large Receive Offload。抓性能数据时建议提前关掉,否则长度分布和校验和都不真实。
6.3 别忘了 802.1Q VLAN Tag 的 4 字节
在 Trunk 口上抓包时,每个帧都会多出 4 字节的 VLAN Tag,正常最大帧因此从 1514 变成 1518。如果你直接用frame.len > 1514去筛选巨型帧,会把带 VLAN 的普通满载帧也捞出来。判断时要根据抓包环境做调整:有 VLAN 就按 1518 算,没 VLAN 按 1514 算。更精确的做法是先过滤 VLAN 再看长度:
frame.len > 1518 vlan && frame.len > 1518这 4 字节在统计大文件传输时影响不大,但在你追查 MTU 争议时非常关键。
6.4 最小帧填充:54 还是 64 的问题
以太网规定最小帧长是 64 字节(含 FCS),而 Wireshark 默认抓到的以太网帧通常不含 FCS,所以很多纯 ACK 包显示为 54 字节或者 60 字节。有些刚接触抓包的人以为看到了损坏包,其实只是因为抓包环境没有把填充字节和 FCS 记录进来。只要你用tcp.len == 0能筛出这些短包,并且它们都是 ACK、SYN、FIN 这类控制报文,就完全不用担心。不要因为在 Packet Lengths 里看到大量 40-79 字节的包就以为是网络故障。
6.5 广播、ARP 干扰了“发送方向”的统计
如果你只过滤了ip.src == 本机IP,ARP 和 DHCP 这类广播报文也会被算进长度统计里。ARP 请求包很小,往往只有几十字节,会把整体平均长度拉低。严谨的统计建议至少加上&& tcp或者&& ip,把非 IP 报文排除掉。尤其是排查“为什么发送方向平均包长这么低”的时候,先排除 ARP 再下结论。
6.6 真实案例:发送方向全是小包,问题出在应用层写小块数据
去年排查一个内部系统大文件上传速度上不去的故障。抓包后,我直接用 Packet Lengths 看发送方向的长度分布,结果 40-79 区间的包占比超过 70%,满载 1514 的包寥寥无几。一开始我怀疑是 MTU 或 MSS 协商问题,但查看 SYN 里的 MSS 选项,协商值是正常的 1460。
继续深入后,在时间戳上发现规律:本机每隔几十毫秒就发送一个一两百字节的 TCP 段,而不是积攒到 1460 字节一次性发出去。这是典型的应用层小数据写入场景,再加上 Nagle 算法没关,TCP 会等待 ACK 到达后再把缓冲区里的小块数据发出去,造成延迟叠加。后来在应用侧开启 TCP_NODELAY,并把多次小写入合并成一次大写入后,发送方向的长度分布立刻变成了满载段为主,传输速度翻了将近三倍。
那次之后,我抓包第一件事永远是看长度分布。它不复杂,却是建立流量体感最直接的方式:先看分布,再按方向过滤,最后定位到具体连接的规律。相比一上来就翻协议解析,这个路径要快得多。