云计算百科
云计算领域专业知识百科平台

BRpc 搭建简单 rpc 服务器

在这里插入图片描述

📚 系列:C++ 工程实践 第 09 篇 ⏱ 阅读约 12 分钟

📌 一句话总结

本文从一份 .proto 写起,跑通 brpc 的完整闭环:① 定义 EchoService 并起服务端;② 同步客户端一个 stub.Echo 拿结果;③ 异步客户端用 done 回调收结果;④ 同一个端口再用 HTTP 访问一次。

文章目录

    • 1. BRpc 是什么:一个端口、三种调用
    • 2. 先定 proto:两个 message 一个 service,生成 Stub 和基类
    • 3. 实现 Service:done 是唯一需要你负责的参数
    • 4. 起服务端:AddService、Start、RunUntilAskedToQuit
    • 5. 同步客户端:第 4 个参数传 NULL
    • 6. 异步客户端:done 一响,回调里收尾
    • 7. 一个端口两种协议:同一份 Echo,HTTP 也能调
    • 8. 构建与踩坑:Makefile 五条链接参数 + 八个高频坑

1. BRpc 是什么:一个端口、三种调用

BRpc 是 C++ 的工业级 RPC 框架,本篇只用它四个角色:Server、Service、Channel、Controller。 RPC 的目标很朴素——让调用远程服务像调用本地函数,序列化、网络、超时重试都由框架吞掉。

先认识这六个名词,后面代码里出现什么都不会慌:

角色是什么谁创建
.proto 里的 message 请求/响应的数据结构 你写,protoc 生成 C++ 类
service 一组 RPC 方法的声明 你写,protoc 生成基类和 Stub
brpc::Server 服务端容器 你在 main 里创建
Service 子类 你实现的业务逻辑 你写类,AddService 注册
brpc::Channel 客户端到服务端的一条连接 客户端 Init
brpc::Controller 一次调用的上下文(超时、错误) 每次调用创建

它最实用的三个特性:一个端口跑 RPC + HTTP、同步/异步/半同步都支持、自带 /status 调试页。本文只讲原生用法,二次封装放到下一篇。

先把环境装好(源码编译,首次大约 5~10 分钟):

sudo apt-get install -y git g++ make libssl-dev libprotobuf-dev \\
libprotoc-dev protobuf-compiler libleveldb-dev
git clone https://github.com/apache/brpc.git && cd brpc && mkdir build && cd build
cmake -DCMAKE_INSTALL_PREFIX=/usr .. && make -j6 && sudo make install

装完 protoc、libbrpc、libprotobuf 都可用,下面四个文件放在同一目录即可编译。

上手成本要有心理准备:brpc 不是 header-only,它依赖 protobuf、gflags、leveldb、openssl 这一串库,第一次源码编译要等几分钟;但装好之后,同一份代码既能当 RPC 服务,又能当 HTTP 服务,后面加服务只需要新增 .proto 和实现类,不用再动网络层。

2. 先定 proto:两个 message 一个 service,生成 Stub 和基类

option cc_generic_services = true; 是 brpc 能生成 Service/Stub 的开关,漏了它后面全是编译错误。 新建 main.proto:

syntax = "proto3";

package example;

// 生成 C++ 服务基类,必须打开
option cc_generic_services = true;

message EchoRequest {
string message = 1;
}

message EchoResponse {
string message = 1;
}

// 定义一个 RPC 方法:Echo
service EchoService {
rpc Echo(EchoRequest) returns (EchoResponse);
}

目录里一共四个文件,make 会先生成 main.pb.* 再编译:

brpc-demo/
├── main.proto
├── brpc_server.cc
├── brpc_client.cc
├── brpc_client_async.cc
└── makefile

执行 protoc –cpp_out=. main.proto 后得到两类东西:

生成物内容谁用
main.pb.h / main.pb.cc 消息类 + EchoService 基类 + EchoService_Stub 服务端、客户端都要
EchoService(虚基类) 纯虚方法 Echo(…),服务端继承它 服务端
EchoService_Stub 带 Channel 的调用代理,客户端直接 .Echo(…) 客户端

