报文在所有的CS架构中,是很重要的基础。因此,在所有工作开展之前,有必要先对报文进行分析。作为一个通用的服务器,那么他的报文格式必须受标准 约束,在灵活性跟可扩展性方便是很重要的考虑因素,而对具体的业务规则反而不能深入的介入。魔兽世界的报文具有他本身私有的业务规则,单一世界是为网游戏 设计的,所以不可避免得要加入业务规则。一个折衷的方案是,服务器内部具有通用的标准规则,而通过转换器将魔兽世界的规则转换为单一世界的通用规则。
从 WorldSock.cpp开始看魔兽世界的报文结构,在int WorldSocket::handle_input_missing_data (void)函数,接收客户端发来的报文。
首先,解开报文头:struct ClientPktHeader { uint16 size; uint32 cmd; } ,用的SARC4解码。关于加密解密在后续中继续讨论。在网络传输中,size跟cmd按照网络惯例是以大端方式保存,因此还需要进行字节序转换。
其 次,是报文体。让我十分差异的是,报文体本身没有经过任何处理,显然只是个缓冲区。而报文体结构是根据命令,在具体的业务中进行处理。报文体主要容纳数值跟字符串,数值是按大端格式保存的,而字符串是以0结尾保存的。
还有 一个很奇怪的是,在流方式的CS中,报文头的边界是很重要的,通常我用0x5555来作为报文边界。这种能避免每个命令之间由于网络传输中无意中添加入无 用的字节引起的问题。因为没有报文边界的话,将导致后续所有的报文无法解码,且无法恢复,而 报文边界的存在,将很快的从下一个正确报文开始重新解码。
在方法bool readPackGUID(uint64& guid),也是蛮好玩的,他加了一个字节,作为位图。后续的连续8个字节中,只有在第一个字节中的比特位是1才是有效的,如果该字节为0,那么该字节就被抛弃了。这个方法很有趣,一般的GUID中,为0的字节还是比较多的,因此压缩空间还比较大。还有一种也可以借鉴,类似于UTF8的编码,只使用一个字节中的7位,当一个字节的最高位是1的话,那么就结束了,这种方法最差情况跟readPackGUID是一样的,多了一个字节。而最好的情况不需要额外的字节。不过从压缩角度来看,readPackGUID应该更有效。
另外一个办法是void appendPackXYZ(float x,float y,float z)这个函数,他用一个uint32来保存X/0.25F[0--->10] Y/0.25F[11--21] Z/0.25F[22-31]。实际上,就是降低精度,保证在0.25的最低精度。因此,传输的字节数也少了很多。
从opcodes.h中,可以看到有NUM_MSG_TYPES=0x507=1287个操作码,包括服务端跟客户端,但真正用到的没有这么多。我会先完成一个前置机,负责接收所有的报文,然后转发给服务器,以及接收服务器的报文转发给客户端。在未来的设计中,我会利用这个前置机,将一部分工作交给其他服务进程,同时又不影响现在的服务架构。当然,前置机的另外一个工作还包括解析报文,以便重演所有的过程,这个是个很有挑战性的任务,需要多方面配合,暂时先放着。
原文链接:https://www.f2er.com/javaschema/287615.html