本帖最后由 九仞 于 2020-4-1 10:21 编辑
※兼容性提示:这篇教程前期默认是你的电脑存储变量时,高位在前,低位在后
※什么意思呢?比如说short类型的1在计算机中的16进制表示是00 01
※在高位优先的电脑里,它会存储为00 01
※而在低位优先的电脑里,它会存储为01 00
※如果只按一种方式难免会遇到一部分电脑运行结果十分奇怪的问题
※后期会讲解怎么实现程序的兼容性优化
高中党近期学习压力比较大,作业特多的那种
因此教程出的比较慢,希望大家谅解
源代码部分已经基本完善了,就差在论坛上出教程了
我会尽快把代码整理发布到Github上,
以便于那些想要尽快得到代码的人直接获取源代码
※第一次写教程,有点小紧张※这篇教程是针对有一定C++基础的小伙伴滴※如果你没有基础,那也没关系,※我会抽时间再写一篇零基础的教程哒※最后感谢您打开这篇教程,如有疑问可以留言※兼容性提示:这篇教程前期默认是你的电脑存储变量时,高位在前,低位在后
※什么意思呢?比如说short类型的1在计算机中的16进制表示是00 01
※在高位优先的电脑里,它会存储为00 01
※而在低位优先的电脑里,它会存储为01 00
※如果只按一种方式难免会遇到一部分电脑运行结果十分奇怪的问题
※后期会讲解怎么实现程序的兼容性优化
首先要鸣谢一篇wiki:https://wiki.vg/Server_List_Ping#Client_to_server
这篇百科详细的阐述了MC获取motd和ping包的工作原理
我也是照着这篇文章写下了基础的代码
特邀嘉宾:@缘生
MC获取服务器状态的原理--客户端部分--原文
这里重点解释一下包的结构下面会使用原文的标号1-5 可以理解为客户端的请求包 头,是固定的不需要做任何的更改6-10 则可以理解为包体,里面包含了客户端要获取的服务器的连接数据,主要是地址数据(ip + 端口)可以认为整个包都是字节类型的,并且字符的编码使用utf-16BE,这里要注意的是每个字节都用两位16进制表示然后详细剖析一下6-10行包的数据到底包含了哪些内容
※※注释:这里的字符长度指的就是一个字母一个单位长度,例如localhost长度就是9
--服务端部分--未完待续...OK,解释完数据包结构,就去解决C++里的编码问题吧!
C++中使用UTF-16BE编码(UCS2-Big Endian)首先说一下我的开发环境我的系统是WIN10 x64为了验证代码的可移植性,我使用visual studio 和 vscode + Mingw-w64进行开发C++默认使用的编码是ANSI码,数据类型就是char类型,占一个字节由此我们需要为C++引入unicode编码(这里如果讲错了,可以说一下)具体可以查看我写的小程序的输出,它输出的是默认输出下的中文编码和UTF-16下的中文编码vs版:
mingw版:
小工具代码我直接贴在这里啦
既然说完了C++的默认编码,接下来就是编码的转换问题了从这个小工具的源码中不难看出,我们已经实现了部分转换功能
我们的转换目标是将这些utf-16字符串转换成一个字节数组,至于为什么要这么转换,我会在后面进行讲解
小工具里已经使用截取char16_t类型的高低位来获取指定的字节,那如果我们将截取下来的字节保存到一个字节数组里不就可以实现这个转换目标了嘛,下面开始着手我们的工作吧
还记得上一篇教程中的MC发包原理嘛,包的前两字节是数据长度,那我们就在写转换函数的同时,写一个获取U16字符串长度的函数,方便我们之后使用。接下来我会先直接给出源代码,再挑一些重点进行进一步的解释
源代码:
※代码太长了,一部分没显示出来,论坛好像限制了代码的长度
※不过核心部分的代码已经包含在里面了
这段代码的精髓部分应该是取char16_t的高低位的部分
bHi = (chr & 0xff00) >> 8;
这句话用自然语言表述就是先将chr变量的低位置0,然后右移8位,赋值给bHi,很晦涩,我们举例子便于理解
上文说到过char16_t是用两个字节存储字符的,我们举“中”这个字
他的UTF16BE编码表示为[size=1.2em]4E2D,两个字节。
由于我们要将它转换为byte(字节)类型并存储到byte数组中,
也就是说我们实际只要4E(高位部分)就行了,如何做呢,是用C++的位运算足矣
我们先使用and运算符将chr的低位置0,使4E2D变为4E00,再将chr右移八位(一字节),变成4E,然后我们就得到了高位部分
bLo = chr & 0x00ff;
同理,我们可以直接使用and运算符,将高位部分置0,得到002D,然后直接赋给bLo变量
也许你会疑问,16位变量为何能够直接赋值给8位变量,
我百度得到的结果是16位变量赋值给8位变量时16位变量的高位会被抛弃,只保留低位部分
也就是说这个002D的赋值操作只会保留2D部分,00直接被抛弃了
那为什么还要使用and运算符呢。。
嘿嘿,只是为了让编译器不报警告
如果不使用and,直接复制,编译器会提示 “=”: 从“char16_t”转换到“byte”,可能丢失数据
其余代码都有注释,大家如果看注释还不懂的话,可以联系我,我会对此进行进一步的解释
未完待续...
由于要向服务器发送数据,因此大家如果有什么推荐的MC服务器可以向我推荐一下
怎么找到我?(Contact)
高中党近期学习压力比较大,作业特多的那种
因此教程出的比较慢,希望大家谅解
源代码部分已经基本完善了,就差在论坛上出教程了
我会尽快把代码整理发布到Github上,
以便于那些想要尽快得到代码的人直接获取源代码
※第一次写教程,有点小紧张
※这篇教程是针对有一定C++基础的小伙伴滴
※如果你没有基础,那也没关系,
※我会抽时间再写一篇零基础的教程哒
※最后感谢您打开这篇教程,如有疑问可以留言
※什么意思呢?比如说short类型的1在计算机中的16进制表示是00 01
※在高位优先的电脑里,它会存储为00 01
※而在低位优先的电脑里,它会存储为01 00
※如果只按一种方式难免会遇到一部分电脑运行结果十分奇怪的问题
※后期会讲解怎么实现程序的兼容性优化
这篇百科详细的阐述了MC获取motd和ping包的工作原理
我也是照着这篇文章写下了基础的代码
特邀嘉宾:@缘生
2021.12 数据,可能有更多内容
高中党近期学习压力比较大,作业特多的那种
因此教程出的比较慢,希望大家谅解
源代码部分已经基本完善了,就差在论坛上出教程了
我会尽快把代码整理发布到Github上,
以便于那些想要尽快得到代码的人直接获取源代码
※第一次写教程,有点小紧张※这篇教程是针对有一定C++基础的小伙伴滴※如果你没有基础,那也没关系,※我会抽时间再写一篇零基础的教程哒※最后感谢您打开这篇教程,如有疑问可以留言※兼容性提示:这篇教程前期默认是你的电脑存储变量时,高位在前,低位在后
※什么意思呢?比如说short类型的1在计算机中的16进制表示是00 01
※在高位优先的电脑里,它会存储为00 01
※而在低位优先的电脑里,它会存储为01 00
※如果只按一种方式难免会遇到一部分电脑运行结果十分奇怪的问题
※后期会讲解怎么实现程序的兼容性优化
首先要鸣谢一篇wiki:https://wiki.vg/Server_List_Ping#Client_to_server
这篇百科详细的阐述了MC获取motd和ping包的工作原理
我也是照着这篇文章写下了基础的代码
特邀嘉宾:@缘生
MC获取服务器状态的原理--客户端部分--原文
Client to server The client initiates a TCP connection to the server on the standard port. Instead of doing auth and logging in (as detailed in Protocol and Protocol Encryption), it sends the following data, expressed in hexadecimal:
- FE — packet identifier for a server list ping
- 01 — server list ping's payload (always 1)
- FA — packet identifier for a plugin message
- 00 0B — length of following string, in characters, as a short (always 11)
- 00 4D 00 43 00 7C 00 50 00 69 00 6E 00 67 00 48 00 6F 00 73 00 74 — the string MC|PingHost encoded as a UTF-16BE string
- XX XX — length of the rest of the data, as a short. Compute as 7 + len(hostname), where len(hostname) is the number of bytes in the UTF-16BE encoded hostname.
- XX — protocol version, e.g. 4a for the last version (74)
- XX XX — length of following string, in characters, as a short
- ... — hostname the client is connecting to, encoded as a UTF-16BE string
- XX XX XX XX — port the client is connecting to, as an int.
代码:
- 0000000: fe01 fa00 0b00 4d00 4300 7c00 5000 6900......M.C.|.P.i.
- 0000010: 6e00 6700 4800 6f00 7300 7400 1949 0009n.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.
这里重点解释一下包的结构下面会使用原文的标号1-5 可以理解为客户端的请求包 头,是固定的不需要做任何的更改6-10 则可以理解为包体,里面包含了客户端要获取的服务器的连接数据,主要是地址数据(ip + 端口)可以认为整个包都是字节类型的,并且字符的编码使用utf-16BE,这里要注意的是每个字节都用两位16进制表示然后详细剖析一下6-10行包的数据到底包含了哪些内容
※※注释:这里的字符长度指的就是一个字母一个单位长度,例如localhost长度就是9
- 6是剩余数据包的长度,什么意思呢?刚才说了6-10是客户端发送给服务器的实际数据(包体),MC的服务器在接受数据包时,会先接收数据包的长度,再接受数据包的内容。这个长度有一个简单的计算公式 7 + 服务器域名或ip的字符长度x2
- 7是客户端版本号,每一个版本所对应的版本号可以参考https://wiki.vg/Protocol_version_numbers#Versions_before_the_Netty_rewrite
- 8是服务器IP或域名的字符长度
- 9是服务器IP或域名的UTF-16BE编码, 这里分享一个可以在线将文本转换成UTF16BE编码的网站:传送门
- 10 是服务器的端口号,需要用16进制表示,这里也分享一个可以在线进行数字进制转换的网站:传送门,需要注意的是,如果服务器端口的16进制表示不足四个字节,则空缺的部分用0补齐,例如25565端口的16进制只有两个字节--63dd,我们要把它补成四个字节--0000 63dd
--服务端部分--未完待续...OK,解释完数据包结构,就去解决C++里的编码问题吧!
C++中使用UTF-16BE编码(UCS2-Big Endian)首先说一下我的开发环境我的系统是WIN10 x64为了验证代码的可移植性,我使用visual studio 和 vscode + Mingw-w64进行开发C++默认使用的编码是ANSI码,数据类型就是char类型,占一个字节由此我们需要为C++引入unicode编码(这里如果讲错了,可以说一下)具体可以查看我写的小程序的输出,它输出的是默认输出下的中文编码和UTF-16下的中文编码vs版:


