06-networking · git:20260819.ca99162 · 2026-08-19 · sha256 af24c62c833dd702

06-networking git:20260819.ca99162A

Immutable. This exact content is served forever at /api/v1/blob/af24c62c833dd702.

# 06 — 网络通信

> 适用版本:Fabric 1.14.4

---

## 约束

### 核心原则

- 包 ID 用 Yarn `net.minecraft.util.Identifier`
- 本档默认教程是 **networking v0**:`net.fabricmc.fabric.api.network.ClientSidePacketRegistry` / `ServerSidePacketRegistry`([FAPI javadoc](https://maven.fabricmc.net/docs/fabric-api-0.74.1+1.19.4/net/fabricmc/fabric/api/network/ServerSidePacketRegistry.html) 仍写明职责;1.14 同期就是这套)
- **C2S 接收**在服务端:`ServerSidePacketRegistry.INSTANCE.register`
- **S2C 接收**在客户端:`ClientSidePacketRegistry.INSTANCE.register`(`ClientModInitializer`)
- **C2S 发送**:`ClientSidePacketRegistry.INSTANCE.sendToServer(id, buf)`
- **S2C 发送**:`ServerSidePacketRegistry.INSTANCE.sendToPlayer(player, id, buf)`
- 回调在网络线程:用 `packetContext.getTaskQueue().execute(...)`(loader-api:`PacketContext#getTaskQueue`,不是 `queue()`)
- 不要编造 `PacketByteBuf.createUnpooled()`(Yarn `PacketByteBuf` 无此静态方法)。写 `new PacketByteBuf(Unpooled.buffer())`
- 不要抄 1.16+ 当默认:`ServerPlayNetworking` / `PayloadTypeRegistry`。loader-api 摘要里若出现 `networking.v1`,只在工程 **确实依赖带 v1 的 FAPI jar** 时再用
- 依赖写完整 `fabric-api`,不要钉死 `fabric-networking-v0:0.1.3+...`,也不要编造 `fabric-entity-dragon-v0`
- `fabric.mod.json` **没有** `network` 键;不需要为发包专门加 mixin

---

## Decision Flow

### Decision: 选择网络通信方式

```
IF 服务端向客户端推送(S2C)
  → 客户端 ClientSidePacketRegistry.INSTANCE.register
  → 服务端 ServerSidePacketRegistry.INSTANCE.sendToPlayer

IF 客户端向服务端请求(C2S)
  → 服务端 ServerSidePacketRegistry.INSTANCE.register(ModInitializer)
  → 客户端 ClientSidePacketRegistry.INSTANCE.sendToServer

IF 需要双向
  → 两端分别 register;发送用上面两套 send*

IF 工程 FAPI 已是 networking-api-v1
  → 才改用 ServerPlayNetworking / ClientPlayNetworking;不要和 v0 混用
```

---

## 基础网络注册

```java
import io.netty.buffer.Unpooled;
import net.minecraft.util.PacketByteBuf;
import net.fabricmc.fabric.api.network.ClientSidePacketRegistry;
import net.fabricmc.fabric.api.network.ServerSidePacketRegistry;

public class MyNetworking {
    public static final Identifier MY_PACKET_ID = new Identifier(MOD_ID, "my_packet");

    // C2S:服务端接收(主入口)
    public static void registerServerReceivers() {
        ServerSidePacketRegistry.INSTANCE.register(MY_PACKET_ID, (packetContext, buf) -> {
            int value = buf.readInt();
            packetContext.getTaskQueue().execute(() -> {
                ServerPlayerEntity player = (ServerPlayerEntity) packetContext.getPlayer();
                // 主线程处理
            });
        });
    }

    // S2C:客户端接收
    public static void registerClientReceivers() {
        ClientSidePacketRegistry.INSTANCE.register(MY_PACKET_ID, (packetContext, buf) -> {
            int value = buf.readInt();
            packetContext.getTaskQueue().execute(() -> {
                // 客户端主线程
            });
        });
    }
}
```

`PacketRegistry#register(Identifier, PacketConsumer)` 与 `PacketConsumer#accept(PacketContext, PacketByteBuf)` 已核 loader-api。

## 发送数据包

```java
public void sendToClient(ServerPlayerEntity player, int value) {
    PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer());
    buf.writeInt(value);
    ServerSidePacketRegistry.INSTANCE.sendToPlayer(player, MY_PACKET_ID, buf);
}

public void sendToServer(int value) {
    PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer());
    buf.writeInt(value);
    ClientSidePacketRegistry.INSTANCE.sendToServer(MY_PACKET_ID, buf);
}
```

## 客户端入口点(网络初始化)

```java
public class ExampleModClient implements ClientModInitializer {
    @Override
    public void onInitializeClient() {
        MyNetworking.registerClientReceivers();

        KeyBindingHelper.registerKeyBinding(new KeyBinding(
            "key.examplemod.open_gui",
            InputUtil.Type.KEYSYM,
            80, // GLFW_KEY_P;Yarn KeyBinding(String, Type, int, String)
            "category.examplemod"
        ));
    }
}
```

Yarn 1.14.4 `KeyBinding` 四参最后一参是 **int code**,不要把 `InputUtil.Type.KEYSYM.createFromCode(80)`(返回 `KeyCode`)塞进第三参。

## 常见错误

- ❌ 把 C2S `register` 写在 `ClientSidePacketRegistry` — 那是 **S2C 接收**
- ❌ 把 S2C `register` 写在 `ServerSidePacketRegistry` — 那是 **C2S 接收**
- ❌ `packetContext.queue()` — 本档是 `getTaskQueue().execute`
- ❌ `PacketByteBuf.createUnpooled()` / `ServerPlayNetworking.send` 当 v0 教程
- ❌ 在网络线程改世界
- ❌ 在 `onInitialize()` 注册 S2C 接收器
- ❌ 包 ID 两端不一致

## 扩展点

| 配合 Skill | 协作说明 |
|------------|---------|
| `mc-gui` | 网络用于 GUI 数据同步 |
| `mc-item` | 物品使用触发网络包 |
| `mc-entity` | 实体状态通过网络同步 |