[高质量插件系列] [协议] 教你如何从外部 Ping 通服务器 - 编程开发 - Minecraft(我的世界)中文论坛 -.html

[高质量插件系列] [协议] 教你如何从外部 Ping 通服务器 - 编程开发 - Minecraft(我的世界)中文论坛 -

Minecraft(我的世界)中文论坛

 找回密码
 注册(register)

!header_login!

只需一步,立刻登录

查看: 2233|回复: 16
打印 上一主题 下一主题

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

[复制链接]
IllTamer 当前离线
积分
2188
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2014-7-17
查看详细资料

来自:黑龙江

跳转到指定楼层
楼主
发表于 2021-12-1 21:52:46 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式

您尚未登录,立即登录享受更好的浏览体验!

您需要 登录 才可以下载或查看,没有帐号?注册(register)

x
本帖最后由 IllTamer 于 2022-3-22 11:51 编辑

引子        众所周知,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 这一个字节即可,服务器会按照以下以下协议返回其当前状态信息:
字段名称字段类型注意事项
包IDByte返回的包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与字段长度之外的可视化数据)

解析后

        细心的小伙伴可以看到诸如motd、在线玩家数之类的数据都已经获取到了,但是还有部分例如 serverVersion 的数据未被捕获。不要着急,这些是高版本协议中所增加的元素内容。
1.6
        有小伙伴会问:为什么为什么先讲 1.6 ?1.4 和 1.5 去哪了?原因很简单,因为 1.4、1.5 的协议是 1.6 的简化版,1.6 的 Notchian 服务器为了兼容先前的版本,都只接受老版本的协议
客户端到服务端
        对于 1.4+ 的 MC 的客户端与服务端 TCP 连接。它不是执行身份验证和登录(如协议协议加密中所述),而是发送以下格式的数据包:
  • FE— 服务器列表 ping 的数据包标识符
  • 01— 服务器列表 ping 的有效负载(始终为 1)
  • FA— 插件消息的数据包标识符
  • 00 0B— 以下字符串的长度,以字符为单位,作为短字符串(始终为 11)
  • 00 4D 00 43 00 7C 00 50 00 69 00 6E 00 67 00 48 00 6F 00 73 00 74— 编码为UTF-16BE字符串的字符串MC|PingHost
  • XX XX— 其余数据的长度,作为短。计算为 ,其中 是 UTF-16BE 编码主机名中的字节数。7 + len(hostname)len(hostname)
  • XX—协议版本,例如 最后一个版本 (74)4a
  • XX XX— 以下字符串的长度,以字符为单位,作为短字符串
  • ...— 客户端连接到的主机名,编码为UTF-16BE字符串
  • XX XX XX XX— 客户端正在连接到的端口,作为整数。
:所有数据类型都是 big-endian 的,而为了向下兼容,所有 Notchian 服务器只关心前 3 个字节(且您只能发送这 3 个字节),而 Bukkit 服务器仅关心前两个字节。读取后,响应将发送到客户端,所有旧版服务器 (<=1.6) 将相应地响应。FE 01 FA
数据包示例:
0000000: fe01 fa00 0b00 4d00 4300 7c00 5000 6900  ......M.C.|.P.i.
0000010: 6e00 6700 4800 6f00 7300 7400 1949 0009  n.g.H.o.s.t..I..
0000020: 006c 006f 0063 0061 006c 0068 006f 0073  .l.o.c.a.l.h.o.s
0000030: 0074 0000 63dd                           .t..c.

服务端到客户端
        在最初的三个字节之后,形如编码为 UTF-16BE 字符串的数据包以 ASCII 0167 的字符为起始标志,每个元素间使用  \0 作为分隔符,具体解析如下:
字段名称字段类型注意事项
包IDByte返回的包ID应为: 0xFF
字段长度Short数据包剩余部分的长度
                        协议版本
                        
一段以 UTF-16BE 编码的字符串例如 74
服务器版本如 1.8.7
MOTD从这里开始,所有字段都应该在同一个字符串中用 § 分隔。此字符串的最大长度为 64 字节。
在线玩家数服务器当前游玩的玩家数量.
最大玩家数服务器能支持的最大玩家数量
解析代码
private void connect() throws IOException {
    try (
            Socket socket = new Socket()
    ) {
        socket.setSoTimeout(TIMEOUT);
        socket.connect(new InetSocketAddress(host, port), TIMEOUT);
        try (
                DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
                InputStream inputStream = socket.getInputStream();
                InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_16BE);
        ) {
            dataOutputStream.write(new byte[]{(byte) 0xFE, (byte) 0x01/*, (byte) 0xFA*/});

            String string = ServerInfoV1_3.getSecureString(inputStream, inputStreamReader);

            if (string.startsWith("§")) {
                String[] data = string.split("\0");
                pingVersion = Integer.parseInt(data[0].substring(1));
                protocolVersion = Integer.parseInt(data[1]);
                serverVersion = data[2];
                motd = data[3];
                onlinePlayers = Integer.parseInt(data[4]);
                maxPlayers = Integer.parseInt(data[5]);
            } else {
                String[] data = string.split("§");
                motd = data[0];
                onlinePlayers = Integer.parseInt(data[1]);
                maxPlayers = Integer.parseInt(data[2]);
            }
        }
    }
}返回的数据内容
原始数据(指除包ID与字段长度之外的可视化数据)

