Unity3D高级编程网络层剖析数据协议

协议包的格式,json, msgpack, protobuf 以及自定义格式
项目的网路层在建设中,除了选择传输协议TCP,UDP,以及应用层协议HTTP方式外,还需要选择在传输过程中的业务层协议格式。前面我们分析了TCP,UDP,HTTP的原理与应用,这里我们来了解下在传输层和应用层之上的业务层中,网络数据传输格式的选择以及它们的利弊。我们将在这里剖析JSON,MessagePack,Protobuf的原理,包括它们都是由什么组成的,怎么序列化的,以及怎么反序列化,通过对原理和底层的剖析使我们对网络数据协议的理解更加透彻清晰。

我们从最常见的JSON格式开始,一步步深入了解业务层协议的规则与背后的原理,一步步剖析复杂的数据格式与底层实现。

===

JSON
JSON原本是JavaScript 对象表示法(JavaScript Object Notation),后来慢慢在被大家所接受普及开来成为一种协议数据的格式。它是存储和交换文本信息的语法,类似于 XML 但又比 XML 更小、更快,更易解析。

JSON 本身是轻量级的文本数据交换格式由字符串组成,它独立于语言且具有自我描述性,这些特性导致它非常容易被人理解。与同是纯文本类型格式的XML相比较,JSON不需要结束标签,JSON更短,JSON解析和读写的速度更快,在JavaScript中能够使用内建的 JavaScript eval() 方法进行解析,JSON还可以使用数组,且不使用保留字(&,<,>,’,”)。

我们来看看 JSON 的语法规则,JSON 数据的书写格式是:名称/值对。名称/值对包括字段名称(在双引号中),后面写一个冒号,然后是值:

"firstName" : "John"
JSON数据由逗号分隔,它的值可以是数字、字符串、真假逻辑值、数组、对象,我们来看看它们在文本中的具体格式:

数字(整数或浮点数)

{
  "number" : 1,
  "number2" : 11.5
}
字符串(在双引号中)

{
  "str1" : "1",
  "str2" : "11"
}
逻辑值(true 或 false)

{
  "logic1" : true,
  "logic2" : false
}
数组(在方括号中)

{
   "array1" : [1,2,3],
   "array2" : [{"str1",1},{"str2",2},{33,44}]
}
对象(在花括号中)

{
  "obj1" : {1, "str1", true},
  "obj2" : {"str2", 2, false},
  "obj3" : null
}
其中对象在花括号中书写,其对象可以包含多个名称/值对:

{ "firstName":"John" , "lastName":"Doe" }
数组在方括号中书写,数组可包含多个对象:

