You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

9.8 KiB

第23讲 | 如何定制合适的开发协议?

什么是开发协议说得简单一点就是一种客户端和服务器端的网络沟通协议Protocol

广义上说协议是计算机各种设备之间沟通的桥梁。比如网络之间需要协议ping一个网站是否通顺也需要协议广播地址也需要协议。我们甚至可以说键盘鼠标操作事件也需要协议Dubbo架构也需要协议沟通等等。

从狭义上说协议指的就是网络协议。比如在网络游戏中客户端和服务器端之间的内容交互就需要网络协议在Web网站中前端和后端的交互也需要协议再比如邮件服务的网络交互也需要协议的交互等等。可以说任何与网络相关的服务都少不了协议的支撑。

在游戏开发中,我们可以自定义一套自己的开发协议,也可以把现成的开发协议拿来使用。具体怎么做呢?我们先来看现在网上用得比较多的几种协议。

三种最流行的开发协议

XML

XML几乎是网络上最早出现的传输协议之一。在最早的Web开发中XML可以作为网络协议也可以用作配置文件。比如某些游戏或者某些应用的配置文件都可以使用XML来编写。

从人类的角度讲,它的可读性比较强,解析也比较方便。我们先来看几种解析方式。

解析方式是这些协议被程序理解的一种方式,按照这种方式解析,和我后面要说的自定义协议的解析和剖析结合起来,乃前后呼应之奇效。

  1. PULL方式PULL解析是一种专门为安卓设备解析XML文件设计的解析方式。这种解析方式更适用于移动设备。PULL解析和我们下面要说的SAX解析比较类似不一样的地方是PULL读取XML文件后触发相应的事件调用方法返回的是number。另外PULL方式可以控制程序的解析停止位置不需要全部解析可以随时停止。

  2. SAX方式SAXSimple API for XML采用事件驱动型方式。语法解析从发现元素开始、元素结束、文本、文档的开始或结束等就开始事件的发送程序员编写响应这些事件的代码就可以直接将数据保存下来。所以优点是不需要载入整个文档资源占用比较少。
    SAX解析器的代码比DOM解析器的代码更精简一般应用于Applet技术。缺点就是这种解析方式并不持久等事件消息过去后如果你没有保存数据那么数据就丢失了从事件的消息中我们只能得到文本数据但不知道这个文本属于哪个元素。但是我们只需XML文档的少量内容就可以解析出我们所需的数据所以耗费的内存更少。

  3. DOM方式DOMDocument Object Model是最传统的解析方式。解析器读入整个文档然后在内容中构建一个XML的树结构使用代码就可以操作这个树结构。优点是整个文档树在内存中便于操作而且支持删除、修改、重排等多种功能。缺点是将整个文档调入内存比较浪费计算机的时间和空间但是如果一旦解析了文档还需多次访问这些数据的话这种方式就可以起到作用了。

JSON

其实目前XML已经不太流行取而代之的是JSON。JSON是一种轻量级的数据交换格式。它用完全独立于编程语言的文本格式来存储和表示数据。

比之XML它看起来更加简洁和清晰层次结构分明JSON易于阅读和编写在程序方面也易于机器解析和生成同时也提升了网络传输效率。这些优点使得JSON很快在程序员中流行起来成为理想的数据交换语言。

它也是移动端比较常见的网络传输协议。相对于前面所说的XML格式它更为简单体积更小加之对网络流量和内存的需求更小所以JSON比XML更适合移动端的使用。

我们来看一下JSON的几种流行的解析程序库。

  1. Gson是谷歌开源的一种解析方法使用Java编写你可以通过提供的JAR文件包使用静态方法直接将JSON字符串解析成Java对象这样使用起来简单方便。

  2. FastJSON是阿里开源的一个解析JSON数据的类库。

  3. JSONObject也是一个解析JSON字符串的Java类。第二、第三这两种用的人都比较少我就不多介绍了。

当然支持别的语言的库也有很多由于JSON比较流行所以各种语言都有其支持的类库版本比如Python、C++、Ruby等等。

ProtoBuf

ProtoBuf全称Google Protocol Buffer 是谷歌公司开发的内部混合语言数据标准。目前正在使用的有接近五万种报文格式定义和超过一万两千多个.proto文件。它们都用于RPC系统和持续数据存储的系统。

