用Wireshark统计发送数据包长度:从抓包到定位应用层传输问题
2026/9/16 7:47:01 网站建设 项目流程

聊个很多人都会忽略的细节。抓包不难,难的是从抓回来的几千个数据包里看出问题。有一次朋友内网传文件奇慢,网卡明明是千兆,实际吞吐只有几十兆,我让他把抓包文件发过来,随手打开 Statistics 里的 Packet Lengths,一眼就看出了问题——他那个程序发出的包一半以上都集中在几十字节的区间。这篇文章就把"统计发送的数据包长度"这件事讲透:从怎么抓、怎么过滤、怎么统计,到怎么从一张包长分布图里反推出应用层到底干了什么。适合刚会用 Wireshark 抓包、但面对一堆数据不知道从哪下手的同学,也适合想用数据说话的网络运维。

1. 包长统计能看出什么:三个最典型的实战场景

1.1 小包多、吞吐低:先看包长分布再去找原因

先说第一个场景。以太网帧本身有开销,帧间隙 12 字节,前导码 8 字节,加上 Ethernet 头 14 字节,一个 64 字节的最小帧实际在链路上要占 84 字节。如果你发的全是小包,有效载荷占比就非常低。举个例子,64 字节帧里 IP 头占 20 字节,TCP 头占 20 字节,真正给应用的数据只有 24 字节左右,有效载荷率不到三成。千兆网卡每秒能处理的包数是有上限的,以入门级网卡百万包每秒来算,如果全发 64 字节小包,理论吞吐也就 500Mbps 左右,和你千兆的口子相比直接腰斩。所以当你发现吞吐上不去,最快的一步就是先看看包长分布里小包占比是不是异常高。

这就是包长统计的第一个价值:快速锁定"是不是小包拖垮了吞吐"。你不用去算包每秒,也不用查网卡参数,只要 Packet Lengths 窗口里 40~79 字节这个区间占了绝对大头,心里就要有数了。

1.2 验证应用有没有吃满 MTU

第二个场景是反向验证。比如你调一个上传模块,怀疑它没有善用 MTU,把大文件拆成了很多小块去发送。以太网标准 MTU 是 1500,加上 14 字节以太头,数据包在链路上最大通常是 1514 字节,带 VLAN 标签的话还要再加 4 字节,变成 1518。如果 TCP 分段正常,一个持续传输的 TCP 流里应该能看到大量 1514 字节左右的满载帧,因为应用写多少数据,TCP 层都会切成 MSS 大小(一般 1460 字节)的段发出去。反过来,假如你统计发送的包,发现 1000 字节以上的帧几乎没有,全是几百字节的,那问题基本就出在应用层:写缓冲区太小、每次发送的字节数太少,或者 Nagle 算法和延迟 ACK 叠加导致吞吐劣化。

这里顺带说一句,很多人以为"我发送了多大,抓包就应该看到多大的帧",这个想法是错的。TCP 是流协议,它不保证你的应用写入边界和网络包边界一一对应,中间还有分段和粘包。所以用包长统计去验证"应用程序有没有高效利用网络",本质上是看帧长度的分布形态,而不是看单次发送的大小。

1.3 给正常流量建基线,异常一眼可见

第三个场景,也是我比较喜欢用的:把包长分布当作流量的"指纹"。一个业务系统在稳定运行的时候,它的包长分布是相对固定的。比如一个心跳类服务,绝大多数包在 60~100 字节;一个视频推流服务,大包占绝大多数;一个 Web 服务,则是典型的双峰分布,既有几十字节的 ACK 和请求,也有 1400 字节左右的大响应。

一旦某天分布形态变了,比如大包比例骤降、或者突然冒出大量超大帧,就说明业务行为有变化,或者是网络中间设备出了状况。你不需要读懂每一个包,只需要定期导出一份包长统计做对比,异常就能在很早期暴露。这不是什么高深的东西,但实操中真的很管用,比盯着一堆告警面板直观得多。

2. 抓包前先把"发送"两个字定义清楚

2.1 抓包位置决定了"发送"的方向

标题里有个词容易被忽略——"发送"。在开始统计之前,你得先想明白:你抓到的数据包,哪些算"发送"?这取决于你抓包的位置。

如果直接在业务服务器上抓包,Wireshark 会同时看到两个方向的流量:进来的和出去的。对服务器来说,出去的包(也就是它发送的包)通常用ip.src == 服务器IP来过滤;对客户端来说,则是ip.src == 客户端IP。注意,这里说的是 IP 层方向,不是数据链路层。