解析后

1.4 - 1.5
        在 Minecraft 1.6 之前,客户端到服务器的操作要简单得多,只发送两字节的起始标识即可:FE 01
当前
        1.6 + 以后,客户端与服务端的连接方式发生改变。
握手
首先,客户端发送状态设置为 1 的握手数据包。
数据包标识
字段名称
字段类型
笔记
0x00协议版本VarInt请参阅协议版本号。客户端计划用于连接到服务器的版本(对于 ping 并不重要)。如果客户端正在 ping 以确定要使用的版本,则应按照惯例进行设置。-1
服务器地址字符串用于连接的主机名或 IP,例如 localhost 或 127.0.0.1。Notchian服务器不使用此信息。请注意,SRV 记录是完全重定向,例如,如果_minecraft._tcp.example.com指向 mc.example.org,则连接到 example.com 的用户除了连接到它之外,还将提供 mc.example.org 作为服务器地址。
服务器端口无符号短默认值为 25565。Notchian服务器不使用此信息。
下一个状态VarInt状态应为 1,但登录时也可以为 2。
请求
        客户端跟进请求数据包。此数据包没有字段。
数据包标识
字段名称
字段类型
笔记
0x00无字段
响应
        服务器应使用响应数据包进行响应。请注意,Notchian 服务器将由于未知原因等待接收以下 Ping数据包30秒,然后超时并发送响应。
数据包标识
字段名称
字段类型
笔记
0x00JSON 响应字符串见下文;与所有字符串一样,这以 VarInt 的长度为前缀
JSON 响应字段是一个JSON 对象,其格式如下:
{    "version": {        "name": "1.8.7",        "protocol": 47    },    "players": {        "max": 100,        "online": 5,        "sample": [            {                "name": "thinkofdeath",                "id": "4566e69f-c907-48ee-8d71-d7ba5aa00d20"            }        ]    },    "description": {        "text": "Hello world"    },    "favicon": "data:image/png;base64,<data>"}
对于此版本的协议传输,我们需要使用 Minecraft 指定的 varInt 函数将 int 转换为 varInt 类型从而构造正确的握手数据包,转换代码如下:
/**
* varInt 读取函数
* @apiNote https://wiki.vg/index.php?title=Protocol&oldid=16681
*/
protected static int readVarInt(DataInputStream in) throws IOException {
    int i = 0;
    int j = 0;
    while (true) {
        int k = in.readByte();
        i |= (k & 0x7F) << (j++ * 7);
        if (j > 5)
            throw new RuntimeException("VarInt too big");
        if ((k & 0x80) != 0x80)
            break;
    }
    return i;
}
/**
* varInt 写入函数
* */
protected static void writeVarInt(DataOutputStream out, int paramInt) throws IOException {
    while (true) {
        if ((paramInt & ~0x7F) == 0) {
            out.writeByte(paramInt);
            return;
        }
        out.writeByte(paramInt & 0x7F | 0x80);
        paramInt >>>= 7;
    }
}
基于上述,可以给出以下解析代码,其中解析 json 部分不再赘述:
/**
* 发送数据包格式为:数据包长度 + 内容
* */
private void connect() throws IOException {
    try (Socket socket = new Socket()) {
        socket.setSoTimeout(9000);
        socket.connect(new InetSocketAddress(host, port), 9000);
        try (
                DataOutputStream out = new DataOutputStream(socket.getOutputStream());
                DataInputStream in = new DataInputStream(socket.getInputStream());
                //> Handshake
                ByteArrayOutputStream handshake_bytes = new ByteArrayOutputStream();
                DataOutputStream handshake = new DataOutputStream(handshake_bytes);
        ) {
            handshake.writeByte(PACKET_HANDSHAKE);
            writeVarInt(handshake, packageProtocolVersion);
            writeVarInt(handshake, host.length());
            handshake.writeBytes(host);
            handshake.writeShort(port);
            writeVarInt(handshake, PACKET_STATUS_HANDSHAKE);

            //< Status Handshake
            writeVarInt(out, handshake_bytes.size()); // Size of packet
            out.write(handshake_bytes.toByteArray());

            //< Status Request
            out.writeByte(0x01); // Size of packet
            out.writeByte(PACKET_STATUS_REQUEST);

            //< Status Response
            // https://wiki.vg/Protocol#Response
            readVarInt(in); // Size
            pingVersion = readVarInt(in);
            int length = readVarInt(in);
            byte[] data = new byte[length];
            in.readFully(data);
            String json = new String(data, StandardCharsets.UTF_8);

            JsonObject jsonObject = new Gson().fromJson(json, JsonObject.class);

            parseJson(jsonObject);
        }
    }
}解析后数据(原始数据为 json 形式存在大量键值对,过于混乱不再给出)

