blob: ff542e2196bb26907654b44054d42bc0d5ffc538 [file] [view]
# 启用 FlatBuffers 消息与 RPC
[English version](../en/flatbuffers.md)
bRPC 提供基于 IOBuf 的 FlatBuffers 消息、构造器、服务描述及 `fb_rpc` 协议。
支持默认关闭,需先编译启用该功能的 bRPC,再让客户端和服务端使用同一套头文件、
生成配置和运行库。完整示例位于 [example/benchmark_fb](../../example/benchmark_fb/README.md)。
## 1. 启用选项与依赖
| 构建方式 | 启用选项 | 可供独立 example 使用的运行库前缀 |
| --- | --- | --- |
| CMake | `-DWITH_FLATBUFFERS=ON` | `<build>/output` |
| Make | `config_brpc.sh --with-flatbuffers` | `<checkout>/output` |
| Bazel | build/test 命令均传 `--define=BRPC_WITH_FLATBUFFERS=true` | 原始 Bazel 输出不是 example 所需的安装前缀 |
**不能只给应用增加 `-DBRPC_WITH_FLATBUFFERS=1`。** FlatBuffers 相关 API 编译在库里。该选项不会改变
Channel、Controller、Server 的内存布局,只用 protobuf 的代码与两种构建的库都能配合使用;使用 FlatBuffers API 的代码链接到未启用该选项的库时会链接失败。`butil/config.h` 中的宏始终为 0 或 1,
应用应使用 `#if BRPC_WITH_FLATBUFFERS`,而不是 `#ifdef`。
先按[入门指南](getting_started.md)准备 C++ 工具链、Protobuf 编译器及开发库、gflags、
LevelDB、OpenSSL、zlib。FlatBuffers RPC **并不消除 Protobuf 依赖**。
FlatBuffers 支持要求 **C++17 及以上**,库本身以及所有包含 FlatBuffers 头文件或生成代码的编译单元
都必须满足。CMake 和 `config_brpc.sh --with-flatbuffers` 会自动选用 C++17,低于 C++17 时头文件会
以 `#error` 报错。这样过度对齐的对象在各处都使用同一套 aligned `new`,不会混用 C++14 和 C++17
的分配规则。只用 protobuf 的构建保持原有标准不变。
| 组件 | FlatBuffers 或测试依赖 |
| --- | --- |
| bRPC 消息及 RPC 运行库 | FlatBuffers 头文件,不链接 `libflatbuffers` |
| 官方 schema 代码生成 | 与头文件版本一致的 `flatc` |
| bRPC 服务代码生成 | `brpc_flatc`,其构建另需匹配的官方头文件和 `libflatbuffers` |
| example 烟测 | Python 3,不需要 GoogleTest |
| 库单元测试 | GoogleTest 及项目原有测试依赖 |
本仓库的 Bazel 和 ON gate 固定使用 **FlatBuffers 25.2.10**。复现该流程时,应使用同版本的
头文件、官方 `flatc` 和用于构建 `brpc_flatc` 的库。不要删除生成头文件中的版本断言。
三个容易混淆的参数:
- 根项目 CMake 的库测试:`FLATBUFFERS_FLATC_EXECUTABLE` 指向官方 `flatc`。
- 生成器验收及 example:`FLATC_EXECUTABLE` 指向官方 `flatc`。
- example:`BRPC_FLATC_EXECUTABLE` 指向 bRPC 的 `brpc_flatc`,不能填成官方 `flatc`。
各阶段还必须使用兼容的同一套 Protobuf。当 CMake 检测到 `Protobuf_VERSION > 4.21` 时,
还需要匹配的 Abseil 依赖;生成器验收和 example 需要 Protobuf 的 CMake config 包
导出传递依赖。运行库与 example 最低要求 CMake 3.16;下文 CTest 命令使用 3.17+ 的 `--no-tests=error`
防止空测试误报成功。只有 3.16 时,可直接执行 `smoke.py`。以下按 Linux/macOS 的
Unix Makefiles 或 Ninja 单配置生成器编写。
运行库使用了 FlatBuffers builder 的内部结构,目前只支持 25.2.10,其他版本会在编译期报错。
版本不一致时应重新生成头文件,不要删除或放宽上游生成代码中的版本断言。Bzlmod 和 WORKSPACE
使用同一个源码归档,并用校验和锁定内容;未采用 `bazel_dep`,是为了避免引入 gRPC、多语言工具
以及与 bRPC 冲突的 BoringSSL 依赖。
如需使用其他 FlatBuffers 版本,需要修改运行库、生成器和 codegen 测试中的版本检查,
再用该版本完整运行 `tools/flatbuffers/verify.sh`。步骤见生成器
[README](../../tools/flatbuffers/README.md#using-another-flatbuffers-version),
其中以 23.5.26 为例,仅适用于 CMake 构建。
## 2. 用 CMake 构建启用后的运行库
从 bRPC checkout 开始。按需提前设置有效的 `CC`、`CXX`。
CMake 构建把生成文件放在 build 目录,example 的绑定代码与构建产物放在 `WORK` 中。
独立 example 需要公共头文件和 `config.h`,build 目录里没有这些文件,所以要把运行库安装到一个前缀。
将下列变量替换为已有安装前缀,即包含 `include/`、`lib/` 或 `lib64/` 的目录;多个变量
可以指向同一前缀。
```sh
REPO="$PWD"
DEPS=/absolute/path/to/dependency-prefix
FB=/absolute/path/to/flatbuffers-prefix
OPENSSL=/absolute/path/to/openssl-prefix
WORK="$(mktemp -d "${TMPDIR:-/tmp}/brpc-benchmark-fb.XXXXXX")"
JOBS=2
printf 'WORK=%s\n' "$WORK"
```
若尚未安装 FlatBuffers,可先从匹配版本的官方源码构建。已有完整安装时跳过此段。
`FB` 必须是可写的安装前缀,不是源码目录:
```sh
FB_SOURCE=/absolute/path/to/flatbuffers-25.2.10-source
cmake -S "$FB_SOURCE" -B "$WORK/flatbuffers" \
-DCMAKE_BUILD_TYPE=Release -DCMAKE_POSITION_INDEPENDENT_CODE=ON \
-DCMAKE_INSTALL_PREFIX="$FB" -DCMAKE_INSTALL_LIBDIR=lib \
-DFLATBUFFERS_BUILD_TESTS=OFF -DFLATBUFFERS_BUILD_FLATC=ON \
-DFLATBUFFERS_BUILD_FLATLIB=ON -DFLATBUFFERS_BUILD_SHAREDLIB=OFF \
-DFLATBUFFERS_INSTALL=ON -DFLATBUFFERS_LIBCXX_WITH_CLANG=OFF
cmake --build "$WORK/flatbuffers" --parallel "$JOBS"
cmake --install "$WORK/flatbuffers"
"$FB/bin/flatc" --version
```
构建静态运行库,不启用库单元测试,因此这一阶段不需要 GoogleTest:
```sh
cmake -S "$REPO" -B "$WORK/runtime" \
-DCMAKE_BUILD_TYPE=Release -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \
-DCMAKE_PREFIX_PATH="$DEPS;$FB;$OPENSSL" \
-DOPENSSL_ROOT_DIR="$OPENSSL" \
-DCMAKE_INSTALL_PREFIX="$WORK/prefix" \
-DWITH_FLATBUFFERS=ON -DBUILD_SHARED_LIBS=OFF \
-DFLATBUFFERS_INCLUDE_DIR="$FB/include" \
-DBUILD_UNIT_TESTS=OFF -DDOWNLOAD_GTEST=OFF -DBUILD_BRPC_TOOLS=OFF
cmake --build "$WORK/runtime" --parallel "$JOBS"
cmake --install "$WORK/runtime"
grep -E '^#define BRPC_WITH_FLATBUFFERS +1$' "$WORK/prefix/include/butil/config.h"
```
最后一条检查必须输出 `#define BRPC_WITH_FLATBUFFERS 1`。公共接口位于
`brpc/flatbuffers/message.h` 和 `brpc/flatbuffers/service.h`。
上面的 policy 参数是兼容 CMake 4 下某些旧依赖策略的可选设置,不是启用 FlatBuffers 的
开关,也不能替代编译器和依赖版本要求。
## 3. 通过 example 完成端到端验证
该示例默认做**有界功能验证**,另有可选的计时模式,用于对比 `fb_rpc` 和等价的 `baidu_std` 服务。[echo.fbs](../../example/benchmark_fb/echo.fbs)
声明 `BenchmarkService.Echo`,显式 wire method ID 为 7。
[server.cpp](../../example/benchmark_fb/server.cpp) 使用 `AddFlatBuffersService` 注册服务;
[client.cpp](../../example/benchmark_fb/client.cpp) 使用生成的 `BenchmarkService::Stub`
及 `fb_rpc` channel。
### 构建生成器和示例
在前一节同一个 shell 中继续,沿用 `REPO`、`DEPS`、`FB`、`OPENSSL`、`WORK`、`JOBS`:
```sh
cmake -S "$REPO/tools/flatbuffers" -B "$WORK/codegen" \
-DCMAKE_BUILD_TYPE=Release -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \
-DBUILD_TESTING=OFF \
-DCMAKE_PREFIX_PATH="$FB;$DEPS" \
-DFLATBUFFERS_INCLUDE_DIR="$FB/include"
cmake --build "$WORK/codegen" --parallel "$JOBS"
cmake -S "$REPO/example/benchmark_fb" -B "$WORK/example" \
-DCMAKE_BUILD_TYPE=Release -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \
-DBUILD_TESTING=ON \
-DCMAKE_PREFIX_PATH="$DEPS;$FB;$OPENSSL" \
-DOPENSSL_ROOT_DIR="$OPENSSL" \
-DBRPC_ROOT="$WORK/prefix" \
-DFLATBUFFERS_INCLUDE_DIR="$FB/include" \
-DFLATC_EXECUTABLE="$FB/bin/flatc" \
-DBRPC_FLATC_EXECUTABLE="$WORK/codegen/brpc_flatc"
cmake --build "$WORK/example" --parallel "$JOBS"
(cd "$WORK/example" && ctest -V --no-tests=error --output-on-failure -R '^benchmark_fb_smoke$')
```
构建时会执行两个生成器,文件位于 `WORK/example/generated/`:
- 官方 `flatc`:`echo_generated.h`,定义 table 类型。
- `brpc_flatc`:`echo.brpc.fb.h`、`echo.brpc.fb.cpp`,定义服务及 stub。
不要提交这些生成文件,也不要在更换版本后沿用旧头文件。生成器若选错库,可在其 CMake
配置中显式传入 `FLATBUFFERS_LIBRARY`。导入 schema、稳定方法 ID 等规则见
[生成器说明](../../tools/flatbuffers/README.md)。
### 烟测的成功标准
必须看到名为 `benchmark_fb_smoke` 的 **1 项 CTest 通过**、命令退出码为 0,并输出:
```text
benchmark_fb smoke passed: 13 verified replies, 2 schema rejections, clean shutdown
```
这是 15 次 RPC(13 次正常响应、2 次预期 schema 拒绝),不是 15 项 CTest。`No tests were found`
不算通过。烟测验证的内容、期限,以及不经 CTest 直接运行的方法见
[example README](../../example/benchmark_fb/README.md#smoke-test)。
### 手工分别运行 server/client
手工运行客户端的命令、预期结果和有界参数见
[example README](../../example/benchmark_fb/README.md#run-separately)。
### 复用已有运行库或改为动态链接
已有兼容的 FB ON 运行库时,无需重编:将 example 配置中的 `BRPC_ROOT` 替换为其安装前缀
(CMake)或 `output/`(Make),`BRPC_FLATC_EXECUTABLE` 指向匹配的生成器,其他步骤不变。
example 会读取该前缀真实的 `include/butil/config.h`。
默认链接 `libbrpc.a`。使用 `LINK_SO=ON` 前,先用 `BUILD_SHARED_LIBS=ON` 构建 `brpc-shared`
并再次执行 `cmake --install`,再用 `-DLINK_SO=ON` 重新配置并构建 example;`LINK_SO` 本身
不会构建运行库。运行库若增加可选依赖,可通过 `BRPC_EXTRA_LIBRARIES` 补充链接库。
## 4. Make、Bazel 和完整回归
### Make
在独立可写 checkout 中配置,沿用前述依赖变量:
```sh
sh config_brpc.sh --with-flatbuffers \
--headers="$FB/include $DEPS/include $OPENSSL/include" \
--libs="$DEPS/lib $OPENSSL/lib" --cc="${CC:-cc}" --cxx="${CXX:-c++}"
make -j"$JOBS"
```
按平台替换为实际 `lib64` 或库目录。产出的 `output/` 可以替换 example 命令中的 `$WORK/prefix`,作为 `BRPC_ROOT`。
Make 单测通过 `FLATC="$FB/bin/flatc"` 选择官方生成器,但还需要已安装的 GoogleTest 库和
gperftools,不是只提供 GoogleTest 源码即可;执行时也需能够加载 `test/libbrpc.dbg.*`。
下方 ON runner 会构建 GoogleTest、设置测试库路径并校验报告;gperftools 等系统测试依赖
仍需预先安装。
### Bazel
```sh
bazel build --define=BRPC_WITH_FLATBUFFERS=true //:brpc
bazel test --define=BRPC_WITH_FLATBUFFERS=true --cache_test_results=no \
//test:brpc_flatbuffers_unittest //test:brpc_flatbuffers_protocol_unittest
```
这些目标验证库,不会自动执行独立 example。不要将原始 `bazel-bin` 当成 `BRPC_ROOT`;
example 需要 CMake 安装前缀或 Make `output/` 的 include/lib 布局。
### 库单测与 ON gate
单独配置库单测时,设置 `BUILD_UNIT_TESTS=ON`、匹配的 `FLATBUFFERS_FLATC_EXECUTABLE`;
若 `DOWNLOAD_GTEST=OFF`,还需指定 `BRPC_SYSTEM_GTEST_SOURCE_DIR`。
构建并运行 `brpc_flatbuffers_unittest`、`brpc_flatbuffers_protocol_unittest`。
仅打开库的 FlatBuffers 选项,不会自动执行测试。
要一次验证两组库单测、两项 codegen 验收及 example,可使用
[ON runner](../../.github/scripts/flatbuffers-on.py)。完整 CMake 门禁需要 **CMake/CTest 3.21+**
以生成 JUnit XML 报告(`--output-junit`)。**`--work` 目录必须尚不存在**:
```sh
python3 "$REPO/.github/scripts/flatbuffers-on.py" \
--build-system cmake --source "$REPO" --work "$WORK/on-gate" --jobs "$JOBS" \
--flatbuffers-prefix "$FB" \
--dependency-prefix "$DEPS" --dependency-prefix "$OPENSSL"
```
该门禁要求 FlatBuffers 25.2.10。不传 `--flatbuffers-prefix` 时会下载、校验并构建该版本。
GoogleTest 默认下载并校验固定的 1.14.0,也可用 `--gtest-source` 复用源码。
平台常规依赖仍需预先安装。Make/Bazel 模式验证两组库测试,只有 CMake 模式额外构建
生成器和 example。
检查 `WORK/on-gate/evidence/summary.json`:`status` 应为 `passed`,应包含所有预期测试,
执行数量非零,失败和跳过均为零。各步骤命令、日志及 XML 即使失败也会保留;
空测试、过滤后少跑或跳过不会被当作成功。
[CI 工作流](../../.github/workflows/flatbuffers-on.yml)覆盖 Linux CMake/Make 的 GCC、Clang,
Linux Bazel GCC,以及 macOS CMake。本地 gate 通过,不代表 GitHub 托管矩阵或另一套依赖
组合已经通过。
## 5. 常见问题
| 现象 | 检查与处理 |
| --- | --- |
| `BRPC_ROOT is not FlatBuffers-enabled` 或缺少 FB 符号 | 用正确选项重编运行库和使用方,检查安装前缀里的 `include/butil/config.h`;不要强制宏或混用 ON 头文件与 OFF 库。 |
| 找不到 `flatbuffers/idl.h`、`libflatbuffers` | 生成器需要完整官方开发安装,单有运行时头文件不够;明确指定 include 和 library 路径。 |
| 头文件与 `flatc` 版本不匹配 | 检查 `flatbuffers/base.h` 和 `flatc --version`,统一版本,在新目录重新生成代码,不删除版本断言。 |
| Protobuf/Abseil 缺头文件或链接符号 | 统一 Protobuf,提供其 config 包和 Abseil 前缀,避免系统与私有安装混用。 |
| macOS 找不到 OpenSSL | 设置实际 `OPENSSL_ROOT_DIR` 和 `CMAKE_PREFIX_PATH`,保持编译器、SDK、架构一致。 |
| 无法写 `src/butil/config.h.tmp` | 只有 `config_brpc.sh`(Make)会往源码目录写这个文件,所以源码必须可写;用独立副本隔离。CMake 把 `config.h` 放在 build 目录。 |
| CMake 4 报旧依赖策略不兼容 | 对相关依赖使用适当的 policy compatibility 设置或升级依赖;不能借此降低 C++ 和依赖版本要求。 |
| `No tests were found` | 检查 example 的 build 目录、`BUILD_TESTING=ON` 和已构建的程序,或直接运行 `smoke.py`。 |
| `LINK_SO=ON` 找不到库或动态加载失败 | 先用 `BUILD_SHARED_LIBS=ON` 构建 `brpc-shared`,再检查共享库及传递依赖的运行时搜索路径。 |
| 连接拒绝或超时 | 等待 readiness,用当前端口,确保同一主机/容器且服务端未超过生命周期;不能计作 schema 拒绝成功。 |
## 6. 消息构造和所有权
先用上游 `flatc --cpp` 生成 `*_generated.h`,再将
`brpc::flatbuffers::MessageBuilder` 传给生成的 `Create...` 函数。
调用 `Finish(root)` 后,通过 `ReleaseMessage()` 取得消息。
* 大消息要在构造时传入预估大小,例如 `MessageBuilder builder(payload_size + 256);`。默认初始大小是 1 KB,每次扩容都会重新申请缓冲区并拷贝已有数据。在我们的 benchmark 中,不预留大小构造 1 MB 的回包会让 server CPU 翻倍,主要来自反复分配和缺页。
* `ReleaseMessage()` 不复制 payload。返回的 Message 是 move-only 对象,持有 IOBuf block 引用;builder 复用或析构后,消息仍然有效。
* Message 和 builder 被移动后,源对象仍可复用。移动启用了 shared string 的 builder 会清空去重缓存,但已生成的 offset 不受影响。
* `Message::CopyFrom` 和 `MergeFrom` 共享同一个引用计数 IOBuf block,不复制
payload 或 metadata。通过任一别名修改数据,其他别名也会看到变化。有并发读者时,
写操作必须由外部同步保护。
* 从普通 `::flatbuffers::FlatBufferBuilder` 导入时,会复制 payload 和 scratch,
保留未完成 table 的状态。旧缓冲区由原分配器释放;若源 builder 拥有该分配器,
导入后也会销毁它。导入过程不猜测应使用 `free` 还是 `delete[]`,也不要求 payload
前有预留空间。
* 只使用 MessageBuilder 自身提供的 move、swap 和 release 操作。不要通过基类
cast 转移对象,也不要调用继承来的 raw-buffer release;这类 detached buffer
会保留成员 allocator 的地址。
* payload 前预留 64 字节并初始化为零。`reduce_meta_size_and_get_buf` 可以缩短
这段空间;扩大时返回失败,原对象保持不变。缩短 metadata 不会移动或改写 payload。
* 序列化 const Message 时,输出 IOBuf 会继续引用原存储。它不是 copy-on-write;仍有读者或序列化结果存活时,不要修改 payload 或 metadata。
* 分配长度在转成 SingleIOBuf 的 uint32_t 长度前会检查。SlabAllocator/builder
的分配失败会终止进程,release build 也一样,不受 `crash_on_fatal_log` 影响。
这是因为 FlatBuffers 的 `vector_downward` 无法在分配器返回 null 后继续工作。
解析时复制数据所需的内存若分配失败,则返回 false,保留原 Message。
`ParseFbFromIOBUF` 负责检查长度和 framing,解析后的 Message 持有自己的存储引用,
但该接口不负责限制接收消息大小。若 `msg_size` 来自不可信对端,调用方必须先按
自己的 max-message-size 策略检查,再调用解析接口。分片或未对齐的数据会在
schema 校验前复制到新存储,因此大小限制必须放在解析之前。
连续输入的 payload 地址按 64 字节对齐时,解析可以共享原存储。MessageBuilder
的分配按 64 字节对齐,但最终 payload 只保证满足 schema 的对齐要求,所以本地
构造的消息未必都能在接收侧零拷贝。当前不支持超过 64 字节的对齐要求。
**Framing 检查不等于 schema 校验。** 读取不可信数据前,先调用
`msg.Verify<YourRoot>()`,成功后再使用 `GetRoot<YourRoot>()` 或
`GetMutableRoot<YourRoot>()`。合法消息中的可选 string/vector 仍可能为 null。
Framing 检查失败不会修改原 Message。
## 7. Service ID 和代码生成
`BrpcDescriptorTable` 保存 namespace、service 名称、按空白分隔的方法名和显式
方法 ID。ID 必须是唯一的非负 int32。手写 descriptor 可以传空 ID 列表,此时按
声明顺序分配 ordinal ID;生成服务必须显式声明 ID:
```fbs
rpc_service BenchmarkService {
First(Request):Response (id: 2);
Second(Request):Response (id: 5);
}
```
* `descriptor.method(position)` 按声明顺序取方法。
* `method.index()` 是稳定的 wire ID,不是数组下标。
* 稀疏 ID 必须通过 `descriptor.FindMethodByIndex(id)` 查找。传输层不能直接用 wire ID 索引稠密数组。
* 删除方法后,不要把原 ID 分配给其他方法。调整声明顺序也不应改变已有方法的 ID。
* namespace `a.b` 和 `a.b.` 会规范化为同一个 service 名称;空 namespace 表示
全局作用域。方法全名包含 service 名称。service hash 使用规范化全名和
MurmurHash3 seed 1。若 ID 需要持久化或在线路上传输,service 名称必须保持稳定。
* Descriptor 初始化成功后不能再次初始化。它通过 RAII 持有 method;生成的 accessor 使用函数局部静态对象,初始化过程是线程安全的。
`tools/flatbuffers/` 中的生成器使用上游 parser 生成服务绑定,并单独链接
`libflatbuffers`。命令和限制见其 [README](../../tools/flatbuffers/README.md)。
生成的 dispatch 会验证请求,拒绝未知、不属于当前 service 或尚未实现的方法。
失败时,它会执行非空 completion callback;成功分派后,completion 由业务实现负责,
并且必须恰好执行一次。
## 8. 网络 RPC
启用或关闭该选项时,Channel、Controller、Server 的内存布局相同,ON 验证会检查这一点。用 `server.AddFlatBuffersService(&service, SERVER_DOESNT_OWN_SERVICE)` 注册生成的服务;将 `ChannelOptions::protocol` 设为 `"fb_rpc"`,并把这个 `brpc::Channel` 传给生成的 stub(`Echo::Stub stub(&channel);`)。stub 用库里的适配器包装该 channel,由适配器调用 `Channel::FBCallMethod`;`Controller::flatbuffers_method()` 返回带类型的 descriptor,protobuf 的 `Controller::method()` 仍为空。手工提供的 descriptor 必须活过整个调用,包括重试和 backup request。
服务注册独立于 protobuf 的 AddService 和 ListServices。Add/Remove/Clear 要求服务端处于已停止(READY)状态,不能是正在 STOPPING;管理操作必须由外部串行化。只有注册成功后所有权才转移。`RemoveFlatBuffersService` 会删除自有服务,Stop/Join 则保留注册以便重启。`GetFlatBuffersServiceCount()` 和 `ListFlatBuffersServices()` 返回这个独立注册表的内容;`ListServices()` 和 `/list` 仍只包含 protobuf 服务,`ServerStatistics::user_service_count` 则统计两类服务之和。重复的 service ID(包括 hash 冲突)以及与 protobuf 方法全名冲突的 FlatBuffers 方法会被拒绝。FlatBuffers 方法与 protobuf 方法使用同一种按方法的属性,因此 `MaxConcurrencyOf()`、`IgnoreEovercrowdedOf()`、服务端整体并发和默认方法并发限制、方法级 bvar 以及 `/status` 都覆盖 FlatBuffers 方法。其他协议无法按名字调用 FlatBuffers 方法。
传输层支持同步/异步调用、重试、backup request、single/pooled/short 连接以及请求/响应附件。每次发送都会分配一个独立的小 header 并共享 payload 存储,不会改写 const 请求的 metadata 前缀,因此重试和并发调用可以安全共享同一个不可变请求。应用仍须保证每个 response 和 Controller 活到调用完成。
Framing 只检查长度,不检查应用 schema。生成的服务会在分派前验证请求,手写服务也必须这样做。调用方使用 root accessor 前必须验证收到的响应 schema,RPC 成功不能替代 `response.Verify<Response>()`。
不支持鉴权、压缩、校验和与 streaming,遇到时会明确拒绝而不是悄悄忽略。FlatBuffers 服务不能通过仅限内置服务的内部端口访问。本传输层不提供 SelectiveChannel/ParallelChannel、HTTP/JSON 映射和 RPC-dump 回放,也不传输 log ID、用户字段和分布式追踪元数据。
完整的客户端/服务端示例见[第 3 节](#3-通过-example-完成端到端验证);示例演示这些 API,其可选的计时模式可以对比 `fb_rpc` 和等价的 `baidu_std` 服务。
### FRPC 帧格式与兼容性
一帧由 `[12 字节 header][metadata][message][attachment]` 组成。header 包含 `FRPC`、uint32 的 body 大小和 uint32 的 metadata 大小;body 大小不含 12 字节 header,但包含后面三段。header 中的两个整数都使用大端(网络字节序),与 `baidu_std` 的 header 一致。
metadata 是一个 FlatBuffers table,定义在 [flatbuffers_rpc_meta.fbs](../../src/brpc/policy/flatbuffers_rpc_meta.fbs):
* `RequestMeta`:service ID、method ID、message 大小、attachment 大小和 correlation ID。
* `ResponseMeta`:错误码、message 大小、attachment 大小和 correlation ID。
仓库里提交的 `flatbuffers_rpc_meta_generated.h` 由 `flatc` 25.2.10 生成;修改 schema 后用 `tools/flatbuffers/gen_rpc_meta.sh` 重新生成,生成头过期时 ON 门禁会失败。新字段只能追加在 table 末尾,不要重排、重新编号、改类型或删除已有字段。读取方会校验 table 并忽略不认识的字段,所以 schema 更新的对端仍然兼容。校验失败的 metadata 会导致连接关闭。大小超过 `INT32_MAX`、payload 长度不一致或 body 过大都会被拒绝。错误响应带非零错误码且没有 payload。Method ID 是稀疏的 wire ID,不是数组下标:只有保留显式 ID 时,增加、删除或重排声明才不会改变已有方法的 ID。这并不意味着旧的按序号编号的 schema 能兼容,已删除的 ID 也绝不能复用。
magic 是 `FRPC`,不是 `BRPC`;和 `baidu_std` 的 `PRPC` 一样,server 靠它在与其他协议共用的端口上识别协议。新连接会紧跟 `baidu_std` 之后尝试 `fb_rpc`,而不是按 ID(30)的顺序。该格式不保证与更早的实验性 FRPC 版本兼容,那些版本使用了不同的 magic、service hash 或字节序。库的全局 Protocol hook 签名保持不变,现有协议回调实现无需修改。自定义的数字协议 ID 不得与新分配的内置 ID 重叠,`PROTOCOL_FLATBUFFERS_RPC` 使用 ID 30。