但如果你是在交换机镜像口抓包,情况就复杂一点。镜像口上看到的流量是双向的,你得根据设备的 MAC 或者 IP 来判定方向。假如两台服务器 A 和 B 通信,你在核心交换机上做了端口镜像,这个口连的是 A,那看到的帧里源 MAC 为 A 的就是 A 发送的,源 MAC 为 B 的就是 A 接收的。所以"发送"不是一个绝对概念,它跟着抓包点走,抓包前必须把这个口子捋清楚。

2.2 用 BPF 捕获过滤器减小干扰

想清楚方向之后,建议在抓包阶段就用捕获过滤器把流量圈小一点,别一股脑全抓。BPF 语法里,src host 192.168.1.100表示只抓源地址是这个 IP 的包,配合tcp or udp可以限定协议。比如我要统计某台客户端发给服务器的包,可以这样写:

src host 192.168.1.100 and tcp

这样抓下来的文件会干净很多,Wireshark 处理起来也快,文件还不容易大到几百兆。不过要注意,捕获过滤器和显示过滤器是两套语法:捕获过滤器用 BPF,显示过滤器用的是 Wireshark 自己的语法(比如ip.src == 192.168.1.100)。很多人把这两者搞混,在捕获过滤器里输入ip.src==1.2.3.4,结果一个包都抓不到,这种低级错误我见过不止一次,写的时候留意一下输入框的背景色就行,BPF 语法输入错误会直接提示。

2.3 分清 frame.len、ip.len、tcp.len、data.len

正式开始统计之前,有一个最基础的概念必须理清:所谓"数据包长度",到底指的是哪个长度?Wireshark 里和长度相关的字段有好几个,统计的时候选错了,结论就全歪了。

字段含义典型用途
frame.len整个帧在链路上的长度,含以太网头包长分布统计首选
frame.captured_len实际捕获到的字节数,可能被 snaplen 截断判断抓包是否截断
ip.lenIP 包总长度,不含以太网头排除以太头干扰
tcp.lenTCP 段的载荷长度,不含 TCP 头和 IP 头看应用数据分段
data.len应用层数据长度配合上层协议分析

打个比方:frame.len 相当于一个快递包裹的整体尺寸,ip.len 是去掉外包装之后箱子的大小,tcp.len 才是里面商品本身的体积。你做"发送的数据包长度"统计,如果没有特别说明,我建议直接用 frame.len,因为它才是最真实的线上尺寸,也和网卡、交换机、抓包工具的统计口径一致。后面我讲的命令、操作,默认都是 frame.len。

3. Wireshark 图形界面统计发送包长:一步步操作

3.1 Packet Lengths 统计窗口怎么读

假设你已经用src host过滤器抓好了包,现在打开 Wireshark,点击菜单 Statistics -> Packet Lengths,会弹出一个分区间的统计表。默认分组是 40-79、80-159、160-319、320-639、640-1279、1280-2559 这些区间,每一行显示这个区间内的包数量、占比、累计数量、累计占比。窗口最上面还有一行总的包数量和平均包长。

读这个表有个诀窍:先看最大那个区间的占比。比如一个 TCP 下载流,1280-2559 区间应该占大头,因为满载帧 1514 字节正好落在这个区间;如果这个区间几乎没有,说明链路上根本没有大包在跑。再看 40-79 区间,这里是 TCP ACK 和各类小包控制包的聚集地,纯下载场景下它会有一定比例,但不会高得离谱。两个区间一对比,传输效率大概什么样,心里就有数了。

Packet Lengths 窗口里也可以直接输入显示过滤器,比如我只想统计某个 IP 发送的包,就在窗口左下方的过滤框里输入ip.src == 192.168.1.100,再点 Apply,统计表会实时变成过滤后的结果。这个功能很多人没注意到,其实是挺好用的,省得在主界面反复切换过滤器。

3.2 用显示过滤器精确圈定"自己发的包"

如果是抓的完整双向流量,想单独统计发送方向,就在主界面的显示过滤器里设置:

ip.src == 192.168.1.100

然后等过滤器框变绿,再去看统计。这里有个小细节:如果你只关心 TCP 而且不在乎 TCP 重传、乱序等异常包,可以在后面加上&& !tcp.analysis.flags来过滤掉所有 TCP 异常标记包,这个我们在第 5 节会详细讲,统计基线一定要用干净的流量。

