tree: 6e5e1a0b0516da5c86610df6af4fda0f395579d3 [search]
  1. client.cpp
  2. CMakeLists.txt
  3. echo.fbs
  4. echo_pb.proto
  5. README.md
  6. server.cpp
  7. smoke.py
example/benchmark_fb/README.md

FlatBuffers RPC example

English guide | δΈ­ζ–‡ζŒ‡ε—

Independent client and server programs using the fb_rpc protocol, a generated FlatBuffers service, and brpc::flatbuffers::Message. By default the client runs a bounded functional check. A timed mode compares fb_rpc with an equivalent protobuf service over baidu_std (see Timed comparison).

The schema declares BenchmarkService.Echo with stable wire method ID 7. The generated stub calls Channel::FBCallMethod, and the server registers the generated service with Server::AddFlatBuffersService. The same server also registers benchmark_fb_pb.BenchmarkService from echo_pb.proto, a protobuf service with the same fields, and serves both fb_rpc and baidu_std.

Build

Dependencies, the FlatBuffers-enabled runtime install, the generator and the example build are described in the guide's verification section. BRPC_ROOT must be an install prefix (or a Make output/ directory) of a bRPC built with FlatBuffers ON; the example reads its include/butil/config.h and rejects an OFF build. The example compiles as C++17, which FlatBuffers support requires. Both flatc and brpc_flatc write generated code only to the build directory; do not commit *_generated.h or *.brpc.fb.*.

To link the shared library, build and install the runtime with -DBUILD_SHARED_LIBS=ON and the brpc-shared target, then configure the example with -DLINK_SO=ON. Extra runtime dependencies can be passed with -DBRPC_EXTRA_LIBRARIES='library1;library2'.

Run separately

By default the server and client accept only IPv4 loopback endpoints. In the build terminal:

"$WORK/example/benchmark_fb_server" --listen_addr=127.0.0.1:0 --duration_s=300

The server prints BRPC_FB_READY 127.0.0.1:<port> after Server::Start succeeds. Use that port in another terminal (shell variables are not shared, so set WORK again):

WORK=/absolute/path/printed/by/the/build
SERVER=127.0.0.1:PORT_FROM_READY
"$WORK/example/benchmark_fb_client" --server="$SERVER" \
  --request_count=16 --thread_num=2 --request_size=8193 --attachment_size=257
"$WORK/example/benchmark_fb_client" --server="$SERVER" \
  --request_count=2 --thread_num=1 --request_size=0 --omit_message=true
"$WORK/example/benchmark_fb_client" --server="$SERVER" \
  --request_count=2 --thread_num=1 --corrupt_request=true
"$WORK/example/benchmark_fb_client" --server="$SERVER" \
  --request_count=2 --thread_num=1

All four commands exit 0. The first prints {"completed":16,"successes":16,"expected_rejections":0,"failures":0}. The corrupt-request run prints {"completed":2,"successes":0,"expected_rejections":2,"failures":0}, and the last one checks recovery on the same server (completed=2, successes=2).

The client verifies the response schema before reading it, then compares the request ID, every string byte, optional-string presence, and the attachment. --corrupt_request=true damages the FlatBuffers root offset but keeps the FRPC framing valid. It succeeds only if every request gets the generated service's schema rejection (error code -1); timeouts and connection errors are failures. failures is a 0/1 flag, not a count, and no throughput is reported.

Bounded controls

Client optionDefaultAllowed
serverrequired127.0.0.1:<nonzero-port>, any IPv4 with allow_remote
protocolfb_rpcfb_rpc, baidu_std (protobuf control service)
request_count161..1000 total, not per thread
thread_num21..16 synchronous callers (1..1024 when timed)
request_size640..1048576 string bytes
attachment_size00..1048576 bytes
timeout_ms15001..5000 per RPC
deadline_ms300001..120000 for the client run
connection_typesinglesingle, pooled, short (short only in count mode)
omit_messagefalsePreserve absent rather than empty string
corrupt_requestfalseRequire schema rejection (fb_rpc, count mode only)
duration_s00 for count mode, 1..3600 for timed mode
warmup_s00..600 unmeasured seconds before a timed run
allow_remotefalseAccept a non-loopback server
use_bthreadfalseRun callers as bthreads, like multi_threaded_echo_c++ --use_bthread
fail_worker_start_at-1Tests only: fail to start caller N (0-based), -1 disables

The server defaults to 127.0.0.1:0, duration_s=60 (1..86400) and max_concurrency=32 (1..4096), and stops earlier on SIGINT/SIGTERM. When duration_s runs out it prints a line to stderr and stops, so give it longer than warmup_s + duration_s of every client run. max_concurrency must be at least the total thread_num of the clients, or calls fail with ELIMIT. --reply_delay_ms (0..10000, default 0) delays every reply and is meant for tests only. Use --allow_remote with a non-loopback listen_addr. Builtin services are disabled. FRPC has no authentication, compression, checksums or streaming, so this is not a production or public-network configuration.

Timed comparison

With --duration_s the client keeps one synchronous RPC in flight per caller thread, verifies every reply, and prints one JSON line with QPS, payload throughput, latency percentiles and the client's CPU cores. Only calls that finish inside the window are counted, and the window closes when the timer expires, not when the last in-flight call returns. started_threads reports how many callers actually ran; if any caller fails to start, the client still prints the line but exits 1. Latency is the RPC alone, without building the request or checking the reply. Percentiles come from a fixed-size histogram and are at most 1/128 above the recorded value; avg_us and max_us are exact. payload_mib_per_s counts the request string and attachment once, although the server echoes both back. SIGINT or SIGTERM closes the window early, and the client prints the result but exits 1. Short connections are rejected in timed mode, because closed connections would exhaust local ports in TIME_WAIT. Run each protocol with identical sizes and concurrency against the same server:

"$WORK/example/benchmark_fb_server" --listen_addr=SERVER_IP:8901 --allow_remote \
  --duration_s=3600 --max_concurrency=4096
for p in fb_rpc baidu_std; do
  "$WORK/example/benchmark_fb_client" --server=SERVER_IP:8901 --allow_remote \
    --protocol=$p --duration_s=30 --warmup_s=3 --thread_num=64 --request_size=8192
done

Both services echo the request string and attachment; the FlatBuffers path builds and verifies a FlatBuffers response, while the protobuf path parses and serializes the equivalent message. The results are only comparable when binaries, hosts, concurrency, sizes and duration are the same.

Smoke test

CTest runs the standard-library-only smoke.py, which starts its own server on an ephemeral loopback port and checks:

  1. finite RPCs with binary attachments, empty and absent strings, and single/pooled/short connections (13 verified replies);
  2. two malformed-schema requests are rejected;
  3. normal requests still work on the same server afterwards;
  4. a timed run where one of two callers fails to start (pthread and bthread) exits non-zero and reports started_threads=1, and a timed run over short connections is rejected;
  5. a timed run against a second server with --reply_delay_ms=600 reports a window of about --duration_s and a median latency of at least 600 ms, although its last call ends after the window closes;
  6. the servers it started are stopped and reaped.

The orchestration deadline is 40 seconds and CTest's timeout is 50 seconds. Needing the SIGKILL fallback fails the test. The smoke test can also run without CTest:

python3 "$REPO/example/benchmark_fb/smoke.py" \
  --server "$WORK/example/benchmark_fb_server" \
  --client "$WORK/example/benchmark_fb_client"

For common failures see the guide's troubleshooting table.