当前位置:C++技术网 > 精选软件 > 云平台开发架构分析系列:6 数据包的处理逻辑分析3(数据包解析实现思路)

云平台开发架构分析系列:6 数据包的处理逻辑分析3(数据包解析实现思路)

更新时间:2017-06-22 09:23:32浏览次数:1+次

    在《云平台开发架构分析系列5:数据包的处理逻辑分析2(数据包拆包实现思路) 》一文中,我已经将数据包拆包的问题的实现思路做了详细的分析。本篇将对数据包协议的解析做一个分析,让你知道数据包协议解析的相关事情。数据包的处理也就分析到解析数据包,对于数据包的业务处理就因具体的业务而不同,没有必要分析了。而回复客户端数据包,也是封装数据包的过程,其实也是解析数据包的逆向过程,在本文会做一个介绍的。
    学习过计算机网络的同学应该很清楚各种协议的定义格式,我说的是格式,不是具体每一个字段的含义哦。各种协议都是按照字节顺序依次定义了各个字段的含义。我们不管是解析协议还是自己组装协议报文,也都是按照协议这样的各个字节的定义来的。不过组装协议数据包和解析还是稍有差别,我来单独说说。

1.组装协议数据包
    我们来看看TCP协议的定义图:
    TCP协议的定义图
    每一个段都是由一定的比特数组成的,源端口占了16比特,也就是2个字节。目的端口也是两个字节。后续的也都是差不多的,无非就是位数长短的问题。对于只需要一个位表示的,也就用一个字节将几个位都涵盖进去。TCP协议就是标准协议的做法,其他各种业务协议也都差不多。
    那么你要组装这样一个协议数据包,你会怎么做?你不会想到说先定义一个字节数组,然后挨个的赋值吧。这样效率太低了哦。我们一般的做法是将协议定义为一个结构体,然后每一个段定义为一个成员,封装一个协议数据包,也就是给一个结构体赋值。这样是不是就非常简单了。但是就仅此而已吗?
    对于结构体,我们需要知道的一点就是结构体内存对齐问题,请阅读文章《结构体对齐问题分析》和《关于成员对齐方式》和《关于结构体内存分配对齐深入理解》。那么我就不再对结构体内存对齐问题做赘述了。
    有了结构体的支撑,协议的组装和解析也就方便的多了。不过,还不够。我们需要增加类来对协议的各个关键的字段进行操作,方便对协议字段的修改和获取。这样我们就要对结构体进行包装。将按照协议定义的结构体作为类内基本数据结构,对外隐藏起来。然后开放公开访问接口。这样一来,既可以简化上层的业务开发,也可以将协议的具体格式和业务分离开来,降低耦合性。以后协议的改动不会对上层的业务处理产生影响。
    另外,协议都会分为协议头和协议数据。协议头就是开始标志、协议数据包总长度等之类的,协议不同这些协议头的定义也不同。不过协议头都是固定的,不管承载的协议内容是什么,协议头的长度和格式都是不会变的,变的只是协议头部分字段的值而已。对于固定的协议头部分,我们一般可以做一个基础的解析支持。因此我们可以将协议头当做是协议解析基类的内容处理。而协议数据则会根据不同的功能有不同的变化,我们则可以在协议解析基类的派生类中来实现。这样一来,可以复用基础类的功能,也让不同的协议内容的业务功能可以各自发挥。
    所以,我们可以提供不同的派生类对不同的协议数据进行包装,并提供不同的功能函数。所以最后我们再组装协议数据包时也就是传一些参数进去,调用对应的组装函数就可以完整协议的组装了。使用很简单,不过我们需要好好写好基础的功能代码。
    我们这里就给定这么多的实现思路,用结构体来定义协议各个字段,用基类支持协议头,用派生类来支持协议数据中具体的业务功能。
    拆包的方式和解析协议数据包的方式要协调一致。如果是整个数据包的拆包,那么最好就按整个数据包进行解析。如果是逐个字节式的拆包,那么就采用字节流的解析方式。这里只是建议,这样匹配的做法,会更加方便。

2.解析协议数据包
    我们掌握了协议的组装,再说解析协议数据包,自然也就好说多了。
    解析协议也就是将接收到的原始数据,拆分之后得到的单个协议的数据包进行分析,解析出业务内容。而解析的方法我们可以分为两种。因为实现方式不一样,各有千秋。
第一种:整体解析方式
    整体解析方式是将一个完整的协议数据包直接灌入协议解析类的存储协议数据的变量中。比如我们可以用一段缓存存放协议数据。我们在组装协议的时候,也是先创建一段内存,然后对各个内存进行赋值。为了提高效率,我们使用连续的内存块,所以都是事先分配好的。不过,协议数据包的长度如果不确定,那么只能动态分配。当需要更大内存时,就扩大内存,但无论如何,都始终保证是一块连续的内存。
    所以我们后续的解析也就可以很方便。当我们拆包的时候,都是按照整个数据包进行拆分,然后将整个数据包直接灌入这个缓存中。灌入之后,然后再调用校验函数进行各个字段的校验,如果合格自然就继续往后解析。而此时的解析就只是简单的调用各个函数了。
    这种方法一步到位,效率很高。而在组装的时候,也是通过参数确定需要的长度一次创建缓存内存完毕,然后只是进行内存的赋值操作而已。最多也就是将多段内存进行拼接,而无需一个个字节的拼接。

第二种:字节流解析方式
    字节流的解析方式是逐字节的按照协议的格式解析,一直解析到协议数据包的结尾。在解析的时候,会利用协议数据包的长度字段进行结尾的截取。而且在生成协议数据包缓存的时候,采用动态逐步生成的方式。协议数据包对象的数据是分散存储的。如果全部解析成功,那么协议数据包也就存储到位。如果解析失败,则停止协议数据包对象的构建。
    字节流的解析方式相当于将拆包和解析合二为一。因为在拆包的同时也就解析出来了数据包。一个数据包被解析完,也就拆出来一个新数据包。后续没有解析的,就还在待解析的缓存中。

    以上两种解析方法没有哪个好与不好的问题,看你个人的喜好。第一种简单直接,效率会高一些。第二种解析效率会低些,但是扩展性会更强些。最好的是将两者结合,吸取两种方式的优点。我个人采用了第一种方式,不过在后续的改进中,将会融入第二种方式的优点的。