小工具代码我直接贴在这里啦
代码:
- #include <string>
- #include <cstdlib>
- #include <locale>
- typedef unsigned char byte;
- void printAsH(char16_t chr16[])
- {
- int i = 0;
- while(chr16 != u'\0')
- {
- byte bHi,bLo;//分离出高低位字节
- char16_t chr = chr16;
- bHi = (chr&0xff00) >> 8;//这里要讲一下运算符顺序,虽然and运算符优先级高于右移运算符,
- //但是and运算符是右结合运算符,也就是说如果不加括号,程序还是会先算右边,再将右边与左边结合
- bLo = chr&0x00ff;
- printf_s("%02x ", bHi);
- printf_s("%02x ", bLo);
- i++;
- }
- }
- int main()
- {
- printf_s("中文UTF-16BE编码\t");
- printAsH(u"中文测试");
- char chsArr[64] = "中文测试";//可以添加更多中文来测试编码
- printf_s("\r\n中文默认编码\t\t");
- for(int i = 0; i < 64 && chsArr != '\0'; i++)
- printf_s("%02x ", chsArr&0xff);
- printf_s("\r\n");
- system("pause");
- return 0;
- }
既然说完了C++的默认编码,接下来就是编码的转换问题了从这个小工具的源码中不难看出,我们已经实现了部分转换功能
字符串前加上前缀u表示后面的字符串使用UTF-16编码存储char16_t则是为了使用UTF-16而引入的变量类型,它使用两个字节存储一个字符
我们的转换目标是将这些utf-16字符串转换成一个字节数组,至于为什么要这么转换,我会在后面进行讲解
小工具里已经使用截取char16_t类型的高低位来获取指定的字节,那如果我们将截取下来的字节保存到一个字节数组里不就可以实现这个转换目标了嘛,下面开始着手我们的工作吧
还记得上一篇教程中的MC发包原理嘛,包的前两字节是数据长度,那我们就在写转换函数的同时,写一个获取U16字符串长度的函数,方便我们之后使用。接下来我会先直接给出源代码,再挑一些重点进行进一步的解释
源代码:
代码:
- #include <string>
- #include <cstdlib>
- #include <locale>
- typedef unsigned char byte;
- void printAsH(char16_t chr16[])
- {
- int i = 0;
- while(chr16 != u'\0')
- {
- byte bHi,bLo;//分离出高低位字节
- char16_t chr = chr16;
- bHi = (chr&0xff00) >> 8;//这里要讲一下运算符顺序,虽然and运算符优先级高于右移运算符,
- //但是and运算符是右结合运算符,也就是说如果不加括号,程序还是会先算右边,再将右边与左边结合
- bLo = chr&0x00ff;
- printf_s("%02x ", bHi);
- printf_s("%02x ", bLo);
- i++;
- }
- }
- int getU16Len(char16_t chr16[])
- {
- int len = 0;
- while (chr16[len] != u'\0')//当前元素不为空,长度就加一
- len++;
- return len;
- }
- byte* getU16String(char16_t chr16[])
- {
- static byte product[128]{0};//最长地址支持到64字节,最后一位是结束标志(U16字符串一个字符两个字节,因此开辟的空间要比字符串长度大两倍)
- int iLength = getU16Len(chr16);
- for(int i = 0; i < iLength; i++)
- {
- byte bHi, bLo;//分离出高低位字节
- char16_t chr = chr16;
- bHi = (chr & 0xff00) >> 8;//这里要讲一下运算符顺序,虽然and运算符优先级高于右移运算符,
- //但是and运算符是右结合运算符,也就是说如果不加括号,程序还是会先算右边,再将右边与左边结合
- bLo = chr & 0x00ff;
- memcpy_s(product + 2*i, sizeof byte, &bHi, sizeof byte);
- memcpy_s(product + 2*i + 1, sizeof byte, &bLo, sizeof byte);
- }
- return product;
- }
- int main()
- {
- printf_s("中文UTF-16BE编码\t");
- printAsH(u"中文测试");
※代码太长了,一部分没显示出来,论坛好像限制了代码的长度
※不过核心部分的代码已经包含在里面了
这段代码的精髓部分应该是取char16_t的高低位的部分
bHi = (chr & 0xff00) >> 8;
这句话用自然语言表述就是先将chr变量的低位置0,然后右移8位,赋值给bHi,很晦涩,我们举例子便于理解
上文说到过char16_t是用两个字节存储字符的,我们举“中”这个字
他的UTF16BE编码表示为[size=1.2em]4E2D,两个字节。
由于我们要将它转换为byte(字节)类型并存储到byte数组中,
也就是说我们实际只要4E(高位部分)就行了,如何做呢,是用C++的位运算足矣
我们先使用and运算符将chr的低位置0,使4E2D变为4E00,再将chr右移八位(一字节),变成4E,然后我们就得到了高位部分
bLo = chr & 0x00ff;
同理,我们可以直接使用and运算符,将高位部分置0,得到002D,然后直接赋给bLo变量
也许你会疑问,16位变量为何能够直接赋值给8位变量,
我百度得到的结果是16位变量赋值给8位变量时16位变量的高位会被抛弃,只保留低位部分
也就是说这个002D的赋值操作只会保留2D部分,00直接被抛弃了
那为什么还要使用and运算符呢。。
嘿嘿,只是为了让编译器不报警告如果不使用and,直接复制,编译器会提示 “=”: 从“char16_t”转换到“byte”,可能丢失数据
其余代码都有注释,大家如果看注释还不懂的话,可以联系我,我会对此进行进一步的解释
未完待续...
由于要向服务器发送数据,因此大家如果有什么推荐的MC服务器可以向我推荐一下
怎么找到我?(Contact)
- 最简单的方式:你可以直接在本帖评论,我可能会在24小时内回答
- 也可以加Q群:Q群传送门 这是我刚组建的一个编程交流群,欢迎大家加入讨论编程问题
- 点击下方我个性签名中的链接,去我的博客留言
嘿嘿,对于@我这个操作,我首先给满分。