⚠️ 生成物不要提交、不要手改

main.pb.cc / main.pb.h 由 .proto 每次生成,入库存一份只会制造冲突;改接口永远改 .proto,让 Makefile 重新生成。

3. 实现 Service:done 是唯一需要你负责的参数

四个参数里 request 只读、response 你填、done 必须 Run 一次——ClosureGuard 让它自动 Run。

#include <brpc/server.h>

#include "main.pb.h"

namespace example {

class EchoServiceImpl : public EchoService {
public:
void Echo(google::protobuf::RpcController* cntl_base,
const EchoRequest* request,
EchoResponse* response,
google::protobuf::Closure* done) override {
// 离开作用域自动 done->Run(),成功失败都不会漏
brpc::ClosureGuard done_guard(done);

// response 由框架创建,这里只负责填充
response->set_message("echo: " + request->message());
}
};

} // namespace example

参数谁负责说明
cntl_base 框架 需要读超时/错误时 static_cast<brpc::Controller*>
request 框架(只读) 不要改它,也不要保存指针到回调外
response 你 框架已创建,填字段即可;required 没填会失败
done 你(Run 一次) ClosureGuard 兜底,异常路径也不会漏

还有一个容易被忽略的协议细节:proto3 的标量字段默认没有「是否设置」的语义,int32=0 和「没赋值」在序列化后无法区分。如果你的接口需要区分这两种情况,用 optional(protobuf 3.15+)或 wrapper 类型,别用 -1 这类魔法值兜底。

4. 起服务端:AddService、Start、RunUntilAskedToQuit

三行起服务:AddService 注册、Start 监听、RunUntilAskedToQuit 阻塞;SERVER_DOESNT_OWN_SERVICE 表示 service 对象生命周期你自己管。

#include <gflags/gflags.h>
#include <brpc/server.h>

#include <iostream>

#include "main.pb.h"

DEFINE_int32(listen_port, 8000, "监听端口");
DEFINE_int32(thread_count, 3, "工作线程数");

int main(int argc, char* argv[]) {
google::ParseCommandLineFlags(&argc, &argv, true);

brpc::Server server;
example::EchoServiceImpl svc;
if (server.AddService(&svc, brpc::SERVER_DOESNT_OWN_SERVICE) != 0) {
std::cerr << "AddService failed" << std::endl;
return –1;
}

brpc::ServerOptions options;
options.num_threads = FLAGS_thread_count;
if (server.Start(FLAGS_listen_port, &options) != 0) {
std::cerr << "Start failed" << std::endl;
return –1;
}

server.RunUntilAskedToQuit(); // 阻塞到 Ctrl+C
return 0;
}

ServerOptions / 调用说明
num_threads 默认等于 CPU 核数,压测时按业务调
idle_timeout_sec 空闲连接多久断开,-1 表示不断
SERVER_DOESNT_OWN_SERVICE 服务对象在栈上,服务器不要帮忙 delete;反之会二次释放

启动成功的日志大致长这样(前两行是 brpc 自己的日志):

I… brpc/server.cpp] Server[EchoServiceImpl] is serving on port=8000.
I… brpc/server.cpp] Check out http://127.0.0.1:8000 in web browser.

5. 同步客户端:第 4 个参数传 NULL

stub.Echo(&cntl, &req, &rsp, nullptr) 的 done 为 nullptr 就是同步:阻塞到响应或错误。

#include <brpc/channel.h>

#include <iostream>

#include "main.pb.h"

int main() {
brpc::Channel channel;
brpc::ChannelOptions options;
options.protocol = "baidu_std"; // 默认协议
options.timeout_ms = 500;
options.max_retry = 3;
if (channel.Init("127.0.0.1:8000", &options) != 0) {
std::cerr << "channel init failed" << std::endl;
return –1;
}

example::EchoService_Stub stub(&channel);

example::EchoRequest req;
example::EchoResponse rsp;
brpc::Controller cntl;
req.set_message("hello brpc");

// done = nullptr:同步阻塞
stub.Echo(&cntl, &req, &rsp, nullptr);

if (cntl.Failed()) {
std::cerr << "rpc failed: " << cntl.ErrorText() << std::endl;
return –1;
}
std::cout << rsp.message() << std::endl;
return 0;
}