还有一类场景是抓 HTTP 或 HTTPS 流量。发出去的请求包都很小,回来的响应包很大,如果你只统计"发送",就会看到包长分布正好是"小包为主",这是完全正常的。所以每次做统计的时候,一定要记住你的业务流特征,别拿一个结果硬套所有场景。

另外,Wireshark 的会话统计(Statistics -> Conversations)和端点统计(Statistics -> Endpoints)里也有按方向拆分的字节数和包数。比如在 Conversations 窗口里,选中一个 TCP 会话,可以看到 A->B 和 B->A 两个方向的包数和字节数,点击 Bytes 列还能排序找到最"肥"的会话。这虽然不是严格意义上的包长分布,但配合 Packet Lengths 可以快速定位到具体是哪个连接在大量发包,是排查"谁在偷偷占带宽"的好工具。

3.3 IO Graph 动态观察包长变化趋势

分布统计看的是"整体形态",有时候你还想知道包长随时间怎么变,这时候就要用 IO Graph。菜单 Statistics -> IO Graph,默认是一张所有流量的速率折线图,单位是包每秒。重点是左侧的 Filter 栏,你可以输入多个条件同时画多条线。

比如我想看"大于 1400 字节的发送包"和"小于 100 字节的发送包"分别是什么趋势,可以配置两条规则:

  • 第一条 Filter 填ip.src == 192.168.1.100 && frame.len > 1400
  • 第二条 Filter 填ip.src == 192.168.1.100 && frame.len < 100
  • 把 Y Axis 改成 Bytes,单位选 Bytes/tick,这样两条线的纵坐标就是字节数

图形出来后非常直观:如果大包那条线在某些时间段突然断了,而小包线还一直撑着,说明那个时间段里应用写数据的节奏出了问题。IO Graph 的 tick 间隔可以调,一般 1 秒就能看得很清楚了,时间跨度大就改成 5 秒或者 10 秒。这里提醒一下,IO Graph 默认统计的是符合过滤器的包数量,不是字节数,看吞吐趋势一定要在 Y Axis 里改成 Bytes。

4. 要精确数值就上 tshark:命令行统计平均包长与分位数

GUI 的 Packet Lengths 已经很直观了,但如果你要写报告、要精确的平均包长、要 P50/P95 分位数,或者要批量处理几十个抓包文件,还是得上 tshark。tshark 和 Wireshark 用的是同一套解析引擎,你不用担心统计口径不一致,它只是把图形界面变成了命令行。

4.1 一条命令算出平均长度、最大最小长度

最简单的用法,先过滤出想要统计的包,然后只输出 frame.len 字段,再交给 awk 做计算。假设抓包文件叫 send.pcapng,统计 192.168.1.100 发送的 TCP 包长度:

tshark -r send.pcapng -Y "ip.src == 192.168.1.100 && tcp" -T fields -e frame.len | awk '{sum += $1; if ($1 > max) max = $1; if (min == 0 || $1 < min) min = $1; count++} END {printf "count=%d, avg=%.1f, min=%d, max=%d\n", count, sum/count, min, max}'

输出类似于count=85432, avg=1178.3, min=54, max=1514,这样就拿到了最核心的三个数。

如果想看分位数,可以用 sort 排序后再算。比如算 P95,也就是从小到大排在第 95% 位置的那个包长:

tshark -r send.pcapng -Y "ip.src == 192.168.1.100 && tcp" -T fields -e frame.len | sort -n | awk '{arr[NR] = $1} END {print "P95 =", arr[int(NR * 0.95)]}'

这些都是 Linux 和 macOS 自带的工具,Windows 上没有 awk 的话,可以用 Git Bash 或者 WSL 跑。嫌麻烦也可以把 tshark 输出重定向到文件,再用 Excel 打开排序,方法多的是,重点是拿到数字之后要能解释得通。

4.2 按目的地址和协议做分组统计

光有整体平均还不够,有时你要知道这台机器发给不同服务器的包长有没有差异。比如它给数据库服务器发的是大包,给监控服务器发的是小包,混在一起统计就看不清了。tshark 有官方的统计模块,用 -z 参数就可以直接出分组结果:

tshark -r send.pcapng -q -z conv,ip

这条命令输出所有 IP 会话的双向统计,包括包数、字节数、平均包长。如果你只想看单向,可以先过滤再统计:

tshark -r send.pcapng -Y "ip.src == 192.168.1.100" -q -z conv,ip

还有一个非常实用的-z io,stat

tshark -r send.pcapng -q -z io,stat,10

这个按 10 秒一个区间输出每个时间窗口的帧数、字节数、平均包长。字段分别是 Frames、Bytes、Avg frame bytes,一眼就能看出哪个时段吞吐异常。想加过滤条件也可以,比如只看大包:

