在一些需要序列化库的场景中,跨平台、高效率、低cpu占用等特性是很有必要的。例如,跨平台应用需要使用支持多平台的协议来保证在不同平台和系统的兼容性,游戏开发中客户端和服务端之间的信息传输需要高效以降低延迟,在一些科学计算场景中需要序列化和反序列化协议来减少计算开销。而谷歌的FlatBuffers库则能很好的支持这些应用场景。FlatBuffers是一个高效、跨平台的序列化库,适用于多种场景,特别是在需要高性能和低内存占用的场景中。
1. FlatBuffers的优点
1.1 快速的数据获取
正常我们获取经过序列化的数据还需要解析和反序列化,但是FlatBuffers通过记录字段的offset直接就能获取到数据,而不需要再经过解析和反序列化的过程。
1.2 内存的高速且高效利用
在将schema加载到缓冲区之后,通过传递指针就可以直接获取数据,而不需要额外的数据拷贝。因此也无需使用堆资源。
1.3 向前向后兼容
1.4 代码量小,依赖少
2. FlatBuffers的结构
2.1 schema解析
2.2 序列化
在序列化过程中,分别会对结构体、枚举和服务进行序列化,并为结构体和枚举分配索引,以确保它们在序列化过程中有唯一的标识。
- 序列化结构体
方法遍历structs_向量中的所有结构体,并调用每个结构体的Serialize方法,将其序列化为二进制格式。序列化后的偏移量被存储在object_offsets向量中,同时更新结构体的serialized_location属性。方法还收集了所有结构体的声明文件路径,并将其存储在files集合中。 - 序列化枚举
方法接下来遍历enums_向量中的所有枚举,并调用每个枚举的Serialize方法,将其序列化为二进制格式。序列化后的偏移量被存储在enum_offsets向量中。方法同样收集了所有枚举的声明文件路径,并将其存储在files集合中。 - 序列化服务
方法最后遍历services_向量中的所有服务,并调用每个服务的Serialize方法,将其序列化为二进制格式。序列化后的偏移量被存储在service_offsets向量中。方法还收集了所有服务的声明文件路径,并将其存储在files集合中。 - 创建Schema文件向量
在遍历和序列化所有结构体、枚举和服务之后,方法准备创建一个包含所有schema文件的向量。这个向量将包含所有序列化后的数据结构,便于后续的存储和传输。
2.3 反序列化
2.4 代码生成(cpp)
针对特定语言,相应的CodeGenerator会生成对应的代码。下面是具体的生成步骤:
- 检查64位FlatBuffer构建器: 调用MarkIf64BitBuilderIsNeeded函数,检查是否需要64位的FlatBuffer构建器。
- 清空代码缓冲区调用code_.Clear()清空代码缓冲区,并添加生成警告信息。
- 生成包含保护: 调用GenIncludeGuard函数生成包含保护宏,并将其添加到代码缓冲区中。
- 添加必要的头文件: 根据选项和解析器状态,添加必要的头文件,如flatbuffers.h和flexbuffers.h。
- 生成前向声明: 遍历所有结构体和表,生成前向声明,以处理可能的循环引用。
- 生成比较运算符: 如果选项启用了对象API和比较运算符生成,遍历所有结构体,生成相应的比较运算符声明。
- 生成迷你反射代码: 如果启用了迷你反射,遍历所有表和结构体,生成迷你反射的前置代码和完整代码。
- 生成枚举代码: 遍历所有枚举,调用GenEnum函数生成枚举代码。
- 生成结构体和表代码: 分别遍历所有固定和非固定的结构体和表,调用GenStruct和GenTable函数生成相应的代码。
- 生成联合类型验证器: 遍历所有联合类型,调用GenUnionPost函数生成联合类型验证器代码。
- 生成全局辅助函数: 如果存在根结构体,生成全局辅助函数,如根数据类型访问器、验证器等。
- 关闭包含保护: 添加包含保护的结束宏。
- 保存生成的代码: 调用SaveFile函数保存生成的代码文件,并根据需要生成嵌入的二进制schema代码。
3. FlatBuffers的使用
本节引入了一个简单的demo,分为writer端和read端。writer_monster.cpp负责读取example.fbs,并且写入字段,输出bin文件。而read_monster.cpp负责读取bin文件。
-----------compilation line------------------------------
#git clone
git clone https://github.com/google/flatbuffers.git
#flatbuffers compilation
cd flatbuffers
cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release
make
#c++ compilation, write_monster.cpp 和 read_monster.cpp 在下方列出
g++ write_monster.cpp -o write_monster -I include -L. -lflatbuffers
g++ read_monster.cpp -o read_monster -I include -L. -lflatbuffers
#run demo
./write_monster #input .fbs and generate c++ header and bin
./read_monster #include c++ header and read bin
--------------------write_monster.cpp-------------------------
#include <iostream>
#include <fstream>
#include "flatbuffers/flatbuffers.h"
#include "example_generated.h" // 生成的头文件
int main() {
flatbuffers::FlatBufferBuilder builder;
// 创建 Monster 数据
auto name = builder.CreateString("Orc");
std::vector<flatbuffers::Offset<flatbuffers::String>> inventory;
inventory.push_back(builder.CreateString("Sword"));
inventory.push_back(builder.CreateString("Shield"));
auto orc = MyGame::Sample::CreateMonster(builder, 1, name, builder.CreateVector(inventory));
// 完成构建
builder.Finish(orc);
// 将数据写入文件
std::ofstream ofs("monster.bin", std::ios::binary);
ofs.write(reinterpret_cast<const char *>(builder.GetBufferPointer()), builder.GetSize());
ofs.close();
std::cout << "Monster data saved to monster.bin" << std::endl;
return 0;
}
--------------------------read_monster.cpp------------------------
#include <iostream>
#include <fstream>
#include "flatbuffers/flatbuffers.h"
#include "example_generated.h" // 生成的头文件
int main() {
// 读取文件内容
std::ifstream ifs("monster.bin", std::ios::binary);
if (!ifs) {
std::cerr << "Failed to open monster.bin" << std::endl;
return 1;
}
// 获取文件大小
ifs.seekg(0, std::ios::end);
std::streamsize size = ifs.tellg();
ifs.seekg(0, std::ios::beg);
// 读取文件内容到缓冲区
std::vector<uint8_t> buffer(size);
if (!ifs.read(reinterpret_cast<char*>(buffer.data()), size)) {
std::cerr << "Failed to read monster.bin" << std::endl;
return 1;
}
// 解析 FlatBuffer
auto monster = MyGame::Sample::GetMonster(buffer.data());
// 访问数据
std::cout << "Monster ID: " << monster->id() << std::endl;
std::cout << "Monster Name: " << monster->name()->c_str() << std::endl;
// 访问库存
for (const auto& item : *monster->inventory()) {
std::cout << "Inventory Item: " << item->c_str() << std::endl;
}
return 0;
}
------------example.fbs---------------------------
namespace MyGame.Sample;
table Monster {
id: int;
name:string;
inventory: [string];
}
root_type Monster;
-------------example_generated.h------------------------------------
// automatically generated by the FlatBuffers compiler, do not modify
#ifndef FLATBUFFERS_GENERATED_EXAMPLE_MYGAME_SAMPLE_H_
#define FLATBUFFERS_GENERATED_EXAMPLE_MYGAME_SAMPLE_H_
#include "flatbuffers/flatbuffers.h"
// Ensure the included flatbuffers.h is the same version as when this file was
// generated, otherwise it may not be compatible.
static_assert(FLATBUFFERS_VERSION_MAJOR == 25 &&
FLATBUFFERS_VERSION_MINOR == 2 &&
FLATBUFFERS_VERSION_REVISION == 10,
"Non-compatible flatbuffers version included");
namespace MyGame {
namespace Sample {
struct Monster;
struct MonsterBuilder;
struct Monster FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
typedef MonsterBuilder Builder;
enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
VT_ID = 4,
VT_NAME = 6,
VT_INVENTORY = 8
};
int32_t id() const {
return GetField<int32_t>(VT_ID, 0);
}
const ::flatbuffers::String *name() const {
return GetPointer<const ::flatbuffers::String *>(VT_NAME);
}
const ::flatbuffers::Vector<::flatbuffers::Offset<::flatbuffers::String>> *inventory() const {
return GetPointer<const ::flatbuffers::Vector<::flatbuffers::Offset<::flatbuffers::String>> *>(VT_INVENTORY);
}
bool Verify(::flatbuffers::Verifier &verifier) const {
return VerifyTableStart(verifier) &&
VerifyField<int32_t>(verifier, VT_ID, 4) &&
VerifyOffset(verifier, VT_NAME) &&
verifier.VerifyString(name()) &&
VerifyOffset(verifier, VT_INVENTORY) &&
verifier.VerifyVector(inventory()) &&
verifier.VerifyVectorOfStrings(inventory()) &&
verifier.EndTable();
}
};
struct MonsterBuilder {
typedef Monster Table;
::flatbuffers::FlatBufferBuilder &fbb_;
::flatbuffers::uoffset_t start_;
void add_id(int32_t id) {
fbb_.AddElement<int32_t>(Monster::VT_ID, id, 0);
}
void add_name(::flatbuffers::Offset<::flatbuffers::String> name) {
fbb_.AddOffset(Monster::VT_NAME, name);
}
void add_inventory(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<::flatbuffers::String>>> inventory) {
fbb_.AddOffset(Monster::VT_INVENTORY, inventory);
}
explicit MonsterBuilder(::flatbuffers::FlatBufferBuilder &_fbb)
: fbb_(_fbb) {
start_ = fbb_.StartTable();
}
::flatbuffers::Offset<Monster> Finish() {
const auto end = fbb_.EndTable(start_);
auto o = ::flatbuffers::Offset<Monster>(end);
return o;
}
};
inline ::flatbuffers::Offset<Monster> CreateMonster(
::flatbuffers::FlatBufferBuilder &_fbb,
int32_t id = 0,
::flatbuffers::Offset<::flatbuffers::String> name = 0,
::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<::flatbuffers::String>>> inventory = 0) {
MonsterBuilder builder_(_fbb);
builder_.add_inventory(inventory);
builder_.add_name(name);
builder_.add_id(id);
return builder_.Finish();
}
inline ::flatbuffers::Offset<Monster> CreateMonsterDirect(
::flatbuffers::FlatBufferBuilder &_fbb,
int32_t id = 0,
const char *name = nullptr,
const std::vector<::flatbuffers::Offset<::flatbuffers::String>> *inventory = nullptr) {
auto name__ = name ? _fbb.CreateString(name) : 0;
auto inventory__ = inventory ? _fbb.CreateVector<::flatbuffers::Offset<::flatbuffers::String>>(*inventory) : 0;
return MyGame::Sample::CreateMonster(
_fbb,
id,
name__,
inventory__);
}
inline const MyGame::Sample::Monster *GetMonster(const void *buf) {
return ::flatbuffers::GetRoot<MyGame::Sample::Monster>(buf);
}
inline const MyGame::Sample::Monster *GetSizePrefixedMonster(const void *buf) {
return ::flatbuffers::GetSizePrefixedRoot<MyGame::Sample::Monster>(buf);
}
inline bool VerifyMonsterBuffer(
::flatbuffers::Verifier &verifier) {
return verifier.VerifyBuffer<MyGame::Sample::Monster>(nullptr);
}
inline bool VerifySizePrefixedMonsterBuffer(
::flatbuffers::Verifier &verifier) {
return verifier.VerifySizePrefixedBuffer<MyGame::Sample::Monster>(nullptr);
}
inline void FinishMonsterBuffer(
::flatbuffers::FlatBufferBuilder &fbb,
::flatbuffers::Offset<MyGame::Sample::Monster> root) {
fbb.Finish(root);
}
inline void FinishSizePrefixedMonsterBuffer(
::flatbuffers::FlatBufferBuilder &fbb,
::flatbuffers::Offset<MyGame::Sample::Monster> root) {
fbb.FinishSizePrefixed(root);
}
} // namespace Sample
} // namespace MyGame
#endif // FLATBUFFERS_GENERATED_EXAMPLE_MYGAME_SAMPLE_H_