运行后输出:

echo: hello brpc

⚠️ 超时别用默认值兜底

timeout_ms 默认 500ms,压测或跨机房很容易误判失败;按接口的 P99 再加余量,别一个值走天下。

6. 异步客户端:done 一响,回调里收尾

把 done 换成 NewCallback,stub.Echo 立刻返回;response/controller 必须 new 在堆上,在回调里释放。

#include <brpc/channel.h>

#include <atomic>
#include <chrono>
#include <iostream>
#include <memory>
#include <thread>

#include "main.pb.h"

static std::atomic<bool> g_done{false};

static void OnEchoDone(example::EchoResponse* rsp, brpc::Controller* cntl) {
// 回调结束时自动释放,避免泄漏
std::unique_ptr<example::EchoResponse> rsp_guard(rsp);
std::unique_ptr<brpc::Controller> cntl_guard(cntl);

if (cntl->Failed()) {
std::cerr << "rpc failed: " << cntl->ErrorText() << std::endl;
return;
}
std::cout << rsp->message() << std::endl;
g_done = true; // 通知主线程:回调结束
}

int main() {
brpc::Channel channel;
brpc::ChannelOptions options;
options.timeout_ms = 500;
if (channel.Init("127.0.0.1:8000", &options) != 0) {
std::cerr << "channel init failed" << std::endl;
return –1;
}

example::EchoService_Stub stub(&channel);

example::EchoRequest req; // 请求可以栈上
req.set_message("hello async");

auto* rsp = new example::EchoResponse;
auto* cntl = new brpc::Controller;

// 回调由框架在响应到达时执行一次
stub.Echo(cntl, &req, rsp, brpc::NewCallback(OnEchoDone, rsp, cntl));

// 等回调真正结束再退出,避免固定 sleep 在慢网络下提前退出
while (!g_done) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
return 0;
}

服务端

客户端

服务端

客户端

#mermaid-svg-vEFPl6QehjKh0ogW{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-vEFPl6QehjKh0ogW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-vEFPl6QehjKh0ogW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-vEFPl6QehjKh0ogW .error-icon{fill:#552222;}#mermaid-svg-vEFPl6QehjKh0ogW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-vEFPl6QehjKh0ogW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-vEFPl6QehjKh0ogW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-vEFPl6QehjKh0ogW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-vEFPl6QehjKh0ogW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-vEFPl6QehjKh0ogW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-vEFPl6QehjKh0ogW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-vEFPl6QehjKh0ogW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-vEFPl6QehjKh0ogW .marker.cross{stroke:#333333;}#mermaid-svg-vEFPl6QehjKh0ogW svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-vEFPl6QehjKh0ogW p{margin:0;}#mermaid-svg-vEFPl6QehjKh0ogW .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-vEFPl6QehjKh0ogW text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-vEFPl6QehjKh0ogW .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-vEFPl6QehjKh0ogW .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-vEFPl6QehjKh0ogW .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-vEFPl6QehjKh0ogW .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-vEFPl6QehjKh0ogW #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-vEFPl6QehjKh0ogW .sequenceNumber{fill:white;}#mermaid-svg-vEFPl6QehjKh0ogW #sequencenumber{fill:#333;}#mermaid-svg-vEFPl6QehjKh0ogW #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-vEFPl6QehjKh0ogW .messageText{fill:#333;stroke:none;}#mermaid-svg-vEFPl6QehjKh0ogW .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-vEFPl6QehjKh0ogW .labelText,#mermaid-svg-vEFPl6QehjKh0ogW .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-vEFPl6QehjKh0ogW .loopText,#mermaid-svg-vEFPl6QehjKh0ogW .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-vEFPl6QehjKh0ogW .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-vEFPl6QehjKh0ogW .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-vEFPl6QehjKh0ogW .noteText,#mermaid-svg-vEFPl6QehjKh0ogW .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-vEFPl6QehjKh0ogW .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-vEFPl6QehjKh0ogW .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-vEFPl6QehjKh0ogW .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-vEFPl6QehjKh0ogW .actorPopupMenu{position:absolute;}#mermaid-svg-vEFPl6QehjKh0ogW .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-vEFPl6QehjKh0ogW .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-vEFPl6QehjKh0ogW .actor-man circle,#mermaid-svg-vEFPl6QehjKh0ogW line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-vEFPl6QehjKh0ogW :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

