FlatBuffers详解

1. 为什么使用FlatBuffers

使用FlatBuffers的原因很简单,那就是简单、效率高和便利。

为了传输数据,我们做了不少努力,研制出不少编解码方法,如:BER、PER、JSON、BSON、XML、HTML等。然而,不管使用何种方法,最终都是直接对数据进行操作,中间的编码和解码运算似乎是多余的。奥卡姆剃刀告诉我们:“如无必要,勿增实体”。在不增加实体的情况下,直接对数据进行操作,这就是FlatBuffers的开发目的。FlatBuffers仅仅增加了VTable和偏移量两个实体。

FlatBuffers很简单,使用起来一点都不难。首先定义好协议文件(schema

),然后使用工具编译成源码文件,然后就可以直接调用源码的接口来操作数据。

FlatBuffers的效率很高。FlatBuffers数据在缓冲区内都是平整的,可以直接访问。

FlatBuffers使用起来很便利。协议文件设计好后就可以发布。任何人拿到这个文件都可以编译成源码来对数据进行操作。这对团队开发很有利。另外使用FlatBuffers不用担心协议文件设计不周到的问题,因为你可以随意往Table里面添加或者删除成员。

2. 为什么不使用FlatBuffers

因为我不关心效率,所以我不使用FlatBuffers。但是能省点CPU资源也没什么不好,至少可以多跑几个服务。

FlatBuffers传输的数据较大,所以我不使用FlatBuffers。其实可以对数据进行压缩再传输。

因为目前开发的项目都使用XML和HTTP,所以我不使用FlatBuffers。建议能在新的模块中使用FlatBuffers。

因为打包后的FlatBuffers中的矢量数据不能随意修改,感觉不爽,所以不使用。对于一般的应用来说,打完包后就会立即发送,很少会修改数据的。

因为目前FlatBuffers的union类型不支持大于256个类型,然而项目中的union将包含超过256个类型的数据结构,所以我不使用FlatBuffers。对于这个问题,可以使用二级或者多级union来解决,即由多个union来实现,如二级union可以至少支持65536个类型。

FlatBuffers传输的数据没有JSON、XML和HTML那么直观,所以不想使用。对于这个问题,可以在收包程序进行LOG来弥补,可以通过查看LOG来获知传输的内容。另外,由于不是明文传输,所以一般的人,如果没有协议文件,即使截取到数据包还得需要花一定的力气才能解开,有利于保密。

FlatBuffers目前没有办法生成C源码,我们的项目使用的都是C源码。对于这个问题,我相信不久就会有版本能生成C源码。

 

3. FlatBuffers详解

FlatBuffers其实就是一个保存了一系列标量和矢量的缓冲区。这个缓冲区中的标量和矢量可以被直接访问。

缓冲区的数据一旦构造成功,里面的矢量数据一般不能变更,除非矢量的长度不大于构造时的长度,且矢量保存的不是偏移量,否则会产生错误。

 

3.1. 标量

所有的整形变量(8位~64位)和浮点变量均为标量。标量的特点是长度固定,字节序列为LittleEndian,这和大部分CPU的一样,以加快访问速度。

FlatBuffers中的偏移量也是标量,但是在构造后不能变更。

Struct结构也可以当做是标量来看待。

3.2. 矢量

字符串和数组是矢量。字符串是以'\0'结尾。矢量的开头必须是一个32位的长度,用来指明矢量的长度,这个长度不包括'\0'和长度本身所占的空间。

 

图 1

如图1所示,STRING和VECTOR都是矢量,唯一的区别是STRING包含一个'\0'结束符合。VECTOR SIZE保存的是VECTOR ELEMENTS的长度,单位是字节。

如果VECTOR ELEMENTS是标量或者STRUCT,那么其中保存的内容就是其数组中的内容;如果是TABLE,那么保存的就是一个偏移量数组,这些偏移量为32位,指向TABLE OBJECT。

3.3. 数据结构

3.3.1. Struct

Struct类型的数据结构是不可以更改的结构。当结构定义好,结构的成员、位置和大小就会固定,不能变更,否则所有的程序都要重新编译和升级。Struct的优点是访问速度快,占用内存少。