tshark -r send.pcapng -q -z io,stat,10,"ip.src == 192.168.1.100 && frame.len > 1400"

这样每个窗口里的大包数量、字节数都有了,和 GUI 的 IO Graph 效果一样,但更容易写进脚本批量处理。

4.3 按时间窗口切片,看包长波动

配合过滤器,你还可以针对特定协议做切片。比如只看 UDP 语音流量的包长分布:

tshark -r voip.pcapng -Y "udp && ip.src == 192.168.1.100" -T fields -e frame.len | sort -n | uniq -c | sort -rn | head -20

这会把最常出现的包长前十名列出来,G.711 语音流通常是 200 多字节的帧,G.729 则在 70 字节上下。看到这种"高度集中在一个长度"的分布,基本就能判断是固定时长的语音包。如果你发现一堆长度乱七八糟的 UDP 包,那可能是应用在逐字节地往外吐数据,性能问题基本跑不掉。

这里给个忠告:tshark 的输出字段顺序、引号嵌套在不同平台上有细微差别,Windows PowerShell 里双引号容易出问题,建议要么用单引号括过滤器,要么把命令写进脚本再执行。别在命令行里反复试,容易把自己绕晕。

5. 包长统计容易踩的三个坑及完整排查过程

5.1 为什么文件里最大只有 520 字节,应用却说发了 2090 字节

这是一个非常经典的问题,网上也经常有人问。你写了个程序,调一次发送接口发了 2090 字节,抓包一看,最大的包才 520 字节,第一反应就是怀疑抓包工具截断了。先别急着怪 Wireshark,我带你完整走一遍排查链路。

第一步,先看包的 frame.len 和 frame.captured_len 是否一致。在 Wireshark 里选中那个 520 字节的包,展开 Frame 层,如果frame.captured_len == frame.len,说明抓包没有截断,snaplen 没问题;如果 captured_len 明显小于 len,那才是真正的截断,去 Capture Options 里把"限制每个包的长度"选项改成 65535 再抓一次。

第二步,确认是不是 TCP 分段。如果第一步确认没有截断,那大概率是这个 2090 字节被 TCP 拆成了多段。以太网 MTU 1500,减去 IP 头 20 字节和 TCP 头 20 字节,MSS 就是 1460。2090 字节的数据会被拆成 1460 加 630 两段,链路层帧长分别约为 1514 和 684。你看到"最大只有 520 字节",很可能是过滤条件把 1514 的大帧筛掉了,或者这个文件里大多数是别的流量。用前面第 4 节的 max 计算一看便知:

tshark -r send.pcapng -Y "ip.src == 你的IP" -T fields -e frame.len | sort -n | tail

第三步,看看是不是 MTU 异常小。如果整个会话里最大帧长确实只有 520 多字节,且所有大包都被切成了这个尺寸,那就要怀疑链路 MTU 是不是被调小了,比如是不是走了某种隧道封装、拨号链路,或者中间设备做了 MSS 钳制。这种情况下,包长统计表会呈现一个很奇怪的现象:所有包的帧长都被"削"到同一个很小的上限附近,没有正常的 1514 帧。确认方法很简单,看看有没有对应的小包 ICMP 差错报文,或者直接在链路两端跑一次大包 ping 测试。

第四步,关于"怎么显示 2090 字节"。如果你关心的是应用层整体发送了多少数据,不要试图在单包里找 2090,而应该用 Follow TCP Stream 看重组后的完整流,或者用 Statistics -> Flow Graph 确认同一序号区间的分包情况。TCP 是流,不是包,统计"发送的数据包长度"统计的是 TCP 分段后的线上帧,不是应用写入大小,这两件事必须分开。

5.2 TSO/GRO 卸载让包长数据"失真"

第二个坑和网卡卸载功能有关。Linux 和 Windows 的网卡驱动普遍开启了 TSO(TCP 分段卸载)和 GRO(接收侧合并)。TSO 的意思是:应用一次发送大块数据,驱动直接把这个大块交给网卡,网卡硬件自己去做 TCP 分段。在抓包软件看来,有时会看到超过 MTU 的超大帧,比如 6000 多字节的"超级帧",这不是网络真的在传巨型帧,而是抓包点拿到了网卡驱动里尚未分段的数据。