阻塞等待 直到拿到结果

stub.Echo 同步请求

响应

服务端

客户端

服务端

客户端

#mermaid-svg-jSHn9oUomscA2FAM{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-jSHn9oUomscA2FAM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-jSHn9oUomscA2FAM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-jSHn9oUomscA2FAM .error-icon{fill:#552222;}#mermaid-svg-jSHn9oUomscA2FAM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jSHn9oUomscA2FAM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-jSHn9oUomscA2FAM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jSHn9oUomscA2FAM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jSHn9oUomscA2FAM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-jSHn9oUomscA2FAM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jSHn9oUomscA2FAM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jSHn9oUomscA2FAM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jSHn9oUomscA2FAM .marker.cross{stroke:#333333;}#mermaid-svg-jSHn9oUomscA2FAM svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jSHn9oUomscA2FAM p{margin:0;}#mermaid-svg-jSHn9oUomscA2FAM .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-jSHn9oUomscA2FAM text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-jSHn9oUomscA2FAM .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-jSHn9oUomscA2FAM .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-jSHn9oUomscA2FAM .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-jSHn9oUomscA2FAM .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-jSHn9oUomscA2FAM #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-jSHn9oUomscA2FAM .sequenceNumber{fill:white;}#mermaid-svg-jSHn9oUomscA2FAM #sequencenumber{fill:#333;}#mermaid-svg-jSHn9oUomscA2FAM #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-jSHn9oUomscA2FAM .messageText{fill:#333;stroke:none;}#mermaid-svg-jSHn9oUomscA2FAM .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-jSHn9oUomscA2FAM .labelText,#mermaid-svg-jSHn9oUomscA2FAM .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-jSHn9oUomscA2FAM .loopText,#mermaid-svg-jSHn9oUomscA2FAM .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-jSHn9oUomscA2FAM .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-jSHn9oUomscA2FAM .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-jSHn9oUomscA2FAM .noteText,#mermaid-svg-jSHn9oUomscA2FAM .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-jSHn9oUomscA2FAM .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-jSHn9oUomscA2FAM .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-jSHn9oUomscA2FAM .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-jSHn9oUomscA2FAM .actorPopupMenu{position:absolute;}#mermaid-svg-jSHn9oUomscA2FAM .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-jSHn9oUomscA2FAM .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-jSHn9oUomscA2FAM .actor-man circle,#mermaid-svg-jSHn9oUomscA2FAM line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-jSHn9oUomscA2FAM :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

立即返回 主线程继续

触发 done 回调 收尾

stub.Echo 带 done 回调

响应到达

对象放哪谁释放
EchoRequest 栈或堆都行 调用返回后框架不再用它
EchoResponse 堆 回调里 unique_ptr 释放
brpc::Controller 堆 回调里 unique_ptr 释放
done(Closure) 框架创建 Run() 后自己 delete,不用管

为什么请求可以放栈上、响应必须放堆上?因为请求在 stub.Echo 返回时就已经被框架序列化发走,之后不再访问;而响应要等网络回来才被写入,回调执行完才安全释放。把这条记成一句话:调用返回后仍会被框架访问的对象,必须活到回调结束。

7. 一个端口两种协议:同一份 Echo,HTTP 也能调

brpc 的 Server 天然双协议:RPC 走默认协议,HTTP 走 POST /服务全名/方法名,调的是同一个方法。 服务起来后,另开一个终端:

