序列化之FlatBuffers

7 分钟阅读

一、基本说明

  • 源码:FlatBuffers
  • 指南:FlatBuffers Programmer’s Guide
  • 结构定义文件为.fbs,注释使用//,可以使用include "my.fbs"嵌套包含文件
  • 可以理解为轻量级的protobuf,不会依赖library,但是编码会复杂一些
  • FlatBuffers的特点是先构造成员,再构造父结点;与protobuf相反

二、schema语法

// Example IDL file for our monster's schema.

namespace MySample;

enum Color:byte { Red = 0, Green, Blue = 2 }

union Equipment { Weapon } // Optionally add more tables.

struct Vec3 {
  x:float;
  y:float;
  z:float;
}

table Monster {
  pos:Vec3;
  mana:short = 150;
  hp:short = 100;
  name:string;
  friendly:bool = false (deprecated);
  inventory:[ubyte];
  color:Color = Blue;
  weapons:[Weapon];
  equipped:Equipment;
}

table Weapon {
  name:string;
  damage:short;
}

root_type Monster;

语法说明

标量类型

  • 8 bit: byte (int8), ubyte (uint8), bool
  • 16 bit: short (int16), ushort (uint16)
  • 32 bit: int (int32), uint (uint32), float (float32)
  • 64 bit: long (int64), ulong (uint64), double (float64)

非标量类型

  • Vector类型,使用[type]定义;不支持嵌套,比如[[type]],但可以再定义table内含vector
  • string,注意只能是UTF-8或 7-bit ASCII
  • 支持struct、enum、union等类型

默认值

  • 只有标量可以使用默认值
  • 使用=赋予默认值

root table

  • 使用root_type定义root table

file_identifier

  • 使用file_identifier "MYFI";定义标识,必须是4个字符
  • flatc加入-b参数后自动身材标识
  • 使用类似MonsterBufferHasIdentifier接口,检查是否存在标识

file_extension

  • flatc默认生成文件的后缀为.bin,使用file_extension "ext";定义后缀

属性字段

  • id:n 用于定义id,从0开始,为了兼容性,建议每个字段用id
  • deprecated 表示停止使用字段,老data仍然存在该字段,但不会生成访问的代码
  • required 必须填充的字段,默认是optional字段

生成代码

  • 使用flatc --cpp monster.fbs生成头文件,比如monster_generate.h
  • 如果需要生成带可修改功能,则使用参数--gen-mutable

三、序列化

原则:必须先构建成员,再构建父结点

构建 string

flatbuffers::FlatBufferBuilder fbb;
//注意:sword的类型为flatbuffers::Offset<flatbuffers::String>
auto sword = fbb.CreateString("Sword");

构建 table

flatc会为定义的table生成create方法和build方法,注意table的类和方法都是命令空间MySample下。

方式一、使用create方法如下:

// 注意Weapon和CreateWeapon都是在命令空间MySample下
flatbuffers::Offset<Weapon> CreateWeapon(
    flatbuffers::FlatBufferBuilder &_fbb,
    flatbuffers::Offset<flatbuffers::String> name = 0,
    int16_t damage = 0);

// 调用方法如下:
auto wp1 = MySample::CreateWeapon(fbb, sword, 10);

方式二、使用build方法如下:

// 注意使用该方法时在`wp_builder.Finish()`之前不能调用任何`fbb.Create`方法
WeaponBuilder wp_builder(fbb);
wp_builder.add_name(sword);
wp_builder.add_damage(10);
auto wp1 = wp_builder.Finish();

构建 vector

所有用[]定义的类型,都需要这种方法构建:

flatbuffers::FlatBufferBuilder fbb;
std::vector<flatbuffers::Offset<Weapon>> weapon_vector;
weapon_vector.push_back(wp1);
names.push_back(...);
//注意: weapons_offse的类型为flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Weapon>>>
auto weapons_offset = fbb.CreateVector(weapon_vector);

构建完成

auto orc = CreateMonster(builder, ......);
builder.Finish(orc);
//构建后的buffer信息: builder.GetBufferPointer() + builder.GetSize()
//释放buffer: builder.ReleaseBufferPointer();

四、反序列化

//其中buffer是序列化后的buffer
auto monster = GetMonster(buffer);
// monster->hp() == 80
// monster->name()->str() == "MyMonster"
auto weps = monster->weapons();
for (unsigned int i = 0; i < weps->size(); i++) {
  assert(weps->Get(i)->name()->str() == expected_weapon_names[i]);
  assert(weps->Get(i)->damage() == expected_weapon_damages[i]);
}

//GetBufferStartFromRootPointer(monster) == buffer_pointer

五、序列化转JSON

以上所有功能只需要包含flatbuffers/include头文件。但是到了转JSON的功能,就需要包含flatbuffers/src下的code_generators.cppidl_gen_text.cppidl_parser.cpputil.cpp等4个源文件。编码时,头文件需要另外包含flatbuffers/idl.hflatbuffers/util.h

加载schema文件

方式一、用脚本将fbs文件生成buffer

#!/bin/bash

MONSTER_HEADER="schema_monster.h"
MONSTER_FBS="monster.fbs"

function generate_text()
{
cat<<EOF >"$MONSTER_HEADER"
#include <string>
const std::string schema_text = "\\
EOF
cat $MONSTER_FBS | while read line; do
    echo "$line\\n\\">> $MONSTER_HEADER
done
echo "\";" >> $MONSTER_HEADER
}

generate_text

这样就生成了schema_text

方式二、加载fbs文件到buffer中

std::string schema_text;
ASSERT(true == flatbuffers::LoadFile("monster_test.fbs",false, &schema_text));

生成json

std::string json_text;
flatbuffers::Parser parser;
ASSERT(true == parser.Parse(schema_text.c_str()));
// 显示默认值
// parser.opts.output_default_scalars_in_json = true;
// buffer对应的是序列号的buffer,json形式保存在json_text中
ASSERT(true == flatbuffers::GenerateText(parser, buffer, &json_text);

// GenerateTextFile直接存到文件中
// bool GenerateTextFile(const Parser &parser, const std::string &path, const std::string &file_name)