网络协议
基本信息
| 属性 | 值 |
|---|---|
| 类型 | FML SimpleNetworkWrapper 风格的 NBT 通道 |
| 通道名 | bdew.neiaddons(NEIAddons.channelId) |
| 协议版本 | netVersion = 1(NEIAddons.netVersion) |
| 编解码器 | network/NBTMessageCodec,MessageToMessageCodec<FMLProxyPacket, NBTTagCompound> |
| 传输格式 | NBTTagCompound 经 CompressedStreamTools.write/read 压缩写入 Unpooled.buffer() |
包体结构
PacketHelper.makePacket 统一封装,固定两层:
| 字段 | 类型 | 内容 |
|---|---|---|
cmd |
String | 子命令名 |
data |
NBTTagCompound | 该命令的负载 |
PacketHelper 只暴露两个方法:
| 方法 | 方向 | 实现 |
|---|---|---|
sendToServer(cmd, data) |
客户端 → 服务端 | NEIAddons.channel.sendToServer(...),底层取 channels.get(Side.CLIENT) 并把 FML_MESSAGETARGET 设为 TOSERVER |
sendToClient(cmd, data, player) |
服务端 → 指定玩家 | NEIAddons.channel.sendTo(...),FML_MESSAGETARGET 设为 PLAYER |
NetChannel 还实现了 sendToAll / sendToAllAround / sendToDimension,但本 mod 源码中没有任何调用点(sendToAllAround 与 sendToDimension 仅有 NetworkRegistry.TargetPoint 签名,仓库内无实例化)。
全部网络消息
cmd |
方向 | 负载 | 何时发送 |
|---|---|---|---|
hello |
服务端 → 客户端 | commands(String,服务端已注册的子命令名,; 分隔)+ version(int,NEIAddons.netVersion) |
PlayerEvent.PlayerLoggedInEvent 与 PlayerEvent.PlayerChangedDimensionEvent 两种事件都触发 |
SetAE2FakeSlot |
客户端 → 服务端 | slot(int,槽位号)、replace(boolean)、item(物品的 NBT) |
玩家在 AE2 界面上对 SlotFake 拖拽/点击物品时 |
⚠️ 全源码只有 2 个 cmd 字符串:"hello"(ServerHandler.sendPlayerHello)与 AddonAppeng.setWorkbenchCommand = "SetAE2FakeSlot"(appeng/AddonAppeng.java:36)。
SetRecipeCommandHandler 会对任意 stacks 列表填充容器,但没有任何地方 registerHandler 它,所以对应的客户端上行命令在本版本里根本不会被服务端接受——见 叠加层与 GUI 挂钩。
握手与版本校验
服务端侧 ServerHandler:
- 构造时
FMLCommonHandler.instance().bus().register(this)订阅 FML 事件。 handlePlayerLoggedIn/handlePlayerChangedDimension都调sendPlayerHello(player),把handlers.keySet()用StringUtils.join(..., ';')拼串发出去。channelRead0从ctx.channel().attr(NetworkRegistry.NET_HANDLER).get()强转成NetHandlerPlayServer,取出nh.playerEntity交给processCommand。
客户端侧 ClientHandler:
handleConnectEvent(ClientConnectedToServerEvent)只打日志并enabledCommands.clear()。- 收到
hello时先enabledCommands.clear(),再比对data.getInteger("version")与NEIAddons.netVersion;不一致就打警告并 return,enabledCommands保持为空集。 - 版本一致则把
commands串按;切分灌入public static Set<String> enabledCommands。 - 其它
cmd一律打"Uknown packet from server: %s"(Uknown是源码原样的拼写错误)。
enabledCommands 是整套「服务端是否装了本 mod」探测机制的核心。客户端功能在真正动手前都会先问一句:
if (ClientHandler.enabledCommands.contains(AddonAppeng.setWorkbenchCommand)) { ... }
服务端没装 NEI Addons 时集合为空,客户端走降级分支——AE2 那边会往聊天栏发一条红色 bdew.neiaddons.noserver(en_US 文案 "This function requires NEI Addons to be installed on the server")。
服务端子命令注册
ServerHandler.registerHandler(command, handler) 往静态 Map<String, SubPacketHandler> 里塞一项,重复注册直接抛 RuntimeException,消息是 "Tried to register handler for command %s that's already registered for %s"。
当前只有一处注册:appeng/AddonAppeng.init() 里的
ServerHandler.registerHandler(setWorkbenchCommand, new SetFakeSlotCommandHandler());
processCommand 找不到 cmd 时打 "Uknown packet from client '%s': %s"。找不到与处理过程中抛异常时都只打日志,不踢玩家。
传输层细节
NBTMessageCodec 标了 @ChannelHandler.Sharable(两边共用一个实例)。encode 用 ByteBufOutputStream + CompressedStreamTools.write,再包成带 FML_CHANNEL 属性值的 FMLProxyPacket;decode 整个体包在 try / catch (Throwable) 里,失败打 "Error decoding packet",finally 关流。因为解码失败是静默吞掉的,协议失步不会断连。
NetChannel.addHandler 用 ch.findChannelHandlerNameForType(NBTMessageCodec.class) 找到编解码器位置,再 pipeline().addAfter(name, side + "Handler", handler),所以 handler 名字分别是 "SERVERHandler" 与 "CLIENTHandler"。
相关条目
- Addon 架构与加载顺序 - 通道在
init阶段建立 - Applied Energistics 2 集成 -
SetAE2FakeSlot的完整生命周期 - 叠加层与 GUI 挂钩 - 未被注册的
SetRecipeCommandHandler - 开发者工具 - 纯客户端,不走网络