curl -X POST http://127.0.0.1:8000/example.EchoService/Echo \\
-H 'Content-Type: application/json' \\
-d '{"message":"from http"}'

{"message":"echo: from http"}

调用方式走什么适合谁
stub.Echo(…) brpc 默认协议 内部 C++ 服务之间
curl / 浏览器 HTTP + JSON 脚本、调试、其他语言
/status 页面 HTTP 内置页 排查连接数、QPS、延迟分位

#mermaid-svg-cJAtwRdEQFXgIwHM{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-cJAtwRdEQFXgIwHM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-cJAtwRdEQFXgIwHM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-cJAtwRdEQFXgIwHM .error-icon{fill:#552222;}#mermaid-svg-cJAtwRdEQFXgIwHM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-cJAtwRdEQFXgIwHM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-cJAtwRdEQFXgIwHM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-cJAtwRdEQFXgIwHM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-cJAtwRdEQFXgIwHM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-cJAtwRdEQFXgIwHM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-cJAtwRdEQFXgIwHM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-cJAtwRdEQFXgIwHM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-cJAtwRdEQFXgIwHM .marker.cross{stroke:#333333;}#mermaid-svg-cJAtwRdEQFXgIwHM svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-cJAtwRdEQFXgIwHM p{margin:0;}#mermaid-svg-cJAtwRdEQFXgIwHM .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-cJAtwRdEQFXgIwHM .cluster-label text{fill:#333;}#mermaid-svg-cJAtwRdEQFXgIwHM .cluster-label span{color:#333;}#mermaid-svg-cJAtwRdEQFXgIwHM .cluster-label span p{background-color:transparent;}#mermaid-svg-cJAtwRdEQFXgIwHM .label text,#mermaid-svg-cJAtwRdEQFXgIwHM span{fill:#333;color:#333;}#mermaid-svg-cJAtwRdEQFXgIwHM .node rect,#mermaid-svg-cJAtwRdEQFXgIwHM .node circle,#mermaid-svg-cJAtwRdEQFXgIwHM .node ellipse,#mermaid-svg-cJAtwRdEQFXgIwHM .node polygon,#mermaid-svg-cJAtwRdEQFXgIwHM .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-cJAtwRdEQFXgIwHM .rough-node .label text,#mermaid-svg-cJAtwRdEQFXgIwHM .node .label text,#mermaid-svg-cJAtwRdEQFXgIwHM .image-shape .label,#mermaid-svg-cJAtwRdEQFXgIwHM .icon-shape .label{text-anchor:middle;}#mermaid-svg-cJAtwRdEQFXgIwHM .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-cJAtwRdEQFXgIwHM .rough-node .label,#mermaid-svg-cJAtwRdEQFXgIwHM .node .label,#mermaid-svg-cJAtwRdEQFXgIwHM .image-shape .label,#mermaid-svg-cJAtwRdEQFXgIwHM .icon-shape .label{text-align:center;}#mermaid-svg-cJAtwRdEQFXgIwHM .node.clickable{cursor:pointer;}#mermaid-svg-cJAtwRdEQFXgIwHM .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-cJAtwRdEQFXgIwHM .arrowheadPath{fill:#333333;}#mermaid-svg-cJAtwRdEQFXgIwHM .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-cJAtwRdEQFXgIwHM .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-cJAtwRdEQFXgIwHM .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cJAtwRdEQFXgIwHM .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-cJAtwRdEQFXgIwHM .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cJAtwRdEQFXgIwHM .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-cJAtwRdEQFXgIwHM .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-cJAtwRdEQFXgIwHM .cluster text{fill:#333;}#mermaid-svg-cJAtwRdEQFXgIwHM .cluster span{color:#333;}#mermaid-svg-cJAtwRdEQFXgIwHM div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-cJAtwRdEQFXgIwHM .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-cJAtwRdEQFXgIwHM rect.text{fill:none;stroke-width:0;}#mermaid-svg-cJAtwRdEQFXgIwHM .icon-shape,#mermaid-svg-cJAtwRdEQFXgIwHM .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cJAtwRdEQFXgIwHM .icon-shape p,#mermaid-svg-cJAtwRdEQFXgIwHM .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-cJAtwRdEQFXgIwHM .icon-shape .label rect,#mermaid-svg-cJAtwRdEQFXgIwHM .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cJAtwRdEQFXgIwHM .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-cJAtwRdEQFXgIwHM .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-cJAtwRdEQFXgIwHM :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

