FlatBuffers初识
文章目录
概述
FlatBuffers一开始是由Google开发用来支持性能高要求的游戏应用的一个开源的序列化工具库,由于其高效性、跨平台,同时支持多语言开发,而后经常被应用在各种传输性能高要求的互联网、物联网等应用场景。
本文仅为个人理解所作,详细请参考官方文档https://google.github.io/flatbuffers/index.html
为什么使用FlatBuffers
- 无需解析即可访问经序列化的数据:FlatBuffers是通过在一个扁平的buffer(一维数组)中存储二进制数据,数据无需经解析即可访问;
- 内存使用高效:访问序列化数据仅需序列化数据时所申请的buffer,经常性与共享内存IPC一起使用实现高效的性能需求;
- 灵活性:序列化数据"table“结构向前与向后同时兼容;
- 强类型:错误发生在编译期,无需在运行期通过代码断言;
- 引用方便:只需要添加一组头文件和一个自定义的生成头文件即可
- 跨平台
对标ProcotolBuffers、JSON
待学习,后补充
用法概述
- 自定义schema文件,定义序列化数据的数据结构(其中包括root对象,具体在schema章节展开);
- 通过FlatBuffers编译器
flatc
根据编译选项将schema文件生成相应开发语言的依赖文件,如C++的--cpp
,生成.h
头文件; - 使用
FlatBufferBuilder
类去构造一个二进制buffer,然后引用上一步生成的头文件中的API在buffer中序列化数据对象并存储; - 数据传输;
- 接收方通过调用相应API在buffer中获取到root对象的指针,通过类似于
object->field()
的方式直接访问数据;
编译构建
FlatBuffers官方提供了多种编译构建的方法,此处仅介绍基于Linux平台的CMake编译构建,其余请参考官方文档:
cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release
make
编译完成,工程根目录下生成flatc
可执行文件,即FlatBuffers编译器
定义数据结构
FlatBuffers在scheme文件使用IDL语言(Interface Definition Languange)自定义数据结构,从本节起我将由一个具体的FlatBuffers使用示例展开用法介绍:
schema示例文件:
//jocker.fbs
namespace StudySample;
table Weapon
{
name : string;
hurt : short;
}
table Corselet
{
name : string;
damage : short =100;
}
union Equiment
{
weapon : Weapon,
corselet : Corselet
}
struct Vec3
{
x : float;
y : float;
z : float;
}
table Jocker
{
name : string;
hp : short =100;
friends : bool =true;
equiment : Equiment;
weapons : [Weapon];
pos : Vec3;
path : [Vec3];
scope : [short];
}
root_type Jocker;
IDL 语法
- 定义: 变量 :类型 默认值 (可选字段);
- 由
root_type
指定序列化的根数据对象
数据类型概述
- 整型
- 字符串:string
- 布尔型
- 结构体
- 联合体
- 数组:由方括号
[]
定义数据类型,固定的内存存储消耗,相比于table内存占用低访问快 - table:FlatBuffers所提供的同时向前向后兼容的数据结构,可末尾字段增加字段,亦可通过关键字
deprecated
禁用字段
编译schema文件
以c++为例:./${FLATBUFFERS_DIR}/flatc --cpp ${EXAMPLE_DIR}/jocker.fbs
${FLATBUFFERS_DIR}
:FlatBuffers工程目录${EXAMPLE_DIR}
:示例工程目录
编译成功后会在当前目录下生成FlatBuffers序列化数据所依赖的.h
头文件
序列化与访问
示例文件:
#include "jocker.h"
using namespace StudySample;
uint8_t* BuildFlatbuffers(void)
{
flatbuffers::FlatBufferBuilder builder(1024);
auto sword_name = builder.CreateString("sword");
auto sword = CreateWeapon(builder, sword_name, 20);
auto axe_name = builder.CreateString("axe");
auto axe = CreateWeapon(builder, axe_name, 30);
auto cuff_name = builder.CreateString("icon-cuff");
auto cuff = CreateCorselet(builder, cuff_name, 30);
Vec3 position = {3.0f, 4.0f, 5.0f};
//Jocker buffer build
auto name = builder.CreateString("Yiff");
std::vector<flatbuffers::Offset<Weapon>> offset_weapons;
offset_weapons.push_back(sword);
offset_weapons.push_back(axe);
auto weapons = builder.CreateVector(offset_weapons);
std::vector<Vec3> vec_Vec3 = {Vec3(1.0f, 0.0f, 0.0f), Vec3(1.0f, 2.0f, 0.0f), Vec3(1.0f, 2.0f, 3.0f)};
auto path = builder.CreateVectorOfStructs(vec_Vec3);
short scope_array[] = {69, 75, 77};
auto scope = builder.CreateVector(scope_array, 3);
//Method1
// auto jocker = CreateJocker(builder, name, 100, true, Equiment_weapon, sword.Union(), weapons, &position, path, scope);
// builder.Finish(jocker);
//Method2
JockerBuilder jocker_builder(builder);
jocker_builder.add_name(name);
jocker_builder.add_hp(100);
jocker_builder.add_friends(true);
jocker_builder.add_equiment_type(Equiment_weapon);
jocker_builder.add_equiment(sword.Union());
jocker_builder.add_weapons(weapons);
jocker_builder.add_pos(&position);
jocker_builder.add_path(path);
jocker_builder.add_scope(scope);
auto jocker = jocker_builder.Finish();
builder.Finish(jocker);
return builder.GetBufferPointer();
}
void ParseFlatbuffers(uint8_t* pbuff)
{
auto jocker = GetJocker(pbuff);
std::cout << "Jocker name: " << jocker->name()->str() << std::endl;
std::cout << "Jocker hp: " << jocker->hp() << std::endl;
std::cout << "Jocker attr(1-friend, 0-enemy): " << jocker->friends() << std::endl;
std::cout << "Jocker equnimet: " << (static_cast<const Weapon*>(jocker->equiment()))->name()->str() << " and its data: " << (static_cast<const Weapon*>(jocker->equiment()))->hurt() << std::endl;
std::cout << "Jocker position: " << jocker->pos()->x() << "-" << jocker->pos()->y() << "-" << jocker->pos()->z() << std::endl;
std::cout << "Jocker path position2-y:" << jocker->path()->Get(1)->y() << std::endl;
std::cout << "Jocker scope2:" << jocker->scope()->Get(1) << std::endl;
}
上述展示了两种构建buffer的API:Method1与Method2
Object based API (C++)
由FlatBuffers官方针对C++编程语言特点而编写的一套API
- 编译选项
--gen-object-api
- 效率性能不如原生用法,但是序列化与反序列化适用C++ STL,构建buffer方便
mutable 变量
应用需要进行学习后再补充
实现原理
待了解补充
性能比较
待了解补充