3.3.2. Table

Table类型可以随意增加和删除成员,是一个很灵活的类型。当要删除一个成员时,你把它指定为deprecated即可(这个成员必须保存在成员列表内,不能删除)。

 

图 2

如图2所示,一个VTABLE可以为多个TABLE OBJECT提供描述信息,每个TABLE OBJECT必须包含一个32位的TABLE OFFSET,指定哪个VTABLE描述它的字段信息。

每个VTABLE都有一个16位的VTABLE SIZE,用来记录本身的大小(包括VTABLE SIZE);TABLE OBJECT SIZE用来记录TABLE OBJECT的大小(包括VTABLE OFFSET这4个字节);TABLE FIELD OFFSET用来记录TABLE INSTANCE内的字段相对TABLE OBJECT的偏移量,即TABLE INSTANCE的第一个字段偏移量必须为4(因为VTABLE OFFSET为32位,占了4个字节)。

每个TABLE INSTANCE保存一系列字段FIELD,这些FIELD可以是普通标量或者struct,也可以是32位的偏移量,指向其它TABLE或者矢量。

3.3.3. ROOT TYPE

FlatBuffers有一个特殊类型就是ROOT TYPE,是一个顶级类型。

 

图 3

如图3所示,ROOT OFFSET是一个32位的偏移量,指向ROOT DATA;ROOT DATA为封包内容。FILE ID为可选内容,可以不设置,可以用作这个封包的ID,注意无'\0'结尾,可以在协议文件中使用file_identifier来指定。

ROOT DATA为整个封包的数据部分,然而ROOT OFFSET未必指向ROOT DATA的开头,它是指向顶层TABLE的TABLE OBJECT,而不是指向VTABLE。因此,即使不填FILE ID,ROOT DATA的值也可能不为0。

3.3.4. Union

联合类型是一个重要的类型,是封包分支的重要方法。

 

图4

如图4所示,每个union有两个部分组成,一个部分是TYPE,另外一个是TABLE偏移量。

因为TYPE只要8位,所以联合类型里面最多只能包含256个TABLE类型,否则会出问题。如果想支持大于256个类型,那么必须通过union+table+union这种方法来扩充,如:

// 第二层union

table T2_1_t { // 分支1

A1:int;

}

table T2_2_t { // 分支2

A2:int;

}

union U2_1_t  {

T2_1_t,

T2_2_t

}

table T2_3_t { // 分支3

A1:int;

}

table T2_4_t { // 分支4

A2:int;

}

union U2_2_t  {

T2_3_t,

T2_4_t

}

// 第一层union

table T1_1_t {

u:U2_1_t ( id: 1 ); // 注意,由于union的类型占一个id,所以这里是1,不是0;0已经默认分配给type了

}

table T1_2_t {

u:U2_2_t ( id: 1 ); // 注意,由于union的类型占一个id,所以这里是1,不是0;0已经默认分配给type了

}

union U1_1_t  { //可以至少支持多达256*256=65536个消息分支

T1_1_t,

T1_2_t

}

// 应用,这个Table可以至少保存多达256*256=65536种内容

table T_t {

u:U1_1_t ( id: 1 );// 注意,由于union的类型占一个id,所以这里是1,不是0;0已经默认分配给type了

}

 

4. 总结

FlatBuffers构造的数据总的来说是只要两种,即固定长度数据和可变长度数据,或者说是标量和矢量。可以认为struct也是标量,因为struct的大小也是固定的。在构造数据时,所有的标量都是直接写到缓冲区去的,所有的矢量需要先写到缓冲区,然后获得偏移量(32位),然后再把偏移量写到适当的结构中,每个矢量都离不开一个偏移量。

FlatBuffers和ProtoBuffers都是谷歌开发的开源产品。相对于ProtoBuffers已经在谷歌内部得到广泛的应用,FlatBuffers却是一个新生的事物。然而,FlatBuffers的反序列化速度是ProtoBuffers的百倍,相信将会得到越来越多的应用。

本文是在阅读FlatBuffers的源码和文档的基础上写出来的,如果有不正确的地方,请告知我

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值