实现自己的数据库驱动——Packet的write(十)

前言

上一篇讲解如何将服务端中代码解析到具体的类中,这篇主要来讲解如何将一个包的数据,按照协议的要求发出。

如服务端需要接受Client的一个Auth,格式要求如下:

你想输入的替代文字

Produce Auth Packet

具体步骤是先生存AuthPacket,然后将对应的数据附上。然后调用auth.write(buffer),将数据写入到buffer中。

再调用buffer.get(bytes, 0, bytes.length)把buffer转为二进制数据返回bytes,然后利用socket编程将bytes传给Server端。这里主要针对auth.write(buffer)进行说明,具体的完整代码可以在我的github上找到。

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
/**
* 根据handshake包中的salt1和salt2生成加密的密码,将用户名、密码、数据库名一起打包成authPacket发送给服务端
* @param rand1 salt1长度为8
* @param rand2 salt2长度为12
* @param user 用户名
* @param password 未加密的密码
* @param database 要操作的数据库名
* @return 返回auth packet的二进制
*/
public static byte[] produceAuthPacket(byte[] rand1,byte[] rand2, String user , String password,String database) {

byte[] seed = new byte[rand1.length + rand2.length];
System.arraycopy(rand1, 0, seed, 0, rand1.length);
System.arraycopy(rand2, 0, seed, rand1.length, rand2.length);

AuthPacket auth = new AuthPacket();
auth.packetId = 1;//包的序列号
auth.clientFlags = getClientCapabilities();//客户端协议
auth.maxPacketSize = 1024 * 1024 * 1024;//包的最大值
auth.user = user;//用户名
try {//利用salt1,salt2对password进行加密
auth.password = SecurityUtil
.scramble411(password.getBytes(), seed);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
auth.database = database;//操作的数据库名

ByteBuffer buffer = ByteBuffer.allocate(256);
auth.write(buffer);//将数据的二进制写入buffer中
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes, 0, bytes.length);
return bytes;
}

这里讲一些特殊字段的write,如长度为4字节的clientFlags

1
2
3
4
5
6
7
//如果是存入四个字节,其实是不能使用int的,因为24-32位是有符号位的,所以这里需要使用Long,这样可以保证前32表示的都是值
public static final void writeUB4(ByteBuffer buffer, long l) {
buffer.put((byte) (l & 0xff));
buffer.put((byte) (l >>> 8));
buffer.put((byte) (l >>> 16));
buffer.put((byte) (l >>> 24));
}

还有数据后跟着null的字段,如username

1
2
3
4
public static final void writeWithNull(ByteBuffer buffer, byte[] src) {
buffer.put(src);
buffer.put((byte) 0);
}

还有在插入数据前,写入长度的数据,如password。因为表示长度的数据是一个LengthEncodedInteger,所以根据长度的大小,先要插入一个标志符,具体如下:

最小值(包含) 最大值(不包含) 存储方式
0 251 1个字节
251 2^16 3个字节(0xFC + 2个字节具体数据)
2^16 2^24 4个字节(0xFD + 3个字节具体数据)
2^24 2^64 9个字节(0xFE + 8个字节具体数据)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static final void writeWithLength(ByteBuffer buffer, byte[] src) {
int length = src.length;
if (length < 251) {
buffer.put((byte) length);
} else if (length < 0x10000L) {
buffer.put((byte) 252);
writeUB2(buffer, length);
} else if (length < 0x1000000L) {
buffer.put((byte) 253);
writeUB3(buffer, length);
} else {
buffer.put((byte) 254);
writeLong(buffer, length);
}
buffer.put(src);
}