Java实现SLP(ServerListPing)获取Minecraft服务器信息(Java版)

First Post:

Last Update:

Word Count:
1.2k

Read Time:
7 min

SLP原理

https://wiki.vg上面有详细说明,发包给Minecraft服务器就行

实现

具体代码在下面,感谢猫车车重写JSON解析部分(旧的是zh32做的,JSON解析错误然后就换了阿里的FastJSON)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
package moe.xmcn.example;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

/**
* @author XiaMoHuaHuo_CN
* @author zh32 <zh32 at zh32.de>
* @author 猫车车
*/
public class ServerListPing {

private InetSocketAddress host;
private int timeout = 7000;
public void setAddress(InetSocketAddress host) {
this.host = host;
}

public InetSocketAddress getAddress() {
return host;
}

public void setTimeout(int timeout) {
this.timeout = timeout;
}

public int getTimeout() {
return this.timeout;
}

public 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) != 128) break;
}
return i;
}

public void writeVarInt(DataOutputStream out, int paramInt) throws IOException {
while (true) {
if ((paramInt & 0xFFFFFF80) == 0) {
out.writeByte(paramInt);
return;
}

out.writeByte(paramInt & 0x7F | 0x80);
paramInt >>>= 7;
}
}

public void fetchData() throws IOException {

Socket socket = new Socket();
OutputStream outputStream;
DataOutputStream dataOutputStream;
InputStream inputStream;
InputStreamReader inputStreamReader;

socket.setSoTimeout(this.timeout);

socket.connect(host, timeout);

outputStream = socket.getOutputStream();
dataOutputStream = new DataOutputStream(outputStream);

inputStream = socket.getInputStream();
inputStreamReader = new InputStreamReader(inputStream);

ByteArrayOutputStream b = new ByteArrayOutputStream();
DataOutputStream handshake = new DataOutputStream(b);
handshake.writeByte(0x00); //packet id for handshake
writeVarInt(handshake, 4); //protocol version
writeVarInt(handshake, host.getHostString().length()); //host length
handshake.writeBytes(host.getHostString()); //host string
handshake.writeShort(host.getPort()); //port
writeVarInt(handshake, 1); //state (1 for handshake)

writeVarInt(dataOutputStream, b.size()); //prepend size
dataOutputStream.write(b.toByteArray()); //write handshake packet


dataOutputStream.writeByte(0x01); //size is only 1
dataOutputStream.writeByte(0x00); //packet id for ping
DataInputStream dataInputStream = new DataInputStream(inputStream);
int size = readVarInt(dataInputStream); //size of packet
int id = readVarInt(dataInputStream); //packet id

if (id == -1) {
throw new IOException("Premature end of stream.");
}

if (id != 0x00) { //we want a status response
throw new IOException("Invalid packetID");
}
int length = readVarInt(dataInputStream); //length of json string

if (length == -1) {
throw new IOException("Premature end of stream.");
}

if (length == 0) {
throw new IOException("Invalid string length.");
}

byte[] in = new byte[length];
dataInputStream.readFully(in); //read json string
String json = new String(in);


long now = System.currentTimeMillis();
dataOutputStream.writeByte(0x09); //size of packet
dataOutputStream.writeByte(0x01); //0x01 for ping
dataOutputStream.writeLong(now); //time!?

readVarInt(dataInputStream);
id = readVarInt(dataInputStream);
if (id == -1) {
throw new IOException("Premature end of stream.");
}

if (id != 0x01) {
throw new IOException("Invalid packetID");
}
//read response

JSONObject object = JSON.parseObject(json);
JSONObject players = JSON.parseObject(object.get("players").toString());
JSONObject description = JSON.parseObject(object.get("description").toString());
JSONObject version = JSON.parseObject(object.get("version").toString());
List<Object> list = new ArrayList<Object>(JSON.parseArray(String.valueOf(description.get("extra"))));

StringBuilder Motd = new StringBuilder();
for (Object o : list) {
JSONObject text = JSON.parseObject(o.toString());
Motd.append(text.get("text"));
}

if (object.get("favicon").toString() != null) {
ServerListPing.Response.setIcon(object.get("favicon").toString());
} else {
ServerListPing.Response.setIcon("");
}

ServerListPing.Response.setPlayerMax(players.get("max").toString());
ServerListPing.Response.setPlayerOnline(players.get("online").toString());
ServerListPing.Response.setProtocol(version.get("protocol").toString());
ServerListPing.Response.setVersion(version.get("name").toString());
ServerListPing.Response.setMOTD(Motd.toString());

dataOutputStream.close();
outputStream.close();
inputStreamReader.close();
inputStream.close();
socket.close();
}

/**
* 获取请求结果
*/
public static class Response {

private static String player_max;
private static String protocol;
private static String player_online;
private static String version;
private static String motd;
private static String favicon;
public static String getPlayerMax() {
return Response.player_max;
}

public static String getPlayerOnline() {
return Response.player_online;
}

public static String getProtocol() {
return Response.protocol;
}

public static String getVersion() {
return Response.version;
}

public static String getMOTD() {
return Response.motd;
}

public static String getIcon() {
return favicon;
}

// 内部变量 设置结果
private static void setPlayerMax(String player_max) {
Response.player_max = player_max;
}

private static void setPlayerOnline(String player_online) {
Response.player_online = player_online;
}

private static void setProtocol(String protocol) {
Response.protocol = protocol;
}

private static void setVersion(String version) {
Response.version = version;
}

private static void setMOTD(String motd) {
Response.motd = motd;
}

private static void setIcon(String favicon) {
Response.favicon = favicon;
}

}

}

Maven部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>moe.xmcn.example</groupId>
<artifactId>ServerListPing</artifactId>
<version>1.0</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
</plugins>
</build>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>

<repositories>

<repository>
<id>maven</id>
<url>https://maven.aliyun.com/nexus/content/groups/public/</url>
</repository>

</repositories>

<dependencies>

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.9</version>
</dependency>

</dependencies>

</project>

调用

先设置服务器地址,类型是java.net.InetSocketAddress

1
2
3
4
5
6
InetSocketAddress isa = new InetSocketAddress(String host, int port);
ServerListPing slp = new ServerListPing();
slp.setAddress(isa);

// 也可以手动设置延迟,单位ms,不设置默认是7000
//slp.setTimeout(int timeout);

然后通过ServerListPing.fetchData()方法获得数据

1
slp.fetchData();

得到数据之后会自动解析,调ServerListPing.Response类获取信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 获得服务器版本
ServerListPing.Response.getVersion();

// 获得服务器协议版本
ServerListPing.Response.getProtocol();

// 获得服务器在线玩家数量
ServerListPing.Response.getPlayerOnline();

// 获得服务器最大玩家数量
ServerListPing.Response.getPlayerMax();

// 获得服务器MOTD
ServerListPing.Response.getMOTD();

// 获得服务器图标
ServerListPing.Response.getIcon();