Skip to content

网络架构 (Network Architecture)

Maingraph for MC 使用 NeoForge 的网络系统来实现客户端与服务端之间的数据同步。在多人游戏模式下,所有蓝图的存储和逻辑均以服务器为准(Source of Truth)。

核心组件

网络系统的实现主要分布在以下类中:

  • MGMCNetwork: 负责所有网络负载(Payload)的注册。
  • BlueprintNetworkHandler: 包含服务端和客户端处理负载的具体逻辑。
  • ltd.opens.mg.mc.network.payloads: 存放所有的负载类,每个类都实现了 CustomPacketPayload

通信流程

1. 获取蓝图列表

当玩家打开蓝图管理器时:

  • 客户端 发送 RequestBlueprintListPayload
  • 服务端 扫描存档目录下的 mgmc_blueprints 文件夹,返回 ResponseBlueprintListPayload
  • 客户端 接收后更新 GUI 列表。

2. 加载蓝图数据

当玩家在列表中选择并打开一个蓝图时:

  • 客户端 发送 RequestBlueprintDataPayload(name)
  • 服务端 读取对应的 JSON 文件,并返回 ResponseBlueprintDataPayload(name, data, version)
  • 客户端 接收后使用 BlueprintIO 反序列化并渲染。

3. 保存蓝图

当玩家在编辑器中点击保存或关闭确认时:

  • 客户端 将当前节点和连线序列化为 JSON 字符串。
  • 客户端 发送 SaveBlueprintPayload(name, data, expectedVersion)
  • 服务端 检查版本(竞态校验),写入文件,并返回 SaveResultPayload
  • 客户端 根据结果显示“保存成功”或错误提示。

4. 管理操作 (重命名/删除)

  • 重命名: 客户端发送 RenameBlueprintPayload -> 服务端执行文件移动 -> 服务端广播或回复新列表。
  • 删除: 客户端发送 DeleteBlueprintPayload -> 服务端执行文件删除 -> 服务端回复新列表。

技术细节

负载定义 (Payload)

每个负载都包含一个唯一的 Type 和一个 StreamCodec 用于二进制序列化。例如:

java
public record SaveBlueprintPayload(String name, String data, long expectedVersion) implements CustomPacketPayload {
    public static final Type<SaveBlueprintPayload> TYPE = new Type<>(Identifier.parse(MaingraphforMC.MODID + ":save_blueprint"));
    
    public static final StreamCodec<ByteBuf, SaveBlueprintPayload> STREAM_CODEC = StreamCodec.composite(
        ByteBufCodecs.STRING_UTF8, SaveBlueprintPayload::name,
        ByteBufCodecs.STRING_UTF8, SaveBlueprintPayload::data,
        ByteBufCodecs.VAR_LONG, SaveBlueprintPayload::expectedVersion,
        SaveBlueprintPayload::new
    );

    @Override
    public Type<? extends CustomPacketPayload> type() {
        return TYPE;
    }
}

竞态条件处理

为了防止多人同时编辑同一个蓝图导致数据覆盖,我们引入了版本校验机制:

  1. 客户端在请求数据时会收到一个 version
  2. 保存时,客户端必须带上这个 expectedVersion
  3. 服务端会对比文件当前的真实版本,如果不一致(说明期间有人保存过),则拒绝保存并提示玩家。

安全性与权限校验 (v0.1.2+)

1. 服务端 OP 权限校验

所有对蓝图执行“修改”的操作(保存、重命名、删除)在服务端均会进行权限检查。

  • 校验逻辑:服务端会通过 ServerPlayer#hasPermissions(2) 检查发送者的权限等级。只有具备 Level 2 或更高权限的玩家(通常为服务器管理员)才能操作蓝图文件。普通玩家只能通过 mgrun 命令(如果配置允许)触发已存在的蓝图,而不能修改它们。

2. 路径穿越防护 (Path Traversal)

服务端在处理任何带有文件名的负载(如 name 字段)时,都会调用 isValidFileName 进行严格校验:

  • 严禁非法字符:文件名只能包含英文字母、数字、下划线、连字符和点。
  • 严禁目录跳转:文件名中严禁出现 ../\
  • 锁定根目录:所有操作被强制锁定在存档目录下的 mgmc_blueprints 文件夹内。

3. 异步 IO 与负载控制

  • 异步保存SaveBlueprintPayload 的文件写入操作在专用的 IO 线程池中异步执行,确保不阻塞 Minecraft 主线程。
  • 格式校验:保存时会对上传的 JSON 数据进行预解析,拒绝损坏的或非蓝图格式的负载。

多人模式与单机模式的区别

  • 单机模式: 逻辑直接操作本地文件路径(Path),不经过网络负载处理逻辑。
  • 多人模式: 客户端被设计为“无状态”的。它不保存任何本地蓝图文件,所有数据必须通过网络负载获取。这种设计确保了数据的一致性,避免了客户端显示过时缓存的问题。

基于 VitePress 构建