网络封包(ModularNetwork 与 7 个封包)
基本信息
| 属性 | 值 |
|---|---|
| 通道 | NetworkRegistry.INSTANCE.newSimpleChannel(ModularUI.ID)(network/NetworkHandler.java:23) |
| 通道名 | modularui2(取自 ModularUI.ID,ModularUI.java:27) |
| 底层 | SimpleNetworkWrapper(FML 1.7.10 SimpleNetworkImpl) |
| 封包数 | 7 |
| 初始化 | NetworkHandler.init(),由 CommonProxy.preInit() 调 NetworkHandler.init()(CommonProxy.java:52) |
功能
7 个封包与注册方向
NetworkHandler.init()(network/NetworkHandler.java:26-36)逐个注册,
按注册顺序分配递增的数字 id(packetId++,:24 与 :39/:40):
| 顺序 | 封包类 | 文件 | 注册方式 | 方向 | 占用 id 数 |
|---|---|---|---|---|---|
| 1 | SClipboard |
network/packets/SClipboard.java |
registerS2C(:27) |
服务端 → 客户端 | 1 |
| 2 | SyncConfig |
network/packets/SyncConfig.java |
registerC2S(:29) |
客户端 → 服务端 | 1 |
| 3 | OpenGuiPacket |
network/packets/OpenGuiPacket.java |
registerBoth(:31) |
双向 | 2 |
| 4 | ReopenGuiPacket |
network/packets/ReopenGuiPacket.java |
registerBoth(:32) |
双向 | 2 |
| 5 | CloseGuiPacket |
network/packets/CloseGuiPacket.java |
registerBoth(:33) |
双向 | 2 |
| 6 | CloseAllGuiPacket |
network/packets/CloseAllGuiPacket.java |
registerBoth(:34) |
双向 | 2 |
| 7 | PacketSyncHandler |
network/packets/PacketSyncHandler.java |
registerBoth(:35) |
双向 | 2 |
7 个封包类实际占用 12 个 id 槽(1 + 1 + 2×5)。 原因见
NetworkHandler.java:46-49:registerBoth内部先调registerS2C再调registerC2S, 两次调用各自执行一次packetId++(:43/:39)。 这也是 id 顺序敏感的原因——插入或重排封包会改变后续所有 id。其他 mod 不应硬编码 id。
三个注册方向方法的实现
| 方法 | 行 | Side 参数 |
|---|---|---|
registerC2S(Class) |
:38-40 |
Side.SERVER |
registerS2C(Class) |
:42-44 |
Side.CLIENT |
registerBoth(Class) |
:46-49 |
先 S2C 后 C2S,各占一个 id |
发送辅助方法
NetworkHandler 提供 3 个静态发送方法:
| 方法 | 行 | 目标 |
|---|---|---|
sendToServer(IPacket) |
:51-53 |
服务器 |
sendToWorld(IPacket, World) |
:55-57 |
指定维度(sendToDimension) |
sendToPlayer(IPacket, EntityPlayerMP) |
:59-60 |
指定玩家 |
封包接口 IPacket
network/IPacket.java:15 定义 IPacket extends IMessage(FML 的 IMessage):
| 成员 | 行 | 说明 |
|---|---|---|
void write(PacketBuffer) |
:22 |
序列化 |
void read(PacketBuffer) |
:30 |
反序列化;javadoc 明确要求「除读取外不要做任何别的事」(:26-27) |
executeClient(NetHandlerPlayClient) |
:38-42 |
@SideOnly(Side.CLIENT),默认返回 null |
executeServer(NetHandlerPlayServer) |
:50-53 |
默认返回 null |
fromBytes(ByteBuf) |
:55-63 |
@Deprecated——IMessage 的旧签名,内部转调 read |
toBytes(ByteBuf) |
:65-73 |
@Deprecated——同上,转调 write |
fromBytes / toBytes 会把 ByteBuf 包装成 PacketBuffer(:59 / :69),
所以实现类只需按 PacketBuffer 写。
剪贴板同步
SClipboard 是唯一的 S2C 封包,负责把服务端剪贴板内容推到客户端;
utils/serialization/ 包(见 utils/ 目录)提供配套的序列化。
ASM 补丁:ItemStack 变长编码
core/ModularUITransformer.java:15-19 注册了 IClassTransformer,
对 net.minecraft.network.PacketBuffer
(core/visitor/PacketByteBufferVisitor.java:14)做字节码改写:
| 改写 | 说明 | 行 |
|---|---|---|
writeByte → writeVarIntToBuffer / func_150787_b |
物品堆叠数改用 varint | :60-64 |
readByte → readVarIntFromBuffer / func_150792_a |
同上,读侧 | :65-69 |
丢弃 POP 指令 |
writeByte 返回 ByteBuf 而 writeVarIntToBuffer 返回 void |
:49-54 |
方法名在开发环境与生产环境不同(PacketByteBufferVisitor.java:17-22):
开发环境用 MCP 名(writeItemStackToBuffer / readItemStackFromBuffer),
生产环境用混淆名(a / c),由 ModularUICore.isDevEnv() 决定
(core/ModularUICore.java:54,injectData 里读 runtimeDeobfuscationEnabled)。
源码注释说明了原因(PacketByteBufferVisitor.java:15-16):
Mixin 给出的是混淆名而非 SRG 名,即使把 SortingIndex 设得更高也一样。
交互
| 触发 | 封包 |
|---|---|
| 打开界面 | OpenGuiPacket |
重开界面(保留 Container) |
ReopenGuiPacket |
| 关闭当前界面 | CloseGuiPacket |
| 关闭全部 MUI2 界面 | CloseAllGuiPacket |
| 同步值变化 | PacketSyncHandler |
| 服务端剪贴板变化 | SClipboard(S→C) |
| 客户端配置请求 | SyncConfig(C→S) |
相关条目
- 同步处理器 -
PacketSyncHandler实际同步的内容 - 值与同步接口 - 同步值的接口契约
- Mixin 与底层补丁 -
SimpleNetworkWrapperMixin与 ASM 变换器