那我在加个Minecraft 服务器信息获取,配合JSON
根据https://wiki.vg/Server_List_Ping公开的wiki
已知Minecraft 服务器信息获取的数据类型
可得知需要以下信息
Protocol Version :VarInt类型;协议版本,这个不重要,不知道协议版本时可以设置为-1
Server Address : String类型;服务器地址
Server Port : UShort类型;服务器端口
Next state : VarInt类型;下一个状态,状态为1,登录为2。
首先我们要先准备好这些数据,根据他所示的数据类型进行组合
首先是VarInt;
这里的VarInt与一般的VarInt相似,而Minecraft的VarInt只使用了7个位。
我们需要先实现VarInt
[C#] 纯文本查看 复制代码 public static byte[] GetVarInt(int value)
{
List<byte> bytes = new List<byte>();
while ((value & -128) != 0)
{
// value & 127
// 因为VarInt需要msb所以一个字节只能储存7bit数据
// 127二进制是0111 1111和value做位与,取7bit的有效数据
// value & 128
// 7bit的有效数据做位或 128二进制是1111 1111,添加msb信息
bytes.Add((byte)(value & 127 | 128));
// 向右移7bit,处理剩余高位的数据
value = (int)((uint)value >> 7);
}
bytes.Add((byte)value);
return bytes.ToArray();
}
实现完VarInt之后,就是String类型,根据Minecraft wiki文档中可得知
可能不太好理解,简单来说就是 字符串数据的长度(VarInt类型)+字符串数据
GetVarInt(Encoding.UTF8.GetBytes(字符串数据))+Encoding.UTF8.GetBytes(字符串数据)
当完成这些数据类型之后,就可以进行组合了。
注意,还要查看一下Minecraft 发包格式
这里使用的是未经Zlib压缩的数据,格式也很好理解,完整的包= Packet ID+Data的长度信息(VarInt)+packet id(VarInt) +data,
好了,知道这些信息之后 就可以开始写TCP通讯了
首先C# 创建TCP客户端对象
[C#] 纯文本查看 复制代码 TcpClient tcpClient = new TcpClient(host, port);
顺便设置一下缓存接收区
[C#] 纯文本查看 复制代码 // 设置TCP接收缓冲区大小
tcpClient.ReceiveBufferSize = 1024 * 1024;
准备一下握手的数据包
[C#] 纯文本查看 复制代码 // 握手数据包
byte[] packetId = GetVarInt(0);
byte[] protocolVersion = GetVarInt(-1);
byte[] serverAddress = Encoding.UTF8.GetBytes(host);
serverAddress = ConcatBytes(GetVarInt(serverAddress.Length), serverAddress);
byte[] serverPort = BitConverter.GetBytes((ushort)port); Array.Reverse(serverPort);
byte[] nextState = GetVarInt(1);
// 请求数据包跟进
byte[] statusRequest = GetVarInt(0);
这里的ConcatBytes作用是将多个数组添加在一起
[C#] 纯文本查看 复制代码 public static byte[] ConcatBytes(params byte[][] bytes)
{
List<byte> result = new List<byte>();
foreach (byte[] array in bytes)
result.AddRange(array);
return result.ToArray();
}
然后进行发送TPC数据
[C#] 纯文本查看 复制代码 // 发送数据包
tcpClient.Client.Send(packet);
tcpClient.Client.Send(statusRequest);
发送之后肯定要接收,接收的数据格式和我们发送时一样。
VarInt类型的数据包总长度信息+packet ID+数据
这里我单独吧读取写了方法以便调用
[C#] 纯文本查看 复制代码 public static byte[] GetVarInt(int value)
{
List<byte> bytes = new List<byte>();
while ((value & -128) != 0)
{
// value & 127
// 因为VarInt需要msb所以一个字节只能储存7bit数据
// 127二进制是0111 1111和value做位与,取7bit的有效数据
// value & 128
// 7bit的有效数据做位或 128二进制是1111 1111,添加msb信息
bytes.Add((byte)(value & 127 | 128));
// 向右移7bit,处理剩余高位的数据
value = (int)((uint)value >> 7);
}
bytes.Add((byte)value);
return bytes.ToArray();
}
/// <summary>
/// 读VarInt类型的整数
/// </summary>
/// <param name="tcpClient"></param>
/// <returns></returns>
public static int ReadVarInt(TcpClient tcpClient)
{
int numRead = 0;
int result = 0;
byte read;
do
{
read = ReadData(tcpClient, 1)[0];
int value = (read & 0b01111111);
result |= (value << (7 * numRead));
numRead++;
if (numRead > 5)
{
throw new Exception("VarInt is too big");
}
} while ((read & 0b10000000) != 0);
return result;
}
/// <summary>
/// 读取TCP接收数据
/// </summary>
/// <param name="tcpClient"></param>
/// <param name="length"></param>
/// <returns></returns>
public static byte[] ReadData(TcpClient tcpClient, int length)
{
if (length > 0)
{
byte[] cache = new byte[length];
Receive(tcpClient, cache, 0, length);
return cache;
}
return new byte[] { };
}
/// <summary>
/// 读取TCP接收数据
/// </summary>
private static void Receive(TcpClient tcpClient, byte[] buffer, int offset, int size)
{
int read = 0;
// read=实际读取量,如果小于要取的数据大小,则继续读取TCP数据
while (read < size)
{
read += tcpClient.Client.Receive(buffer, offset + read, size - read, SocketFlags.None);
}
}
我们先读取总长度信息
[C#] 纯文本查看 复制代码 int dataLength = ReadVarInt(tcpClient);
在读取长度信息长度的数据
[Asm] 纯文本查看 复制代码 //读取数据包总长度的数据,使用List以便后期数据处理
List<byte> packetData = new List<byte>(ReadData(tcpClient, dataLength));
这时候已经吧数据全取回来了
接着我们读Packet ID,一样的他是VarInt数据类型 我们也要读取一下,这里我又建了一些方法,供list数据处理,与上面不同的是,他是已经读取出来的数据待处理,而上面的是读取TCP缓冲区的数据
[C#] 纯文本查看 复制代码 /// <summary>
/// 读VarInt类型的整数
/// </summary>
/// <param name="tcpClient"></param>
/// <returns></returns>
public static int ReadVarInt(List<byte> lists)
{
int numRead = 0;
int result = 0;
byte read;
do
{
read = ReadByte(lists);
int value = (read & 0b01111111);
result |= (value << (7 * numRead));
numRead++;
if (numRead > 5)
{
throw new Exception("VarInt is too big");
}
} while ((read & 0b10000000) != 0);
return result;
}
public static byte ReadByte(List<byte> lists)
{
// 读取一个字节
byte result = lists[0];
// 删除已读字节
lists.RemoveAt(0);
return result;
}
public static byte[] ReadData(List<byte> lists, int size)
{
// 连续读取字节
byte[] result = lists.Take(size).ToArray();
// 删除已读字节
lists.RemoveRange(0, size);
return result;
}
然后判断他是否为0x00,这里是根据Minecraft wiki得知的,是0x00包头ID数据的话 后面紧跟这JSON数据字符串
如wiki所提供的例子
我们读取JSON数据
[C#] 纯文本查看 复制代码 // 响应的内容应该是呈JSON字符串数据,字符串=字符串数据的长度信息(VarInt)+字符串数据
// 先读取JSON长度
int JsonLength = ReadVarInt(packetData);
// 读取数据内容,并将字节数组转换成UTF8字符串
string result = Encoding.UTF8.GetString(ReadData(packetData, JsonLength));
就获取到一个Minecraft 服务器响应的JSON编码的字符串
这时就能使用到之前的JSON解析
我们调试一下,成功取出
这里只取了version里的信息,其他同理。这个还可以根据WIkI文档里实现各种各样的功能,可模拟Minecraft 网络通讯协议实现 |