RPC 客户端 stub

8000 端口

curl 或浏览器

EchoService.Echo

路径写成 /example.EchoService/Echo(全名)或 /EchoService/Echo(短名)都能命中——brpc 按首段是否带 . 来判断用哪种查找方式。

这就是「一个端口多协议」最直观的收益:同一份业务逻辑,不用为 HTTP 再写一遍 Controller。

对调试也友好:联调时不用先写一个客户端,curl 一条命令就能验证参数和返回;线上排查时打开 /status,能看到每个方法的 QPS、延迟分位和错误码分布,比在业务代码里到处埋点更省事。

8. 构建与踩坑:Makefile 五条链接参数 + 八个高频坑

链接参数固定 -lbrpc -lleveldb -lprotobuf -lgflags -lssl -lcrypto;坑集中在 proto 开关、端口、done 生命周期、超时。

CXX := g++
CXXFLAGS := -std=c++17 -O2
LDLIBS := -lbrpc -lleveldb -lprotobuf -lgflags -lssl -lcrypto -lpthread

all: brpc_server brpc_client brpc_client_async

main.pb.cc main.pb.h: main.proto
protoc –cpp_out=. $<

brpc_server: brpc_server.cc main.pb.cc
$(CXX) $(CXXFLAGS) -o $@ $^ $(LDLIBS)

brpc_client: brpc_client.cc main.pb.cc
$(CXX) $(CXXFLAGS) -o $@ $^ $(LDLIBS)

brpc_client_async: brpc_client_async.cc main.pb.cc
$(CXX) $(CXXFLAGS) -o $@ $^ $(LDLIBS)

clean:
rm -f brpc_server brpc_client brpc_client_async main.pb.cc main.pb.h

.PHONY: all clean

坑现象修法
忘了 cc_generic_services 没有 EchoService 类 打开该选项后重新 protoc
少链接 brpc 依赖 undefined reference to brpc::… 补 -lbrpc -lleveldb -lprotobuf -lgflags -lssl -lcrypto
端口被占 Start failed 换端口,或 ss -lntp 查占用
异步用栈上 response 偶发崩溃 response/controller 必须堆上且活到回调
回调忘了释放 内存缓慢上涨 unique_ptr 守卫 / ClosureGuard
同步调用卡住 服务端没起、地址/端口不对 先跑 ./brpc_server,核对 channel.Init 的地址与端口
brpc 与 protobuf 版本不匹配 编译/链接诡异报错 用配套版本,或按 brpc README 安装
生成物提交进仓库 每次编译冲突 .gitignore 掉 *.pb.cc / *.pb.h

如果只记一句话:proto 是契约,框架是管道,业务只负责填 response。契约稳定了,后面换协议、加节点、做灰度,都是管道层的事。

📝 小结

① 接口先行:`.proto` 定契约,`cc_generic_services` 生成 Stub。

② 同步/异步只差最后一个参数:`nullptr` 阻塞,`NewCallback` 回调。

③ 一个端口两种协议:RPC 给服务调,HTTP 给脚本和浏览器调。

📦 项目源码

本文示例所在的 C++ 微服务脚手架(点播系统):https://github.com/syagain-zsx/cpp-microservice-scaffold-vod-system

🔜 下一篇

《BRpc 二次封装:Channel 管理、服务管理与回调工厂》——业务代码里不该到处 new Channel。关注专栏不迷路。

🔗 参考

[1] brpc 官方文档:https://brpc.apache.org/

[2] brpc GitHub:https://github.com/apache/brpc

[3] Protocol Buffers:https://protobuf.dev/

赞(0)
未经允许不得转载:网硕互联帮助中心 » BRpc 搭建简单 rpc 服务器
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!