You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何读取Minecraft数据包并转换为JSON?自制服务器开发疑问

解决Minecraft服务器二进制数据包解析问题

你看到的乱码是因为Minecraft的网络数据包是二进制序列化格式,不是直接可读的UTF-8字符串,直接调用toString()自然会显示乱码。要解析这些数据并转成JSON,需要严格遵循Minecraft的网络协议规范,核心步骤是解码VarInt、处理数据包分片、解析对应协议段的内容。

核心原理

Minecraft数据包结构:

  • 开头是VarInt类型的数据包总长度
  • 接着是VarInt类型的数据包ID
  • 最后是对应ID的负载数据(可能包含字符串、VarInt、NBT等类型)

无依赖实现步骤

1. 实现VarInt解码函数

VarInt是Minecraft协议的基础整数编码,用1-5字节存储,每个字节的最高位表示是否还有后续字节:

function readVarInt(buffer, offset = 0) {
    let value = 0;
    let position = offset;
    let bytesRead = 0;
    let currentByte;

    do {
        currentByte = buffer.readUInt8(position);
        value |= (currentByte & 0x7F) << (bytesRead * 7);
        bytesRead++;
        position++;
        if (bytesRead > 5) {
            throw new Error("VarInt exceeds maximum length (5 bytes)");
        }
    } while ((currentByte & 0x80) !== 0);

    return { value, bytesRead };
}

2. 缓存分片数据,解析完整数据包

Socket的data事件可能只返回部分数据包,所以需要缓存数据直到收到完整的包:

import { createServer } from "net";

const server = createServer();

server.on("connection", (socket) => {
    let buffer = Buffer.alloc(0);

    socket.on("data", (chunk) => {
        // 拼接新收到的数据到缓存
        buffer = Buffer.concat([buffer, chunk]);

        // 循环解析所有完整的数据包
        while (buffer.length > 0) {
            try {
                // 先读数据包总长度
                const { value: packetLength, bytesRead: lengthBytes } = readVarInt(buffer);
                // 检查缓存是否包含完整数据包
                if (buffer.length < lengthBytes + packetLength) {
                    break; // 数据不够,等待下一次data事件
                }

                // 截取数据包内容(跳过长度前缀)
                const packetBuffer = buffer.slice(lengthBytes, lengthBytes + packetLength);
                // 更新缓存,移除已解析的数据包
                buffer = buffer.slice(lengthBytes + packetLength);

                // 解析数据包ID
                const { value: packetId, bytesRead: idBytes } = readVarInt(packetBuffer);
                const payload = packetBuffer.slice(idBytes);

                // 根据数据包ID处理对应逻辑
                handlePacket(packetId, payload);
            } catch (err) {
                console.error("解析数据包出错:", err);
                buffer = Buffer.alloc(0); // 出错时清空缓存,避免死循环
                break;
            }
        }
    });
});

server.listen(25565, () => {
    console.log("服务器监听在25565端口");
});

3. 处理不同类型的数据包,转成JSON

以最常见的Handshake(0x00)和Login Start(0x00)数据包为例:

// 维护连接状态(握手/登录/状态/游戏),示例初始为握手状态
let connectionState = "handshake";

function handlePacket(packetId, payload) {
    switch (connectionState) {
        case "handshake":
            if (packetId === 0x00) {
                const { value: protocolVersion, bytesRead: protoBytes } = readVarInt(payload);
                const { string: serverAddress, bytesRead: addrBytes } = readString(payload.slice(protoBytes));
                const port = payload.readUInt16BE(protoBytes + addrBytes);
                const { value: nextState, bytesRead: stateBytes } = readVarInt(payload.slice(protoBytes + addrBytes + 2));

                // 更新连接状态
                connectionState = nextState === 1 ? "status" : "login";

                console.log("Handshake数据包转JSON:", JSON.stringify({
                    protocolVersion,
                    serverAddress,
                    port,
                    nextState
                }, null, 2));
            }
            break;
        case "login":
            if (packetId === 0x00) {
                const { string: playerName } = readString(payload);
                console.log("Login Start数据包转JSON:", JSON.stringify({
                    playerName
                }, null, 2));
            }
            break;
        default:
            console.log(`未处理的连接状态: ${connectionState},数据包ID: 0x${packetId.toString(16)}`);
    }
}

// 读取Minecraft协议中的字符串(前缀是VarInt表示长度,后面是UTF-8字节)
function readString(buffer, offset = 0) {
    const { value: length, bytesRead: lengthBytes } = readVarInt(buffer, offset);
    const string = buffer.toString('utf8', offset + lengthBytes, offset + lengthBytes + length);
    return { string, bytesRead: lengthBytes + length };
}

关键说明

  • Minecraft的协议状态分为握手、登录、状态、游戏四个阶段,相同的数据包ID在不同阶段对应不同的payload结构,必须维护连接状态才能正确解析。
  • 如果需要解析NBT格式的数据(比如游戏内的物品、区块数据),可以基于原生Buffer实现NBT解码器,无需第三方依赖。

内容的提问来源于stack exchange,提问作者sujang958

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 11:50:32