浅谈游戏中的字节型通信

in 寻寻觅觅 with 1 comment

前言

联网游戏中最重要的就是客户端和服务器的通信,界面是通信数据和用户之间的媒介,它告诉用户收到了什么样的数据,并将这些数据以特定的形式呈现出来,便于用户理解,而用户再根据自己的理解与界面交互,界面再根据用户交互内容向服务器发送合法的数据,告诉服务器用户做了什么。

通信的方式包括字节型和字符型两大类。字节型通信是将客户端与服务器之间的数据整理成字节数组,并将字节数组打包、标记、加密甚至切片,最后发送数据。字符型通信是利用json和xml等文本型数据进行通信,这类通信类型在通信时将文本数据编码成字符数组处理发送。二者相比,相同长度下,字节型通信往往能发送更多的数据,可加密性比较好,但相应的,字节型通信需要提前定义好通信格式,否则晦涩难懂的字节数组可能让人没有头绪。

概述

以下是我从s2游戏中了解到的一些字节型通信的知识。

字节型通信的数据是由字节数组组成的,长度较短,对它进行加解密的代价也很小。

在加密前,字节数组应包含两部分:数据头和数据体。

这一点就像http协议里面的请求头和请求体(响应头和响应体),数据头包含数据的基本属性,用来描述发送和接收的数据信息的特征,字节型通信中往往长度固定,字符型通信中可以用类似json、xml的格式,长度可以固定;数据体包含的是通信的核心数据,无论是在字节型还是字符型通信中,其长度都不固定,数据量越大,长度越长。

数据类型

对于字节型通信,无论是在数据头还是数据体中,其数据类型固定,大抵有数字、字符串和数组三种,他们决定了数据的写入方式。

数字

数字包括整型和浮点型两大类,其中整型又有8位、16位、32位甚至64位之分,浮点型也有32位和64位之分。

但是,2字节(16位)以上的数字,内部又有大端编址和小端编址两种类型。

关于编址,此处不再赘余,可参考: https://www.onesrc.cn/p/big-endian-and-little-endian.html

字符串

对于字符串型数据,将他们写入字节数组时往往写入他们对应的编码,至于这种编码到底是哪种类型的,是定长的UNICODE系还是变长的UTF系类型,这就由开发者自己决定了。另外,对于UTF字符串类型数据,其长度大小写在字符串的第一位(可参考java的String)。但是在字节通信中,可以提前规定指定长度的字节为字符串,不在编码中写入字符串长度,当然也可以不指定长度,把字符串的长度写在字符串的最前面。

类型
定长字符串字符编码字符编码字符编码字符编码
变长字符串字符串长度字符编码字符编码字符编码

数组

这里的数组可以是数字组成的数组,也可以是自定义数据类型的数组。在将数组转换成字节数组时,先将数组的长度写入第一位,然后写入数组的元素,各个元素按其类型顺序写入。

数组
[a0, a1, a2]数组长度a0a1a2

数据包

数据头

数据头包含数据包的基本属性和信息,一般包括数据长度, 协议号, 用户标识, 消息序码, 校验码五部分(当然也可能更多)。

数据长度是数据包的长度,可以用来检验数据完整性。如果所有通信包都很短,那么数据长度就可以省略;但是如果数据过长,一次不能发送完,那么就需要切片分成几部分,再分别发送,这时候数据长度就必不可少了,如果没有数据长度,接收方就会把数据包的切片理解成多个数据包!所以建议还是在数据头中为数据长度保留一个位置。

协议号是提前定义好的数据包结构,一般只影响数据体。同一数据体在不同的协议号下会被理解成不同的数据,所以协议号必不可少。

用户标识是用户的唯一标识,用来识别不同的用户,让系统知道数据是那个用户发送的。(游戏通信中往往都是包含状态的长连接,即使没有用户标识,系统也能利用已有的状态判断是哪个用户发送的。所以这个标识主要是为开发者准备的,方便开发者查看。)

消息序码是对消息的顺序编码,其值不一定是0,1,2,3这样的顺序,也可以是0,1,4,9这类具有特殊函数关系的顺序。消息序码的另外一作用是避免重复包,每一个消息都有序码。但有时候,比如网络链接不稳定时,客户端有可能会重复发送一个包,这是如果被服务端当成多个包就显得不合适了。所以,对于这些重复包,我们完全可以在客户端做出调整,让他们在发送这些重复包时使用相同的序码,让服务端知道这是同一个数据,以避免被服务端重复处理。

校验码是对数据包的校验,它可以在一定程度上避免数据被用户篡改。至于校验算法是什么样的,一般由开发者自定义。字节通信中字节数组是其核心,所以校验的算法一般以字节为单位进行。

数据体

数据体是通信的核心部分,通信的信息都写在了数据体里面,其具体内容由通信的协议号和通信内容共同决定。

加密

直接发送数据头和数据体是不行的,没有一点保密性,尤其是其中的字符串内容部分,若不做处理,很容易被人发现端倪。

加密方式也是多种多样,但是选择加密方式时一定要注意加密方式的可解密性。如果你放荡不羁,hash、hash两下,那你就凉了,无法解密!你也就无从得知信息到底是什么内容了。通常情况下,为了保证数据的可解密性和加密后数据不至于过长,加密方式的选择都是以异或为主,再以位移为辅。这种加密方式类似于函数中的全射(至少是很接近全射的单射),能够保证加密后得到的数据和原来相比不会过长,同时保证了数据的可还原性。

加密后的数据长度可能改变,所以一般都需要将加密后的数据长度重新写入到通信包的前面。

切片

游戏通信通常用的是套接字(socket),但套接字一次通信的数据量是有限的(通信量过大更容易出错,出现数据丢失等问题),所以数据量很大时,需要对数据切片处理,然后按顺序发送这些切片包。

至此,数据包打包完成!

题外话

以上都是在探究s2游戏时的一些认识和心得,可能很有限,但是值得了解一下。

Responses
  1. js

    alert('Hello World!\n'你好呀!);

    Reply