【游戏】服务器性能测试(五)网络协议包序列化
目录
一、前言
上一期介绍了压力测试工具的设计思路,接下来的几期会逐步拆分其中的模块进行单独说明,今天介绍的内容是关于通讯协议的序列化与反序列化,其实理解后也非常的简单易懂。
二、字段类型与序列化
网络游戏前后端通信的消息协议始终是基于制定的规则,即发送端使用什么样的方式进行序列化,接收端就需要使用对应的方式进行反序列化,只有这样才能保证发收双方能够正常“交流”
消息协议的组成离不开基础数据类型,这些数据类型包括单字符(char)、短整型(short)、整型(int)、长整型(long long)、浮点数(float,double)、字符串(char* / string)。这些数据类型具体占多少字节可以参考游戏协议测试一:协议测试介绍。
消息协议通常由长度+消息号+协议内容组成,那如何进行协议组装呢?首先消息协议可以看作是一连串的单字符组成的数据,我们只需要将其他数据类型的数据按相应进行转换成单字节字符数组,然后合并发送即可。
# c语言网络send方法的定义,char* 其实就是由char组成的数组 int send(SOCKET s,const char *buff,int len,int flag);
那么这里主要以int(整型)为例进行介绍如何转换成char*格式。首先int是由4个字节组成。例如16909060可以表示为(1<<24) |(2<<16)|(3<<8)|4。可以使用移位的方式进行拆成4个单字节组成如下代码。
# c/c++ int v = 16909060; char h1 = (v >> 24) & 0xFF; # 暂时忽略有无符合 char h2 = (v >> 16) & 0xFF; char l1 = (v >> 8) & 0xFF; char l2 = v & 0xFF; print("h1=%d, h2=%d, l1=%d, l2=%d",h1, h2, l1, l2) # 1,2,3,4 # 这样就将int v转化成 char数组 char* v_to_chars = {h1, h2, l1, l2}
通过上面的代码就可以将1个int的数转成char*的表示方法。那么针对short、long long原理也是如此,浮点数就需要特殊处理一下,然后字符串本身就是char*因此不需要做转化。
# c/c++ # 协议结构为 协议长度(int) + 协议消息(short) + 协议内容 short msgID = 0x2002; char* data = "hello world!"; #计算 4 + 2 + data的长度 int len = sizeof(int) + sizeof(short) + strlen(data) char* packet = new char(len); # 以下是伪代码填充packet packet[0~3] => {len>>24&0xFF, len>>16&0xFF,len>>8&0xFF,len&0xFF} packet[4~5] => {msgID>>8&0xFF, msgID&0xFF} packet[6~] => data # 调用send方法即可发送序列化好的协议内容出去 send(socket, packet, len, 0);
根据以上方法即可完成对协议的序列化发送,虽然本例代码中协议内容仅只有一个字符串,了解基本的方法后,添加其他复杂的内容无非是做更详细的转换后组装起来。注:本例做了一个错误的示范即字符串通常发送时前面会额外增加表示这个字符串长度的字段变成`packet_len + msgID + data_len + data`。
另外假设我把int v的表示char*表示方法由`{h1, h2, l1, l2}` 变为 `{l2, l1, h2, h1}`即倒转过来是不是也可以呢?答案是可以,这是一个新的概念:大小端,具体内容可以参考百度搜索。
通过上面的介绍应该差不多对协议的序列化有一定的了解,但其实在日常的工作开发中,上面这一层面往往是封装好的,业务同学只需要调用类似下面的代码即可。
Packet packet; packet.writeShort() # 写入短整型 packet.writeInt() # 写入整型 packet.writeByte() # 写入单字节 packet.writeString() # 写入字符串 packet.writeFloat() # 写入浮点数 # 然后发送出去 some_obj.send(packet)
三、扩展内容
1. 数组序列化
数组是非常常见的数据存储方式,例如玩家的好友列表通常是一个数组,通常的序列化方式是数组元素总数+数组个元素数据写入
Friend myFriends[10]; packet.writeShort(10) # 数组长度 for(f : myFriends) # 遍历写入每个元素的数据 { packet.writeInt(f.id); packet.writeString(f.name); packet.writeInt(f.level); }
2. 有符号和无符号
针对char、short、int都会需要考虑数据的表示范围,就会接触到有符号或无符号。那么在C/C++中可以定义unsigned即可,而对应java就需要&0xFF进行转化。
3. 不同语言的序列化库
3.1. python语言可以使用自带的struct库,通过pack进行打包序列化,unpack进行反序列化。
#python 可以使用struct struct.pack("i",111) struct.pack("3s","aaa")
3.2. java语言可以自己实现,也可以使用ByteBuffer类,序列化使用putXXX,反序列化使用getXXX即可。
# java nio.ByteBuffer b = ByteBuffer.allocate(size) b.putInt(111) b.putShort(22)
四、协议的反序列化
只要理解了协议的序列化后,反序列化即按照对应的方法反过来即可,具体如下代码演示。
char* data = "xxxx" char h1 = data[0] char h2 = data[1] char l1 = data[2] char l2 = data[3] int v = (h1<<24&0xFF) | (h2<<16&0xFF) | (l1<<8&0xFF) | (l2&0xFF)
关于协议的序列化和反序列化就介绍到这里,如有疑问可以关注我的微信公众号给我留言哦,希望对大家有所帮助!
欢迎微信搜索"游戏测试开发"关注一起沟通交流。