半个包
参数减半,不是字节减半 · 返回总条目
“半个”不是把一个包截掉一半发出去,而是只带一半有效参数、另一半由服务端沿用上一个包。拉弓包里有两种半个:握手中的半个 C07,以及窗口内回发时用 C04 代替 C06 省掉朝向。
协议里每一帧都带长度前缀、按帧解码,少一个字节服务端就会把后面的流读错。所以“半个”只能是参数层面的省略:发送方知道接收方手里已经有这份状态,就不再重复写一遍。
两种半个
半个 C07 | 半个拉弓包 | |
|---|---|---|
| 发生在 | 首包之前,握手的第 4 步 | 首包之后,窗口内的回发 |
| 省掉的是什么 | 坐标与朝向填占位值,服务端不读;只有 action 有效 | Yaw 与 Pitch 整段不发,用 C04 代替 C06 |
| 沿用的是 | 此前正在使用的物品与玩家位置 | 上一个移动包报上去的朝向 |
| 省掉的字节 | 不改帧结构,只改字段取值 | 8 字节(两个 float) |
半个 C07
C07 是 ServerboundPlayerActionPacket,动作枚举里有一项 RELEASE_USE_ITEM。这一项只用得上动作字段:方块坐标固定填 BlockPos.ZERO,朝向固定填 DOWN,服务端处理释放时根本不看这两个值。
所以这个包照常带长度前缀、照常完整发出——只是坐标与朝向是占位值。字节数没变,变的只是有效信息的比例。“半个”在协议语境里一直指这个意思。
半个拉弓包
回发时的移动部分有两种写法,两者都是合法的独立包,只是携带的字段不同:
| 写法 | 移动部分 | 线上字段 | 字节数 | 没带的部分 |
|---|---|---|---|---|
| 一个 | C06(PosRot) |
X, Y, Z, Yaw, Pitch, On Ground | 8 × 3 + 4 × 2 + 1 = 33 | 无,全部由客户端给出 |
| 半个 | C04(Pos) |
X, Y, Z, On Ground | 8 × 3 + 1 = 25 | Yaw、Pitch 沿用上一个移动包的值 |
差的 8 字节正是两个 float。Charge Ticks 与 Sequence 不在移动包里:前者是客户端本地计时,后者由 C0F 带回,两种写法里都一样。
为什么可以省
窗口只有 1 tick。在这一 tick 里,客户端手里的朝向通常和上一次移动包报上去的完全一样——重复发一遍不增加信息,只增加占用,也增加对齐失败的概率。用 C04 省掉朝向,服务端沿用上一个包的值,结果等价。
前提是服务端那份状态必须是新的。Sequence 保证的正是这件事:配对号对得上,说明窗口就是刚开的那个,服务端手里的朝向也刚更新过。配对号对不上,整个包被丢弃,省掉的朝向也就无从谈起。
为什么容易混
两处都叫“半个”,但一处省的是字段取值(C07 填占位值),另一处省的是字段本身(C06 换成 C04)。前者不改变帧长度,后者少 8 字节。抓包时把两者当成一回事,就会在字节数上对不上。
| 在抓包里看到 | 这是哪一种 |
|---|---|
C07 后面跟着全零坐标与 DOWN | 半个 C07,握手第 4 步 |
C04 只有 25 字节移动字段 | 半个拉弓包,窗口内回发 |
C06 带满 33 字节 | 完整的回发写法 |
校验顺序与丢弃规则见Serverbound 一侧;在序列里的位置见握手序列。