反过来,GRO 在接收方向把多个小段合并成一个较大的帧交给上层,抓包也可能看到合并后的长度。于是你的包长统计会出现一些"看起来不真实"的数值:要么冒出 4000、6000 字节的包,要么所有包都偏大。这不是 Wireshark 的问题,是你的抓包点在网卡驱动和协议栈之间,看到的是卸载前后的中间状态。

解决办法有三个。最简单的是统计时心里有数,知道哪些是超大帧,别把它们当成线上真实帧长;第二个办法是在被测机器上关掉卸载功能,Linux 下用ethtool -K eth0 tso off gro off gso off,Windows 下在网卡高级属性里关闭"大量发送卸载";第三个办法是换抓包位置,比如用交换机镜像口,那里看到的是完全真实的线上帧。我一般建议先不关卸载,抓完看到异常再决定,因为这本身也是排查线索——如果某台机器突然出现大量超大帧,先检查它的卸载设置是不是被动过。

5.3 重传包、乱序包混入统计导致结论跑偏

第三个坑比较隐蔽。TCP 丢包重传的时候,同一个序号的数据会在抓包里出现多次。如果你只是按ip.src过滤统计发送包长,重传的包会被重复计算,包数和字节数都会虚高。更麻烦的是,超时重传的包往往是应用已经写出的同一批数据,重复计数之后,你统计出来的"发送数据包长度"和真实的新数据发送量会有偏差。

怎么避免?在显示过滤器里加一个排除条件,把 TCP 分析标记过滤掉:

ip.src == 192.168.1.100 && !tcp.analysis.flags

tcp.analysis.flags会把乱序、重传、快速重传、重复 ACK、零窗口等一堆异常标记全部涵盖,是排查阶段保持统计干净的最好工具。如果你想保留重传做专门分析,就把!tcp.analysis.flags去掉,但统计基线数据建议始终排除。

另外还有一个容易被忽略的:ACK 包在双向各自统计。一个完整的 TCP 会话,A 发给 B 的是数据大包,B 发给 A 的是 ACK 小包。如果统计脚本只按 IP 方向抓,没问题;如果你误用了双向统计,会把 ACK 的 54 字节小包全部算进"发送",结果平均包长被拉得很低,看起来像"应用没发大包",其实是统计口径错了。这个问题在 GUI 的 Conversations 里最明显,它默认会分两个方向列出来,用的时候注意别只看合计列。

6. 一个真实案例:从包长分布反推应用的发送行为

说了这么多理论,最后讲一个我前段时间处理的案例,把整个思路串一遍。

有个同事负责的日志上报模块出了问题,客户反馈说上报速度慢,一个 20MB 的日志文件要传十几分钟。他把抓包文件发给我,我第一件事就是看包长分布。用 Packet Lengths 打开,结果非常异常:1280-2559 区间几乎为零,绝大多数包集中在 64 到 200 字节之间。这说明这个模块根本没有用满 MTU。

然后我用显示过滤器ip.src == 客户端IP看发送方向的包长,发现平均包长只有 180 字节。按这个平均值算,一个 20MB 文件要拆成差不多 12 万个包,而同样数据量如果正常按 1460 字节 MSS 发送,只要 1.4 万个包左右。包数量差了将近 9 倍,传输效率自然上不去。

再往下挖,问题出在应用代码上。同事的日志模块为了实时上报,把每一条日志都当成一次单独的网络写入,每条日志短的几十字节,长的几百字节。也就是说,应用的写缓冲太小,TCP 层几乎每次都要发出一个不满载的段。再加上 Nagle 算法和延迟 ACK 的相互作用,有时候还会产生额外的等待,进一步放大延迟。

这里顺便说一句,我当时怎么确认是 Nagle 导致的延迟:用 IO Graph 的 1 秒粒度看发送速率,能明显看到周期性"一坨一坨"的突发,而不是平滑的流,这个特征就是经典表现。让同事在 socket 上开启 TCP_NODELAY,并把日志攒成 4KB 左右的缓冲批量写,问题迎刃而解。

这个案例想说明的是:包长统计不是拿来炫技的,它最大的价值是帮你把"应用层怎么用网络"这件事变成一张可以被讨论的图。从一张分布表,到平均包长的一个数字,再到 IO Graph 的趋势,层层递进,问题就藏不住。

最后再分享一个我自己的操作习惯:每次抓包完成,不管有没有发现问题,我都会花十秒钟跑一下 Packet Lengths,顺手把平均包长记在笔记里。时间长了,你对自己系统的包长基线会敏感得可怕,某天数字不对劲,不用等用户报障,你就已经知道有人改了代码。这个习惯,比任何高级技巧都值钱。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询