抓包
Wireshark / tshark · 按 tick 对齐 · 返回总条目
在客户端与本地服务端之间抓一次,按 50 ms 的 tick 对齐,就能直接看到 C08 与半个 C07 是否落在同一批帧里、服务端有没有下发 S32,以及每个包的 ID 与长度前缀。
两个前提
不满足这两条,抓到的字节流解不开:
- 不加密。服务端开
online-mode=false,或用本地代理把加密层代掉。加密之后抓到的是 AES/CFB8 密文,包头也读不出来。 - 知道有没有压缩。服务端一旦下发
set compression,超过阈值的包结构会从「长度 + 包 ID + 载荷」变成「长度 + 未压缩长度 + 载荷」,阈值以下的包仍按原样发。两种结构混在同一条连接里。
抓环回流量要选 loopback 适配器(Windows 上是 Npcap 的 Npcap Loopback Adapter),不要选物理网卡。
抓一次
- 起本地服务端,客户端连
127.0.0.1:25565。 - 开始抓包,过滤器先留空,等进入游戏后再加条件。
- 主手持弓,拉满 20 tick,按流程发出这一串。
- 停止抓包,套用显示过滤器
tcp.port == 25565 && tcp.len > 0。
命令行版本,按时间顺序导出每个 TCP 段的时间戳与载荷:
tshark -r pullbow.pcapng -Y "tcp.port == 25565 && tcp.len > 0" \
-T fields -e frame.number -e frame.time_relative -e tcp.len -e tcp.payload
要看整条连接的重组字节流,用 follow:
tshark -r pullbow.pcapng -q -z follow,tcp,raw,0
怎么切包
TCP 是字节流,一个段里可能塞了好几个包,也可能把一个包拆到两个段里。所以不要按段数包,要按字节流切:
- 读第一个 VarInt,得到这一帧的长度。
- 往后取该长度的字节,得到载荷。
- 载荷的第一个 VarInt 就是包 ID。
- 按该 ID 对应的字段结构把剩下的字节读完,继续下一帧。
VarInt 每字节 7 位有效数据,最高位为 1 表示还有后续字节。包 ID 是 VarInt 而不是定长整数,这也是手工切包最容易错的一步。
怎么判断同一 tick
服务端主循环 20 tick/秒,一 tick 50 ms。同一 tick 内发出的几个包,抓包时间戳通常相差不到 1 ms;跨 tick 的则至少差 40 ms 以上。用 frame.time_relative 排序看差值就够分辨。
严格判定要回到客户端侧:在发包处打时间戳并与主循环帧号对齐,再和抓包时间戳比对。抓包只能给出网络上的先后,给不出客户端内部的执行顺序。
| 看到的现象 | 说明 | 下一步 |
|---|---|---|
C08 与 C07 落在同一毫秒 | 这一轮对齐成立 | 继续看服务端有没有回 S32 |
| 两者相差 40 ms 以上 | 跨了 tick,C08 的重置没有作用到释放上 | 检查发送路径是否经过主线程队列 |
只有 C08,没有 C07 | 释放没有发出去,或被压缩阈值改变了解析 | 先确认压缩状态,再核对切包 |
有 S32 但没有 C0F | 回号没走异步路径 | 检查 Pong 是否被排队 |
一串 C04 连续出现 | 级联上报,位移与朝向都在里面 | 对照 Serverbound 一侧的字段表 |
常见坑
- Nagle 合并。连续小包会被攒到一起发,看起来像一个大包。抓包时关掉 Nagle,或按字节流切包而不是按段切。
- 压缩阈值。同一条连接里大包带未压缩长度、小包不带,切包逻辑要按阈值分支。
- 环回网卡。选错适配器会一个包都抓不到,且不会有报错。
- 时间戳精度。Wireshark 默认微秒级,够用;改成秒级就分不出同 tick 了。
切出包之后,把 ID 对照一下:1.21.4 的取值见包 ID 对照;字段含义见Clientbound 与Serverbound 两侧。