网络协议

基本信息

属性 值
类型 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"。

相关条目