[Bukkit插件开发教程] [高质量插件系列] [协议] 教你如何从外部 Ping 通服务器

引子

        总所周知,Minecraft 的服务端和客户端是分离的两部分,客户端与服务端通过 TCP / IP(特指 Java 版,基岩版使用的是 UDP) 进行数据通讯(所以我们需要在服务端配置 server.properties 的 port 属性以及客户端连接时所需输入 IP:PORT)。如果我们知道客户端与服务端所采用的具体通讯协议,那么就可以伪装客户端对服务器发起访问请求从而进行一系列操作(比如压测

知识点

本节所涉及知识点如下:

  • 对于特定协议的解析与封装
  • Socket API
  • BIO

说明

        总所周知,截止到发帖日期,Minecraft Server 的最新版本已经达到了 1.18+,对于这样一个累积多年进行了无数版本迭代的成熟项目,其协议必然也经过了一系列发展变化,所以出于上手难度的考虑,本文将从低到高介绍 MC C / S 通信协议版本,基于 MC Server 的向下兼容,高版本服务器也支持对低版本客户端的解析,故此处我们使用 Sugarcane 1.17.1 这与的高板本服务器完成本章的测试。

开始

BETA 1.8 - 1.3

        在 Minecraft 1.4 以前,如果需要请求服务器返回当前基本信息,则仅需向服务器发送 0xFE 这一个字节即可,服务器会按照以下以下协议返回其当前状态信息:

字段名称 字段类型 注意事项
包ID Byte 返回的包ID应为: 0xFF
字段长度 Short 数据包剩余部分的长度
MOTD 一段以 UTF-16BE 编码的字符串 从这里开始,所有字段都应该在同一个字符串中用 § 分隔。此字符串的最大长度为 64 字节。
在线玩家数 服务器当前游玩的玩家数量.
最大玩家数 服务器能支持的最大玩家数量

基于上述我们可以编写以下程序对数据包进行解析,此处先给出通用工具方法

    /**
     * 获取经校验的合法字符串内容
     * @apiNote 数据包ID需为 0xFF 且长度合法
     * */
    protected static String getSecureString(InputStream inputStream, InputStreamReader inputStreamReader) throws IOException {
        int packetId = inputStream.read();
        if (packetId == -1)
            throw new IOException("Premature end of stream.");
        if (packetId != 0xFF)
            throw new IOException("Invalid packet ID (" + packetId + ").");
        int length = inputStreamReader.read();
        if (length == -1)
            throw new IOException("Premature end of stream.");
        if (length == 0)
            throw new IOException("Invalid string length.");

        char[] chars = new char[length];
        if (inputStreamReader.read(chars, 0, length) != length)
            throw new IOException("Premature end of stream.");
        return new String(chars);
    }

 解析代码

    /**
     * @version BETA - 1.3
     * */
    private void connect() throws IOException {
        try (
                Socket socket = new Socket()
        ) {
            socket.setSoTimeout(TIMEOUT);
            socket.connect(new InetSocketAddress(host, port), TIMEOUT);
            try (
                    OutputStream dataOutputStream = socket.getOutputStream();
                    InputStream inputStream = socket.getInputStream();
                    InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_16BE);
            ) {
                dataOutputStream.write(0xFE);

                String string = getSecureString(inputStream, inputStreamReader);
                String[] args = string.split("§");
                motd = args[0];
                onlinePlayers = Integer.parseInt(args[1]);
                maxPlayers = Integer.parseInt(args[2]);
            }
        }
    }

返回的数据内容(敏感部分已做处理

原始数据(指除包ID与字段长度之外的可视化数据)

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值