最后
        Minecraft 系列会持续更新,后期会关注生物 AI、自制类库等方面的内容。感兴趣的小伙伴不妨关注一波~







评分

参与人数 7人气 +8金粒 +33宝石 +9收起 理由
huiyi10+ 8Ssssssssssssssssssss
可可的验证+ 1Ssssssssssssssssssss
炼药锅+ 1MCBBS有你更精彩~
土球球+ 3+ 25+ 9高亮奖励
nihoooo+ 1MCBBS有你更精彩~
白灯+ 1MCBBS有你更精彩~
。—。+ 1MCBBS有你更精彩~

查看全部评分

帖子永久链接: 

Minecraft中文论坛 - 论坛版权1、本主题所有言论和图片纯属会员个人意见,与本论坛立场无关
2、本站所有主题由该帖子作者发表,该帖子作者享有帖子相关版权
3、其他单位或个人使用、转载或引用本文时必须同时征得该帖子作者的同意
4、帖子作者须承担一切因本文发表而直接或间接导致的民事或刑事法律责任
5、本帖若有内容转载自其它媒体,不代表本站赞同其观点和对其真实性负责
6、若本帖涉及任何版权问题,请立即告知本站,本站将及时予以删除并致以最深的歉意
7、Minecraft(我的世界)中文论坛管理员和版主有权不事先通知发贴者而删除本文

Panzako 当前离线
积分
224
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2021-9-20
查看详细资料

来自:吉林

沙发
发表于 2021-12-1 22:23:10 | 只看该作者
高质量,可以看出楼主对MC的网络工作机理相当了解了
点赞!
不过对于实际应用,更推荐使用MCProtocol库,可以让开发事半功倍喔

评分

参与人数 1人气 +1收起 理由
IllTamer+ 1感谢支持~ 理论同样重要哦

查看全部评分

回复

使用道具 举报

635419450 当前离线
积分
5624
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2013-5-29
查看详细资料

来自:广东

板凳
发表于 2021-12-2 01:53:10 | 只看该作者
挺好,学到了,有空尝试一下
回复

使用道具 举报

炼药锅 当前离线
积分
2452
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2015-3-22
查看详细资料

来自:广西

地板
发表于 2022-2-8 23:59:03 | 只看该作者
文中提到的"Notchian"是什么意思
回复

使用道具 举报

gsdengdeng 当前离线
积分
53
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2022-2-6
查看详细资料

来自:中国

5#
发表于 2022-2-16 18:36:06 | 只看该作者
感谢分享,收藏了
回复

使用道具 举报

耗子 当前离线
积分
14122
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2012-2-18
查看详细资料

来自:陕西

6#
发表于 2022-3-18 14:40:13 | 只看该作者
分类不对吧,这应该是其它开发教程

评分

参与人数 1人气 +1收起 理由
IllTamer+ 1已更正

查看全部评分

回复

使用道具 举报

老章 当前离线
积分
108
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2020-4-7
查看详细资料

来自:山东

7#
发表于 2022-3-28 15:46:22 | 只看该作者
mcbbs有你更精彩
回复

使用道具 举报

可可的验证 当前离线
积分
156
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2019-11-16
查看详细资料

来自:贵州

8#
发表于 2022-3-29 14:41:24 | 只看该作者
我想问问基岩版怎么去通信,使用udp吗?我用抓包软件抓了一下,看不懂
回复

使用道具 举报

海绵c2 当前离线
积分
685
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2019-10-1
查看详细资料

来自:上海

9#
发表于 2022-3-30 02:29:22 | 只看该作者
谢谢分享,楼主可以再出一期rcon的教程吗
回复

使用道具 举报

鱼跃y 当前离线
积分
241
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2017-6-1
查看详细资料

来自:江西

10#
发表于 2022-3-30 08:17:27 | 只看该作者
第一次发现还有这么NB的版块 i了
回复

使用道具 举报

积分
62
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2021-1-14
查看详细资料

来自:河北

11#
发表于 2022-3-31 21:23:26 | 只看该作者
牛皮啊666
回复

使用道具 举报

asdafs 当前离线
积分
176
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2019-2-18
查看详细资料
头像被屏蔽

来自:辽宁

12#
发表于 2022-5-14 07:49:04 | 只看该作者
mcbbs有你更精彩~
回复

使用道具 举报

Virlhyc 当前离线
积分
1261
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2018-6-25
查看详细资料

来自:浙江

13#
发表于 2022-5-15 08:37:24 | 只看该作者
wuw!蟹蟹!
回复

使用道具 举报

魏介 当前离线
积分
39
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2021-6-15
查看详细资料

来自:上海

14#
发表于 2022-6-7 20:39:52 | 只看该作者
厉害6666666666
回复

使用道具 举报

BAQ 当前离线
积分
168
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2018-9-7
查看详细资料

来自:广东

15#
发表于 2022-6-19 14:55:55 | 只看该作者
感谢分享
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册(register)

本版积分规则