如何通过FabricMC模组在Minecraft客户端发送数据包并在多人模式的Bukkit服务器接收?
Hey there! I’ve worked on cross-compatibility between Fabric mods and Bukkit plugins before, so let’s break down exactly how to send a custom text packet from your Fabric client and catch it on a Bukkit server. This requires custom packet handling on both sides, since we’re bridging two different modding ecosystems.
We’ll create a custom client-to-server (C2S) packet in your Fabric mod, define its structure (in this case, a text string), then write a Bukkit plugin that listens for this specific packet using Netty (the underlying network library Minecraft uses). The key is making sure both sides agree on the packet ID and data format to avoid misinterpretation.
First, we’ll build the packet class and add logic to send it (e.g., when a player presses a key or right-clicks an item).
Step 2.1: Create the Custom Packet Class
This class defines how the packet is written (client-side) and read (server-side):
import net.minecraft.network.Packet; import net.minecraft.network.PacketByteBuf; import net.minecraft.network.listener.ServerPlayPacketListener; public class CustomTextC2SPacket implements Packet<ServerPlayPacketListener> { private final String message; // Initialize with the text we want to send public CustomTextC2SPacket(String message) { this.message = message; } // Constructor for reading the packet from a byte buffer (used by the server) public CustomTextC2SPacket(PacketByteBuf buf) { this.message = buf.readString(32767); // Match Minecraft's max string length } // Write the text to the byte buffer (client sends this) @Override public void write(PacketByteBuf buf) { buf.writeString(message); } // Server-side handler (we'll handle this in Bukkit, so leave empty for now) @Override public void apply(ServerPlayPacketListener listener) {} }
Step 2.2: Add Logic to Send the Packet
Trigger this code when you want to send the packet (e.g., in a key bind or item interaction):
import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.ClientPlayNetworkHandler; // Get the client's network handler (make sure the player is connected!) ClientPlayNetworkHandler networkHandler = MinecraftClient.getInstance().getNetworkHandler(); if (networkHandler != null) { // Create and send the packet with your text CustomTextC2SPacket packet = new CustomTextC2SPacket("Hello from my Fabric mod!"); networkHandler.sendPacket(packet); }
Important: Assign a unique packet ID for your custom packet. For this example, we’ll use 0x42—double-check that this ID isn’t used by vanilla Minecraft for C2S packets (look up your Minecraft version’s packet list to avoid conflicts).
Bukkit uses Netty under the hood, so we’ll create a channel interceptor to listen for incoming packets and parse our custom one.
Step 3.1: Plugin Main Class
Register the Netty interceptor when your plugin enables:
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.craftbukkit.v1_20_R3.CraftServer; import net.minecraft.server.network.ServerConnection; import io.netty.channel.Channel; public class PacketReceiverPlugin extends JavaPlugin { @Override public void onEnable() { registerPacketInterceptor(); getLogger().info("Custom packet receiver is active!"); } private void registerPacketInterceptor() { ServerConnection serverConnection = ((CraftServer) getServer()).getServerConnection(); for (var networkManager : serverConnection.getNetworkManagers()) { Channel channel = networkManager.channel; // Add our handler after the decoder to process parsed packets if (channel.pipeline().get("custom-packet-handler") == null) { channel.pipeline().addAfter("decoder", "custom-packet-handler", new CustomPacketHandler(this)); } } } }
Step 3.2: Netty Handler to Catch the Packet
This class filters incoming packets and processes our custom one:
import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelHandlerContext; import net.minecraft.network.PacketDataSerializer; import org.bukkit.Bukkit; public class CustomPacketHandler extends ChannelDuplexHandler { private final PacketReceiverPlugin plugin; private static final int CUSTOM_PACKET_ID = 0x42; // Match the ID you used in Fabric public CustomPacketHandler(PacketReceiverPlugin plugin) { this.plugin = plugin; } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof PacketDataSerializer serializer) { serializer.markReaderIndex(); // Save the current read position try { int packetId = serializer.readVarInt(); // Read the packet ID (vanilla format) if (packetId == CUSTOM_PACKET_ID) { // Read the text from the packet String message = serializer.readString(32767); plugin.getLogger().info("Received custom packet: " + message); // Run Bukkit API actions on the main thread (critical for thread safety!) Bukkit.getScheduler().runTask(plugin, () -> { Bukkit.broadcastMessage("[Custom Packet] " + message); }); // Don't pass the packet to vanilla (it doesn't recognize our ID) return; } } catch (Exception e) { serializer.resetReaderIndex(); // Reset if reading fails } } // Pass non-custom packets to vanilla processing super.channelRead(ctx, msg); } }
- Packet ID Uniqueness: Always verify your chosen packet ID isn’t used by vanilla Minecraft for your target version. Using a conflicting ID will break vanilla functionality.
- Data Format Consistency: The order you write data in the Fabric packet must exactly match the order you read it in the Bukkit handler (e.g., write a string first, read a string first).
- Thread Safety: Never call Bukkit API methods directly from the Netty IO thread—use
Bukkit.getScheduler().runTask()to switch to the main server thread. - Connection Check: In the Fabric client, always check if
networkHandleris non-null before sending the packet (this ensures the player is connected to a server).
- Package your Fabric mod and install it in your client’s
modsfolder. - Build your Bukkit plugin and drop it in the server’s
pluginsfolder. - Launch the client, connect to your server, and trigger the packet send event.
- Check the server console—you should see your custom message, and it will be broadcast to all players!
内容的提问来源于stack exchange,提问作者Sudo-su-Bash