{
    "employees": [
        { "firstName":"John" , "lastName":"Doe" },
        { "firstName":"Anna" , "lastName":"Smith" },
        { "firstName":"Peter" , "lastName":"Jones" }
    ]
}
JSON 文件的文件类型通常是 “xxx.json” 用来扩展名用来说明是json格式的文本文件。在HTTP协议中还定义了Json格式的MINE类型以方便终端逻辑识别,JSON 文本的 MIME 类型是 “application/json” (MIME (Multipurpose Internet Mail Extensions) 是描述消息内容类型的因特网标准。其他MIME 消息包含文本、图像、音频、视频以及其他应用程序专用的数据。

我们在平时的编程中 JSON 解析器也比较多,例如simpleJson,MiniJson,DataContractJsonSerializer,JArray,JObject等等,都是非常通用高效的插件,也可以自己’造轮子‘做一个JSON解析器,做时也要多考虑下效率和性能方面的问题。

自定义二进制流协议格式
大部分的网络协议都具有一定的通用性,JSON是最典型的案例,其他的包括 XML,MessagePack,Protobuf都是相对通用的,但我们所要说的自定义二进制流协议则不是,理论上说它完全不通用,其原因是它被设计出来就不需要顾及通用性。

我们在存储一串数据的时候,无论这串数据里包含了哪些数据以及哪些数据类型,当我们拿着这串数据解析的时候我们应该首先知道数据如何解析,这是定义协议格式的目标。简单说就是,当我们收到一串数据的时候,我们用什么样的规则知道这串数据里的内容的,这就是协议规则的目标。JSON就制定了这么一个规则,这个规则以字符串 KEY-VALUE 简单配对的形式,以及一些辅助的符号‘{’,’}’,’[’,’]’组合而成,这个规则比较通用且易于理解,这使得任何人拿到JSON数据都能一眼知道里面有什么数据。

自定义二进制协议格式则不具有通用性,并不是任何人拿到数据都能知道里面装的是什么的,有且只有两端协定的双方才知道该如何解析收到的数据,对于破解自定义二进制流的内容也只有靠猜因为协议格式只有制定时的双方才知道(虽然猜的难度也不是很大,很多辅助都靠经验猜测数据内容)。

一个自定义二进制流协议格式,分成三部分:

数据大小|协议编号|具体数据
用代码结构可以表示为:

class Mssage
{
  uint Size;
  uint CommandID;
  byte[] Data;
}
数据大小、协议编号、具体数据,这三者构成了一个完整的协议内容,当然很多时候command id 可以放入具体的数据中去。

现在假设我们客户端有这样一个数据结构需要传输到服务端去:

class TestMsg
{
  int test1;
  float test2;
  bool test3;
}
服务端拿到数据时,其实是完全不知道当前拿到的数据是什么,也不知道数据是否完整,有可能只拿到一半的数据,或者一部分的数据。因此首先我们要确定的是,我们收到的数据包它的完整的大小有多大,只有知道完整的包体大小才能确定我当前收到的数据在大小上是否完整,我们是要等待继续接受后面的数据,还是现在就可以进行解析操作了。

为了确定包的完整性,我们必须先向二进制流中读取4个byte,组合成一个无符号整数,整数总共32位,也就是说我们的数据包的大小最大可以为2的32次减1个byte,这个整数让我们知道了接下来数据的大小。例如我现在接收到了20个byte后,读取了前4个byte,组成一个整数后这个整数为24,说明后面16个byte是一个不完整的包体,我应该继续等待后续的数据到来。

其次我们要确定的是收到的数据包是属于哪个协议格式。于是我们再读取4个byte大小的数据,组成一个无符号整数CommandID,用来确定协议号。假如这个无符号整数的协议号为1002,就代表是接下来的数据是编号为1002的协议的数据格式。假设我们上面这个TestMsg类就是协议号1002的数据体,那么接下来连着这个协议号的所有数据直到包体大小的末尾都是这个TestMsg的数据,我们可以提取后解析为该类实例。

在解析这个具体数据的时候,我们需要根据生成这个数据的顺序来解析,写入数据的顺序和读取数据的顺序是一致的。假设在生成这个二进制流数据时,我们的顺序是,先推入test1变量,再推入test2变量,再推入test3变量。其中test1变量为4个byte的整数,test2变量为4个byte的浮点数,test3变量为1个byte的布尔值,于是就有了如下byte数组结构:

xxxx|xxxx|x
这样一个形状的二进制流,每个‘x’为一个byte,前两次4个byte组成一个int和float数据,最后1个byte组成布尔数据,‘        ’只是为了解释说明用的分隔符不存在于数据内,这个数据是由9个byte组成,其中前4个byte为test1,中间4个byte为test2,后面1个btye为test3。
在向网络传输的中整个数据包TestMsg的格式为如下:

13|1002|test1|test2|test3
上述格式中13为接下来的数据包大小,1002位协议编号,test1        test2        test3为具体数据。我们在解析的时候也需要按照生成时的顺序来解析,先读取前4个byte组成一个整数赋值给test1,接着再读取4个byte组成一个浮点数赋值给test2,接着再读取1个byte赋值给test3,完成数据解析。
对于数组形式的数据则要在原来的基础上多增加一个长度标志,比如 int[]类型数据,在生成时先推入代表长度的无符号整数数据,再连续推入所有数组内容,在解析的时候做同样的反向操作,先读取4个byte的长度标志,再对连续读取N个具体数据,N为提取的长度。我们举例int[]为3个整数数组则二进制为如下效果:

xxxx|xxxx|xxxx|xxxx
前4个byte为长度数据,接着3次4个byte为数组内的整数数据。

自定义二进制流协议格式为最不通用的格式,但可以成为最节省流量的协议方式,因为每个数据都可以用最小的方式进行定义,比如协议号不需要4个byte,2个byte大小代表2的16次-1也就是65535就够用了,长度有可能也不需要4个byte,只要2个甚至1个byte就够用了,有些数据不需要4个byte组成int整数,只需要2个byte数组ushort就够用了,甚至有些可以组合起来使用,比如协议结构中有4个bool,可以拼成一个byte来传递,这些都可以完全由我们来控制包体的大小不受到任何规则的限制,这也是自定义二进制协议格式最吸引人的地方。

文章来源:https://www.yxkfw.com/thread-70138-1-1.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值