这是一种轻便、高效的结构化数据存储格式可以用于结构化数据的序列化操作。它很适合用作数据存储或RPC数据交换格式。可以用于通讯协议、数据存储等领域。由于是独立的协议系统所以它和开发语言、运行平台都没有关系可以用在扩展的序列化结构数据格式。目前提供了 C++、Java、Python 、Ruby、Go等语言的开发接口API。

ProtoBuf方便的地方在于它有一款编译器可以将.proto后缀的协议文件编译成C++、Java、Python等语言的源代码。你可以直接看到和利用这些源代码且不需要自己去做解析所以不同语言之间使用ProtoBuf的协议规范都是一样的但是有一个问题是ProtoBuf存储的文件格式是二进制的由于是二进制的所以程序员需要调试其保存的内容就有点麻烦当然这可能只是对于某些人来说的瑕疵吧对于大部分人来讲方便性还是大于瑕疵的。

ProtoBuf的编码风格是这样的花括号的使用类似C/C++、Java。数据类型的命名方式使用驼峰命名比如DataType、NewObject。字段的变量小写并使用下划线连接类似GNU规范比如proto_buf、user_name。枚举类型使用大写并使用下划线连接比如MY_HOMEBEST_FRIEND。

Protobuf并不是针对大型数据设计的Protobuf对于1M以下的message有很高的效率但是当message大于1M的时候Protobuf的效率会逐步降低。

如何自己定义协议包?

我们讲完了三种目前最流行的开发协议,接下来我们要讲讲如何自己定义协议包。

我们所说的协议包,是在TCP和UDP传输之上的协议包也就是通过字符串的形式发送的协议包。这些协议包在客户端和服务器之间做了约定,也就是说,客户端和服务器都能通过拿到协议包来进行解包操作,并且进行一系列的逻辑运算并返回结果,当然结果也是协议包的形式发送出去。

一个好的协议不仅能节约网络带宽,也能让接收端快速拿到和解析需要的内容。设计协议包,必须保证安全性完整性

为了保证完整性,接收方需要知道协议的长度,或者知道协议的尾部在哪里。

我们可以给协议最末尾添加分隔符,该分隔符需要特殊字符。不能被传输的内容所混淆,又要能达到方便接收方辨认,因此,该特殊字符需要具有唯一性。比如我们可以将“!@#$”这四个字符做为分隔符,那么协议看起来可能是这样:

[协议头][协议体][协议结尾分隔符]

你可能要问了,在传输的过程中,我知道了协议长度,不需要协议头,只需要协议长度就可以?是的。因为有了协议长度,协议尾部有没有分隔符就不重要了。如果我们固定好输出协议长度的字节数,就可以忽略协议头。在这种情况下,协议看起来像是这样:

[协议长度2字节][协议体]

这样简单地就能定义整个协议的内容。

在读取的时候我们只需要读取开头的两个字节转换为一个short的长度或者四个字节一个int的长度在第三个字节开始就是协议体。让程序开始计算长度如果长度少于协议长度所定义的长度那就继续接收如果接收长度超过协议所定义的长度切割协议体并将下一段开始的协议存储到内存中留待下一次取出。这种方式是最方便的。

我们在保证协议完整性的同时也要保证协议不被破坏和篡改也就是所说的安全性。在这种情况下最直接的方式就可以将协议内容进行加密。比如SHA-256或者AES等等加密方式将内容加密随后传输过去最简单的做法就是将密码在客户端和服务器端协商好就可以了。

看起来可能是这个样子:

[协议长度2字节][加密协议体]

小结

这节内容差不多了,我们总结一下。我和你介绍了这几个内容。

  • 我介绍了三种的开发协议XML、JSON和ProtoBuf以及它们对应的解析方式。XML是网络上最早出现的传输协议之一。

  • 游戏或应用的配置文件都可以使用XML来编写但是目前XML已经不太流行取而代之的是JSON。

  • ProtoBuf适合用作数据存储或RPC数据交换格式缺点是保存比较麻烦但是总体来讲还是比较方便的。

  • 自己定义协议包需要考虑完整性和安全性。接收方需要知道协议的长度,或者知道协议的尾部在哪里,就可以保证协议包的完整性。而最直接的给协议包加密,就可以保证安全性。

最后,给你留一个小问题吧。

在自定义协议中,如果使用添加协议结尾的方式来做协议,如何才能保证协议结尾分割字符串不和协议本身的二进制内容重复?

欢迎留言说出你的看法。我在下一节的挑战中等你!