ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

FlatBuffers C++ 语言使用指南:从构建、序列化到 gRPC 的完整实战

FlatBuffers C++ 语言使用指南:从构建、序列化到 gRPC 的完整实战 FlatBuffers C 语言使用指南从构建、序列化到 gRPC 的完整实战【免费下载链接】flatbuffersFlatBuffers: Memory Efficient Serialization Library项目地址: https://gitcode.com/GitHub_Trending/fl/flatbuffers导读本文以 FlatBuffers 官方 C 语言指南docs/source/languages/cpp.md为核心骨架结合本仓库flatbuffers 开源项目中的源码、示例与测试文件系统讲解在 C 中读写 FlatBuffers 的全部要点从 schema 编译、基础读取、Object Based API、指针与字符串类型定制、反射与迷你反射、字典模拟、直接内存访问、不可信缓冲区校验、文本/JSON 解析到高级 union 特性、线程模型、浮点与 locale 兼容性以及基于 gRPC 的端到端通信。读完本文你将掌握在 C 项目中完整落地 FlatBuffers 的实战方案并能定位到仓库中对应的实现与示例代码做进一步验证。开始之前前置条件与编译生成在深入 C 特有的用法之前需要说明docs/source/tutorial.md提供了面向所有支持语言的通用 FlatBuffers 使用教程包含 C而本页聚焦于 C 专属的细节与坑点。使用 C 版本的前提是已经编写好一个 FlatBuffers schema 文件例如mygame.fbs扩展名不重要已经用 Schema 编译器flatc编译出 C 头文件例如执行flatc -c mygame.fbs会生成mygame_generated.h编译与链接时flatbuffers/include目录需要在头文件搜索路径中因为生成的头文件依赖flatbuffers/flatbuffers.h。关于flatc的完整用法参见 使用 Schema 编译器schema 语法参见 编写 Schema构建整个项目参见 构建文档。FlatBuffers C 库代码位置与测试库代码位置C 运行时库的源码位于include/flatbuffers/目录核心头文件包括flatbuffers.h主入口头文件包含 Builder、Table、Vector 等核心类型flatbuffer_builder.hFlatBufferBuilder实现序列化核心verifier.h缓冲区校验器详见下文访问不可信缓冲区reflection.h与reflection_generated.h反射支持minireflect.h迷你反射支持idl.hschema/JSON 文本解析Parsergrpc.hgRPC 集成。测试代码与运行测试代码位于tests/目录其中主测试文件为 tests/test.cpp。该测试文件与flatc一起构建构建方法参见 构建文档。构建完成后在仓库根目录flatbuffers/下运行测试程序flattests。在 Linux 上直接执行./flattests使用 FlatBuffers C 库读取与访问FlatBuffers 的 C 支持同时具备读取read与写入write能力。使用流程是先用flatc的--cpp选项从 schema 生成 C 类然后在代码中同时包含 FlatBuffers 运行时与生成代码。读取一个 FlatBuffer 二进制文件下面的示例演示如何读取一个.mon二进制文件并访问其中的字段#include flatbuffers/flatbuffers.h #include monster_test_generated.h #include iostream // C header file for printing #include fstream // C header file for file access std::ifstream infile; infile.open(monsterdata_test.mon, std::ios::binary | std::ios::in); infile.seekg(0, std::ios::end); int length infile.tellg(); infile.seekg(0, std::ios::beg); char *data new char[length]; infile.read(data, length); infile.close(); auto monster GetMonster(data);这里monster的类型是Monster *它指向缓冲区内部注意根对象指针并不等于buffer_pointer。这正是 FlatBuffers 的设计核心——读取零拷贝、零解析直接通过偏移量访问内存。生成的头部文件为每个字段提供了便捷访问器accessor例如hp()、mana()std::cout hp : monster-hp() std::endl; // 80 std::cout mana : monster-mana() std::endl; // default value of 150 std::cout name : monster-name()-c_str() std::endl; // MyMonster注意上面从未存储过mana值因此它会返回 schema 中定义的默认值FlatBuffers 对等于默认值的字段不落盘读取时直接返回默认值。shared属性字符串池化以下属性被支持作用于字段shared字段级作用于字符串字段时将字符串池化string pooling作为默认序列化行为。具体而言CreateXxxDirect系列函数以及 Object Based API 的Pack函数会改用CreateSharedString来创建字符串。CreateSharedString的实现位于 include/flatbuffers/flatbuffer_builder.h它会复用已序列化的相同字符串的偏移量从而减少重复字符串占用的内存。注意字符串池只在单个 Builder 生命周期内生效该函数实现中维护了string_pool相关的去重逻辑。Object Based API便捷的对象模型FlatBuffers 的一切设计都围绕内存效率因此其基础 API 要求前序构造pre-order construction且修改mutation困难。当效率不是首要考量时可以通过--gen-object-api生成更便捷的 Object Based API它能够把 FlatBuffer 解包unpack/打包pack成普通对象与标准 STL 容器从而获得便捷的构造、访问与修改能力。使用方式// Autogenerated class from table Monster. MonsterT monsterobj; // Deserialize from buffer into object. GetMonster(flatbuffer)-UnPackTo(monsterobj); // Update object directly like a C class instance. cout monsterobj.name; // This is now a std::string! monsterobj.name Bob; // Change the name. // Serialize into new flatbuffer. FlatBufferBuilder fbb; fbb.Finish(Monster::Pack(fbb, monsterobj));所有生成的对象类型如MonsterT都继承自flatbuffers::NativeTable内部字段用std::string、std::vector、std::unique_ptr等标准容器表达可直接像普通 C 类一样使用。Object Based API 专属属性以下属性专用于 Object Based API 的代码生成native_inline字段级由于 FlatBuffer 的 table 与 struct 在给定缓冲区中是可选的可能为 null它们在原生类NativeTable中最适合用指针具体为std::unique_ptr表达。该属性将成员声明改为直接使用该类型本身而不是包在unique_ptr中。native_default(value)字段级对声明为native_inline的成员该属性指定的值会原样写入该成员在类构造函数初始化列表中的初始化表达式。native_custom_alloc(custom_allocator)table 或 struct 级使用 Object Based API 时所有在 unpack 过程中分配的 NativeTable 都会使用指定的自定义分配器该分配器同样作用于native_custom_alloc声明的 table 中出现的任何std::vector。典型用途是从内存池分配从而加快 Object Based API 的解包速度。最小示例——schematable mytable(native_custom_alloc:custom_allocator) { ... }custom_allocator必须在包含flatbuffers.h之前定义template typename T struct custom_allocator : public std::allocatorT { typedef T *pointer; template class U struct rebind { typedef custom_allocatorU other; }; pointer allocate(const std::size_t n) { return std::allocatorT::allocate(n); } void deallocate(T* ptr, std::size_t n) { return std::allocatorT::deallocate(ptr,n); } custom_allocator() throw() {} template class U custom_allocator(const custom_allocatorU) throw() {} };native_type(type)struct 级某些情况下对某个 struct 存在更合适的 C 数据类型。例如 schemastruct Vec2 { x: float; y: float; }默认生成的 Object Based API 类是struct Vec2T : flatbuffers::NativeTable { float x; float y; };但有时使用用户自定义的 C 类型更有用因为它可以提供更多功能例如struct vector2 { float x 0, y 0; vector2 operator(vector2 rhs) const { ... } vector2 operator-(vector2 rhs) const { ... } float length() const { ... } // etc. };native_type属性会用给定类型替换生成类的使用。以上例继续所有 Object Based API 生成代码中Vec2T的位置都会换成vector2。然而由于native_type对 flatbuffers 而言是未知类型用户必须提供以下函数来辅助序列化namespace flatbuffers { Vec2 Pack(const vector2 obj); vector2 UnPack(const Vec2 obj); }native_type_pack_name(name)struct 级需与native_type同时指定当你想多次复用同一个native_type例如使用不同精度时必须让 Pack/UnPack 函数名唯一否则会产生编译错误。该属性会向期望的 Pack/UnPack 函数名追加一个名称。所以在上面的例子中指定native_type_pack_name(Vec2)后你需要实现的是namespace flatbuffers { Vec2 PackVec2(const vector2 obj); vector2 UnPackVec2(const Vec2 obj); }native_type(type)table 级table 同样可以映射为原生类型。例如 schematable Matrix (native_type: NativeMatrix) { rows: int32; columns: int32; values: [float]; }会对应一个用户自定义的 C 类class NativeMatrix { ... }此时编译器会生成以下函数声明用户必须提供并链接对应的函数定义struct Matrix FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { // ... static ::flatbuffers::OffsetMatrix Pack( ::flatbuffers::FlatBufferBuilder _fbb, const NativeMatrix* _o, const ::flatbuffers::rehasher_function_t* _rehasher nullptr); void UnPackTo( NativeMatrix* _o, const ::flatbuffers::resolver_function_t* _resolver nullptr) const; }最后还有两个文件级/顶层属性native_include(path)文件级由于native_type可能引入 flatbuffers 未知的类型生成代码可能需要包含外部头文件。该属性会在生成代码顶部直接添加一条#include指令包含指定路径。force_align注意该属性在 Object API 中可能不被尊重具体取决于与new一起使用的分配器的对齐方式。仓库中可参考的实际用法见 tests/native_type_test.fbs 与其生成头文件 tests/native_type_test_generated.h、实现 tests/native_type_test_impl.h。外部引用External referencesObject API 还有一个额外能力允许加载多个相互独立的 FlatBuffer并让它们通过哈希互相引用对方的对象在 Object API 中表现为类型化指针。实现方式在被引用的对象中有一个字段使用了字符串哈希特性见 schema 文档 中的hash属性在引用它的字段上也放一个同样的哈希同时用cpp_type属性指定要引用的 C 类型可以是任意 C 类型会追加一个*在 JSON 或以其他方式创建这些缓冲区时确保它们使用相同的字符串或哈希调用UnPack或Create时需要一个把哈希映射到对象的函数详见resolver_function_t与rehasher_function_t类型定义。使用不同的指针类型默认情况下对象树由std::unique_ptr构成但你可以全局通过flatc的--cpp-ptr-type参数或按字段通过cpp_ptr_type属性改为任意智能指针类型my_ptrT或指定naked来获得裸指针T *。与智能指针不同裸指针不替你管理内存生命周期需要自行维护。若某个 FlatBuffer 字段想引用--cpp-ptr-type指定的指针类型则把该字段的cpp_ptr_type属性设为default_ptr_type。使用不同的字符串类型默认情况下对象树使用std::string可全局--cpp-str-type参数或按字段cpp_str_type属性替换。自定义字符串类型必须支持成员函数T::c_str()、T::length()和T::empty()。此外该类型必须能从std::string构造因为默认实现是先构造一个std::string再用来初始化自定义字符串类型——这种方式妨碍自定义字符串类型的零拷贝高效构造。--cpp-str-flex-ctor参数或字段级属性cpp_str_flex_ctor可以改变这一行为改为直接以 FlatBuffers String 的指针与长度来构造自定义字符串类型此时自定义类需要如下格式的构造函数custom_str_class(const char *, size_t);请注意该字符数组不保证以 NULL 结尾判断字符串末尾应始终使用传入的 size。反射Reflection与原地扩容FlatBuffers 提供实验性的反射支持即使不知道缓冲区确切格式也能读取和写入数据甚至能原地修改字符串和向量的长度resizing。其实现方式非常精妙存在一个描述 schema 的 schema元 schema即 reflection/reflection.fbs。编译器flatc可以把任何它解析过的 schema 以二进制 FlatBuffer 形式写出对应这个元 schema生成.bfbs文件。例如仓库中的 tests/monster_test.bfbs、tests/arrays_test.bfbs。运行时加载一个这样的二进制 schema即可遍历与之对应的任意 FlatBuffer 数据而无需预先知道其确切格式可以查询存在哪些字段然后进行读写。便捷的字段操作可包含头文件flatbuffers/reflection.h它同时包含了元 schema 的生成代码与大量辅助函数如类型判断IsScalar/IsInteger/IsFloat、类型大小查询GetTypeSize等见 include/flatbuffers/reflection.h。目前的使用示例可参考tests/test.cpp中的ReflectionTest()。迷你反射Mini Reflection还有一种更受限的反射形式可直接内联进生成代码完全不做二进制schema 访问。它设计目标是把反射开销压到最低大约为每个字段向可执行文件增加 2–6 字节但不会包含二进制schema 拥有的全部信息。通过--reflect-types若还想要字段/枚举名称则改用--reflect-names把这些信息加入生成代码。之后即可用这些信息把 FlatBuffer 打印成文本例如auto s flatbuffers::FlatBufferToString(flatbuf, MonsterTypeTable());MonsterTypeTable()为每个类型在生成代码中声明。产出的字符串与基于Parser的文本生成器输出的 JSON 非常相似。该功能需要flatbuffers/minireflect.h其中还包含便捷的 visitor/iterator使你无需了解 FlatBuffers 或 reflection 编码即可基于迷你反射表编写自己的输出/功能。在 FlatBuffer 中存储 map / 字典FlatBuffers 原生不支持 map但可以通过向量 二分查找模拟其行为无需把数据解包成std::map之类就能直接从 FlatBuffer 进行快速查找。使用方法把 table 中的某个字段标记为 key 字段为该字段设置key属性例如name:string (key)。一个 table 只能有一个 key 字段且必须是字符串或标量类型。照常写出该类型的 table并把它们的 offset 收集到数组或向量中。不要调用CreateVector改为调用CreateVectorOfSortedTables见 include/flatbuffers/flatbuffer_builder.h它会先对所有 offset 排序使它们指向的 table 按 key 字段有序再序列化。访问时用Vector::LookupByKey代替Vector::Get例如myvector-LookupByKey(Fred)返回指向对应 table 类型的指针找不到时返回nullptr。LookupByKey的实现位于 include/flatbuffers/vector.h内部执行二分查找因此速度与std::map相当且可能因缓存友好而更快。注意LookupByKey只在向量已排序时有效未排序时很可能找不到元素。直接内存访问从上面的示例可见缓冲区中的所有元素都通过生成的访问器访问。原因有二一是所有平台上的数据都以小端little endian格式存储访问器在大端机器上会执行字节交换二是布局通常对用户不可知。但对于 struct其布局是确定性的且跨平台保证一致标量按自身大小对齐struct 整体对齐到其最大成员因此允许直接用sizeof()和memcpy访问指向 struct甚至 struct 数组的内存。要计算 struct 子元素的偏移请确保子元素本身也是 struct这样就能用指针计算出偏移而无需硬编码——这对配合 OpenGL 的glVertexAttribPointer之类 API 使用 struct 数组非常有用。必须注意struct 在所有机器上仍然是小端因此只有能保证不在大端机器上运行时才可使用这类技巧加上assert(FLATBUFFERS_LITTLEENDIAN)是明智之举。访问不可信的缓冲区Verifier 校验生成的访问器通过偏移量访问字段速度很快但这些偏移量在运行时不做校验畸形缓冲区可能导致程序访问随机内存而崩溃。处理已知来源的大量数据例如自己生成在磁盘上的数据时这可以接受但读取可能被攻击者篡改的网络数据时这就不合适了。为此可以在访问数据前使用缓冲区校验器verifier它检查所有偏移量、所有字段大小以及字符串的 NULL 结尾确保访问缓冲区时所有读取都落在缓冲区内部。每个根类型都会生成对应的校验函数例如对MonsterVerifier verifier(buf, len); bool ok VerifyMonsterBuffer(verifier);若ok为 true则缓冲区可以安全读取。除不可信数据外此函数也可在调试模式下调用作为对数据在传输过程中被破坏的额外保险。校验并非免费但它通常比完整遍历更快标量数据实际上不会被触碰而且校验可能把缓冲区提前带入缓存实际开销可能比预期更低。在可能遭受拒绝服务攻击DoS的特殊场景verifier 还有两个额外的构造参数用于限制嵌套深度与 table 总数上限超过即判定缓冲区畸形。默认值为Verifier(buf, len, 64 /* max depth */, 1000000 /* max tables */)对大多数使用场景足够。在源码 include/flatbuffers/verifier.h 中可以看到Verifier的Options结构还支持更多细粒度配置check_alignment是否校验对齐默认 true、check_nested_flatbuffers是否校验嵌套 flatbuffer默认 true、max_size缓冲区最大尺寸默认FLATBUFFERS_MAX_BUFFER_SIZE等。文本与 Schema 解析使用二进制缓冲区配合生成头文件是开销最低的用法但有些场景需要文本格式例如与源码版本控制配合更好或想给用户提供易访问的数据也可能你已经有一批 JSON 格式数据或某个工具直接产出 JSON只要能为它写出 schema就能轻松直接使用这些数据。JSON 格式的 schema 演进兼容性遵循与二进制格式相同的规则兼容演进的 schema 下JSON 格式数据保持前向/后向兼容。文本格式有两种用法方式一把编译器当作转换工具这是推荐路径无需在程序中新增任何代码且由于可以随产品发布二进制数据而效率最大化。缺点是用户/开发者需要额外执行一步不过可以自动化flatc -b myschema.fbs mydata.json这会生成二进制文件mydata_wire.bin可按前述方式加载。方式二让程序直接加载文本这给予最大灵活性甚至可以同时支持两者先检查两种文件需要时从文本重新生成二进制否则直接加载二进制。此选项目前仅 C 可用Java 可通过 JNI 使用。如构建章节所述此技术需要额外链接几个文件到程序中并包含flatbuffers/idl.h。先把文本schema 或 json加载进内存缓冲区可用 include/flatbuffers/util.h 中便捷的LoadFile()工具函数然后构造 parserflatbuffers::Parser parser;接着按顺序解析任意数量的文本文件parser.Parse(text_file.c_str());这与命令行编译器的工作方式类似由同一个Parser对象按顺序解析的文件后面的文件可以引用前面文件中的定义。典型流程是先加载 schema 文件把定义填充进Parser再加载一个或多个 JSON 文件。Parse的可选参数是一个以 NULL 结尾的 include 路径列表若不指定任何 include 语句都尝试从当前目录解析。若有解析错误Parse返回falseParser::error_中包含带行号的人类可读错误字符串应展示给该文件的作者。每个 JSON 文件解析完后Parser::fbb成员变量就是包含该文件二进制缓冲区版本的FlatBufferBuilder可按前述方式访问。examples/samples/sample_text.cpp 演示了上述全部操作用LoadFile加载samples/monster.fbs与samples/monsterdata.json先后Parse二者再用GenText从生成的二进制还原 JSON 并比对验证。线程安全模型读取FlatBuffer 不会触碰原始缓冲区之外的任何内存且完全是只读全 const因此即使没有任何同步原语也可以安全地从多个线程并发访问。创建FlatBuffer 则不是线程安全的构建相关的全部状态都包含在一个FlatBufferBuilder实例中不触碰其外部的内存。要做到线程安全要么不在线程间共享FlatBufferBuilder实例推荐要么手动用同步原语包装。按设计没有自动化的方式——因为多线程构造单个缓冲区的情况罕见同步开销却很高。高级 union 特性C 实现目前支持 union 向量即字段可声明为[T]其中T是 union 类型而非 table 类型也支持 union 中出现 struct 和 string除了 table 之外。示例见 tests/union_vector/union_vector.fbs其中union Character同时包含 tableAttacker、BookReader、structRapunzel与 stringOther、Unusedunion Gadget则完全是 struct 成员FallingTub、HandFanMovie表中既有普通 union 字段main_character也有 union 向量characters: [Character]。对应测试为tests/test.cpp中的UnionVectorTest。由于这些特性尚未移植到其他语言一旦使用这些缓冲区将无法在其他语言中使用flatc会拒绝编译使用这些特性的 schema。这些特性减少了之前使用 union 所需的table 包裹量。要使用标量只需把它们包进一个 struct 即可。嵌套对象深度限制与栈溢出控制FlatBuffers 的 schema 或 json 解析器是一种递归解析器。为避免栈溢出解析器内置了递归深度限制schema 中的嵌套声明数量或 json 中嵌套对象的数量都有限制。默认深度限制为64。可通过定义FLATBUFFERS_MAX_PARSING_DEPTH覆盖此限制该定义对测试或嵌入式应用场景很有帮助。详情参见 构建文档 中基于 CMake 的构建说明。对 C-locale 的依赖FlatBuffers 文法 使用 ASCII 字符集表示标识符、字母数字字面量与保留字。内部实现依赖受 C-locale 影响的函数例如strtod()或strtof()。库期望用点号.作为浮点数整数部分与小数部分的分隔符其他分隔符如,会破坏兼容性解析 schema 或 json 文件时可能报错。标准 C locale 是全局资源整个应用只有一个 locale。部分现代编译器与平台提供 locale 无关或 locale 收窄的函数strtof_l、strtod_l、strtoll_l、strtoull_l以解决此依赖这些函数使用指定 locale 而非全局或线程 locale。它们是 POSIX-2008 的一部分但不属于 C/C 标准库因此某些平台上可能缺失。FlatBuffers 库会在配置期和编译期尝试检测这些函数CMake 的 CMakeLists.txt检查stdlib.h中是否存在strtol_l和strtod_l编译期 include/flatbuffers/base.h_MSC_VER 1900表示 MSVC2012 或更高版本用 MSVC 构建时_XOPEN_SOURCE700表示 POSIX-2008用 GCC/Clang 构建时。检测后宏FLATBUFFERS_LOCALE_INDEPENDENT会被置为0或1。要覆盖或停止检测可用 CMake 的-DFLATBUFFERS_LOCALE_INDEPENDENT{0|1}或预定义FLATBUFFERS_LOCALE_INDEPENDENT符号。用环境变量FLATBUFFERS_TEST_LOCALE可测试库与特定 locale 的兼容性FLATBUFFERS_TEST_LOCALE ./flattests FLATBUFFERS_TEST_LOCALEru_RU.CP1251 ./flattests浮点数支持FlatBuffers 库假设 C 编译器与 CPU 兼容IEEE-754浮点标准。若启用fast-math或/fp:fast模式schema 与 json 解析器可能失败。十六进制与特殊浮点字面量根据 文法fbs与json文件可使用十六进制与特殊NaN、Inf浮点字面量。FlatBuffers 使用strtof和strtod解析浮点字面量并有代码检测编译器对这些字面量的兼容性。条件满足时预处理常量FLATBUFFERS_HAS_NEW_STRTOD会被置为1。若该常量小于1浮点字面量支持将在编译期受限——此时包含十六进制或特殊字面量的 schema 无法使用。NaN 值的比较NaNNot a Number是表示未定义或不可表示值的特殊浮点值可显式赋给变量通常表示缺失值也可能是数学运算的结果。IEEE-754定义了两类NaN静默 NaNqNaNsQuiet NaNs信号 NaNsNaNsSignaling NaNs。根据IEEE-754与NaN比较永远返回无序结果即使与自身比较也一样。因此含一个或多个NaN的整个 FlatBuffers 对象将不等于自身。FlatBuffers 中等于默认值的标量字段实际上不存储于序列化数据中而是在代码中生成见 编写 Schema。含NaN默认值的标量字段会破坏这一行为。若 schema 中有大量NaN默认值FlatBuffers 可以把无序比较覆盖为有序比较(NaNNaN)-true。编译程序时定义符号FLATBUFFERS_NAN_DEFAULTS即可启用该有序比较。FLATBUFFERS_NAN_DEFAULTS引入的额外计算在 GCC 或 Clang 下非常廉价——这些编译器对isnan检查有编译期实现而 MSVC 没有。使用 gRPC开始之前使用 C 的 FlatBuffers gRPC 之前应熟悉FlatBuffers 序列化格式本身以及 gRPC 的用法。使用 FlatBuffers gRPC C 库下文示例即 grpc/samples/greeter 目录中的完整代码。首先看 schema grpc/samples/greeter/greeter.fbstable HelloReply { message:string; } table HelloRequest { name:string; } table ManyHellosRequest { name:string; num_greetings:int; } rpc_service Greeter { SayHello(HelloRequest):HelloReply; SayManyHellos(ManyHellosRequest):HelloReply (streaming: server); }运行flatc时传入--grpc选项会额外生成greeter.grpc.fb.h与greeter.grpc.fb.cc以及普通的greeter_generated.h。服务端代码 grpc/samples/greeter/server.cpp#include grpcpp/grpcpp.h #include iostream #include memory #include string #include greeter.grpc.fb.h #include greeter_generated.h class GreeterServiceImpl final : public Greeter::Service { virtual grpc::Status SayHello( grpc::ServerContext* context, const flatbuffers::grpc::MessageHelloRequest* request_msg, flatbuffers::grpc::MessageHelloReply* response_msg) override { flatbuffers::grpc::MessageBuilder mb_; // We call GetRoot to parse the message. Verification is already // performed by default. const HelloRequest* request request_msg-GetRoot(); // Fields are retrieved as usual with FlatBuffers const std::string name request-name()-str(); // flatbuffers::grpc::MessageBuilder is a FlatBufferBuilder with a // special allocator for efficient gRPC buffer transfer, but otherwise // usage is the same as usual. auto msg_offset mb_.CreateString(Hello, name); auto hello_offset CreateHelloReply(mb_, msg_offset); mb_.Finish(hello_offset); // ReleaseMessageT() detaches the message from the builder, so we can // transfer the response to gRPC while simultaneously detaching that // memory buffer from the builder. *response_msg mb_.ReleaseMessageHelloReply(); assert(response_msg-Verify()); // Return an OK status. return grpc::Status::OK; } virtual grpc::Status SayManyHellos( grpc::ServerContext* context, const flatbuffers::grpc::MessageManyHellosRequest* request_msg, grpc::ServerWriterflatbuffers::grpc::MessageHelloReply* writer) override { // The streaming usage below is simply a combination of standard gRPC // streaming with the FlatBuffers usage shown above. const ManyHellosRequest* request request_msg-GetRoot(); const std::string name request-name()-str(); int num_greetings request-num_greetings(); for (int i 0; i num_greetings; i) { auto msg_offset mb_.CreateString(Many hellos, name); auto hello_offset CreateHelloReply(mb_, msg_offset); mb_.Finish(hello_offset); writer-Write(mb_.ReleaseMessageHelloReply()); } return grpc::Status::OK; } flatbuffers::grpc::MessageBuilder mb_; }; void RunServer() { std::string server_address(0.0.0.0:50051); GreeterServiceImpl service; grpc::ServerBuilder builder; builder.AddListeningPort(server_address, grpc::InsecureServerCredentials()); builder.RegisterService(service); std::unique_ptrgrpc::Server server(builder.BuildAndStart()); std::cerr Server listening on server_address std::endl; server-Wait(); } int main(int argc, const char* argv[]) { RunServer(); return 0; }客户端代码 grpc/samples/greeter/client.cpp#include grpcpp/grpcpp.h #include iostream #include memory #include string #include greeter.grpc.fb.h #include greeter_generated.h class GreeterClient { public: GreeterClient(std::shared_ptrgrpc::Channel channel) : stub_(Greeter::NewStub(channel)) {} std::string SayHello(const std::string name) { flatbuffers::grpc::MessageBuilder mb; auto name_offset mb.CreateString(name); auto request_offset CreateHelloRequest(mb, name_offset); mb.Finish(request_offset); auto request_msg mb.ReleaseMessageHelloRequest(); flatbuffers::grpc::MessageHelloReply response_msg; grpc::ClientContext context; auto status stub_-SayHello(context, request_msg, response_msg); if (status.ok()) { const HelloReply* response response_msg.GetRoot(); return response-message()-str(); } else { std::cerr status.error_code() : status.error_message() std::endl; return RPC failed; } } void SayManyHellos(const std::string name, int num_greetings, std::functionvoid(const std::string) callback) { flatbuffers::grpc::MessageBuilder mb; auto name_offset mb.CreateString(name); auto request_offset CreateManyHellosRequest(mb, name_offset, num_greetings); mb.Finish(request_offset); auto request_msg mb.ReleaseMessageManyHellosRequest(); flatbuffers::grpc::MessageHelloReply response_msg; grpc::ClientContext context; auto stream stub_-SayManyHellos(context, request_msg); while (stream-Read(response_msg)) { const HelloReply* response response_msg.GetRoot(); callback(response-message()-str()); } auto status stream-Finish(); if (!status.ok()) { std::cerr status.error_code() : status.error_message() std::endl; callback(RPC failed); } } private: std::unique_ptrGreeter::Stub stub_; }; int main(int argc, char** argv) { std::string server_address(localhost:50051); auto channel grpc::CreateChannel(server_address, grpc::InsecureChannelCredentials()); GreeterClient greeter(channel); std::string name(world); std::string message greeter.SayHello(name); std::cerr Greeter received: message std::endl; int num_greetings 10; greeter.SayManyHellos(name, num_greetings, [](const std::string message) { std::cerr Greeter received: message std::endl; }); return 0; }关键要点flatbuffers::grpc::MessageBuilder是FlatBufferBuilder的特化版本带有一个针对高效 gRPC 缓冲区传输的特殊分配器其余用法与普通 Builder 完全一致ReleaseMessageT()把消息从 builder 中移交出来既把响应交给 gRPC又把该内存缓冲区从 builder 中分离gRPC 侧的MessageT在GetRoot()前默认已经执行过缓冲区校验Verification流式server streaming用法就是标准 gRPC 流式 上述 FlatBuffers 用法的组合SayManyHellos在循环中Finish后通过writer-Write(...)逐条写出多条HelloReplygreeter.fbs中的rpc_service Greeter声明了两个方法一元 RPCSayHello与服务端流式 RPCSayManyHellos后者通过在方法上声明(streaming: server)指定。延伸阅读Tutorial 教程面向所有语言的完整入门教程含 C使用 Schema 编译器flatc命令行完整选项编写 Schemaschema 语法、hash、key等属性说明构建文档CMake 构建、FLATBUFFERS_MAX_PARSING_DEPTH与FLATBUFFERS_LOCALE_INDEPENDENT相关配置文法fbs/json 文本格式文法测试主文件 tests/test.cpp含ReflectionTest、UnionVectorTest等gRPC 相关测试与构建脚本见 grpc 目录。【免费下载链接】flatbuffersFlatBuffers: Memory Efficient Serialization Library项目地址: https://gitcode.com/GitHub_Trending/fl/flatbuffers创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表