BRPC 从入门到上线:百度高性能 RPC 框架全解析

什么是 RPC
RPC(远程过程调用)是一种让程序跨机器 / 跨进程调用函数的技术,它把复杂的网络通信、数据打包、传输、解包全部封装起来,让你调用远端服务的代码,写法和调用本地函数几乎完全一样,不用关心底层网络细节。
简单说,它的核心就是把远程调用伪装成本地调用。我们只需要像写普通代码一样发起调用,RPC 框架会自动把参数发给远端服务器、执行函数、再把结果返回给我们。
它的主要用途是分布式系统、微服务之间的互相通信,比如后端服务之间请求数据、调用功能,让不同程序、不同服务器能像一个整体一样协同工作,省去手动写 Socket、处理网络协议的繁琐工作。
平时写 C++,本地函数调用是这样:
// 同一程序里,直接调用
int add(int a, int b) { return a + b; }
int result = add(10, 20);
用 RPC 时,我们的代码几乎不变:
// 代码写法一样,但add函数在远端服务器
int result = rpc_add(10, 20);
RPC 框架会自动把10、20打包发给远端,让远端执行add,再把结果30传回来,我们完全不用管 Socket、网络通信。
所以 RPC 的核心,就是像调用本地函数一样,调用另一台机器 / 另一个进程的函数,底层网络、数据传输全被封装好。
简单说,RPC 就是分布式服务的 “函数调用桥梁”。微服务、多服务器协同工作时,不用手写复杂网络代码,就能让不同程序互相调用功能,和写本地代码一样简单。
要清楚,RPC 不是应用层协议,它是远程过程调用的编程模型 / 框架。
- 应用层协议:HTTP、WebSocket、DNS、FTP 这些,是网络通信格式与规则。
- RPC:是一套让我们像调用本地函数一样调用远端函数的机制。
RPC 可以基于各种应用层协议跑:
- 基于 HTTP:gRPC-Web、一些 HTTP 接口封装的 RPC
- 基于 HTTP/2:gRPC
- 基于 TCP 自定义协议:brpc、dubbo、thrift 等
所以:RPC ≠ 协议,RPC 是用协议来实现的 “调用方式”。
| 框架 | 开发者 | 语言支持 | 序列化 | http 协议 | 文档 | 编译问题 (ubuntu 22.04) | 服务端推送 | 异步调用 | 流式传输(大块数据传输) | 易用性 |
|---|---|---|---|---|---|---|---|---|---|---|
| grpc | C++、Java、Python、Go | pb | 支持 | 文档较为完善,没有中文文档 | 主要是下载 submodule 的问题,没有 vpn | 不支持 | 1. 异步需要客户端自己创建消费线程,使用起来较为麻烦2. 支持异步服务器 | 支持(接口简单易用) | 1. 简单易用2. 支持跨语言、跨平台 | |
| brpc | baidu | C++ | pb | 支持 | 文档较为完善,有中文文档 | 编译基本没有问题 | 支持,但是支持的不好,是通过一次长的 RPC 调用实现的 | 1. 通过回调函数支持客户端异步步调2. 支持异步 | 支持(接口稍微比 grpc 原生) | 1. 提供简洁的 API 和友好的使用文档,易于上手2. 在 sofa-pbrpc 基础上加了一些封装,比如资源管理等,更易用 |
| srpc | sogou | C++ | pb、Thrift | 支持 | 文档略显简单,有独立网站、都托管在 github | 编译基本没有问题 | 不支持 | 1. 支持异步客户端2. 通过 workflow 支持异步服务器 | 不支持 | 专注于异步编程,性能较高,用起来更复杂一些 |
| sofa-pbrpc | baidu | C++、Java | pb | 支持 | 官方文档较少 | 编译存在问题,依赖的库版本都比较老 | 不支持 | 1. 通过回调函数支持客户端异步步调 | 不支持 | 1. 轻量化2. 接口简单,容易使用 |
brpc 库的使用介绍
brpc 介绍
开发 C++ 高性能分布式程序时,你是否也有过这样的烦恼:想做跨服务的远程调用,却要手写 Socket 通信、数据序列化 / 反序列化、网络异常处理,光实现一个简单的远程方法调用就要写几百行底层代码;想搭建高吞吐的 RPC 服务,又要处理连接管理、负载均衡、异步请求,手动实现的代码要么性能拉胯,要么难以维护;想同时支持 HTTP、gRPC、Redis 等多协议,却要引入多个框架,服务部署和接口调用变得无比繁琐;更糟的是,线上服务需要监控、调试、性能分析,手写的网络代码根本没有这些能力,出了问题只能盲查日志,排障效率极低。
尤其是在高并发的后端场景:搜索、推荐、机器学习平台需要亿级请求的高吞吐 RPC 调用,手写代码满足不了性能要求;微服务架构下多个服务间需要灵活的同步 / 异步调用,普通的 HTTP 框架延迟太高;想快速对接公司内部的各种协议,却要重复开发协议解析模块,耗时又易出错。轻则开发效率大打折扣,重则线上服务因网络延迟、连接异常出现性能瓶颈,甚至服务不可用。
而百度开源的 brpc 正是为解决这些痛点而来 —— 这款工业级、高性能的 C++ RPC 框架,核心就是让你用极简的代码,快速实现高吞吐、低延迟、多协议、易扩展的分布式远程调用,它就像给你的 C++ 分布式程序装上了一个 “高性能通信引擎”,不仅支持 RPC 核心能力,还内置 HTTP/HTTPS、Redis、Memcached 等多协议支持,自带监控、调试、性能分析工具,无需重复开发底层网络和协议代码,堪称 C++ 高性能分布式开发的 “利器”,广泛应用于搜索、存储、广告、推荐等百度核心业务场景。
光说不练假把式,我们用一个 “模拟轻量级 RPC 服务 + 客户端调用” 的例子,快速感受 brpc 的便捷性:(基于 protobuf 定义接口,几步就能实现跨进程远程调用)
如果不清楚 RPC 是什么的话,可以先简单了解:RPC 是远程过程调用,让你调用远程服务器的方法像调用本地函数一样简单 😊
第一步:定义 Protobuf 接口文件(echo.proto)
brpc 基于 Protobuf 做接口定义和数据序列化,只需简单定义请求、响应和服务方法,即可自动生成调用代码,无需手动处理数据解析。
syntax = "proto3";
package example;
// 开启通用服务生成,brpc 基于此生成服务端/客户端代码
option cc_generic_services = true;
// 定义 Echo 请求结构
message EchoRequest {
string message = 1; // 待回显的消息
}
// 定义 Echo 响应结构
message EchoResponse {
string message = 1; // 回显的结果消息
}
// 定义 RPC 服务,包含一个 Echo 远程方法
service EchoService {
rpc Echo(EchoRequest) returns (EchoResponse);
}
第二步:实现 RPC 服务端代码(rpc_server.cpp)
brpc 封装了所有服务端底层逻辑,只需实现 Protobuf 定义的服务方法,配置端口和线程数,即可启动高吞吐的 RPC 服务,支持同步 / 异步处理、连接管理等核心能力。
#include <gflags/gflags.h>
#include <butil/logging.h>
#include <brpc/server.h>
#include "echo.pb.h"
// 定义命令行参数,方便配置服务端口、线程数
DEFINE_int32(listen_port, 8000, "RPC服务监听端口");
DEFINE_int32(num_threads, 4, "服务处理线程数,默认等于CPU核心数");
DEFINE_int32(idle_timeout, -1, "空闲连接超时时间,-1表示不关闭");
// 实现 Protobuf 定义的 EchoService 服务
class EchoServiceImpl : public example::EchoService {
public:
EchoServiceImpl() = default;
~EchoServiceImpl() override = default;
// 实现核心的 Echo 远程方法
// cntl_base:请求控制对象(包含连接、超时、日志等信息)
// request:客户端传入的请求数据(只读)
// response:需要填充的响应数据
// done:回调对象,处理完成后需调用 Run() 完成响应
void Echo(google::protobuf::RpcController* cntl_base,
const example::EchoRequest* request,
example::EchoResponse* response,
google::protobuf::Closure* done) override {
// 用 ClosureGuard 自动管理 done,RAII 方式自动调用 Run(),避免手动遗漏
brpc::ClosureGuard done_guard(done);
// 转换为 brpc 专属的 Controller,获取更多请求信息
brpc::Controller* cntl = static_cast<brpc::Controller*>(cntl_base);
// 打印客户端请求信息(可选,用于调试)
LOG(INFO) << "收到客户端请求:" << request->message();
// 填充响应数据:将客户端消息拼接后返回,模拟业务处理
response->set_message(request->message() + " [brpc 远程回显]");
}
};
int main(int argc, char* argv[]) {
// 解析命令行参数
google::ParseCommandLineFlags(&argc, &argv, true);
// 初始化日志:禁用无用日志,避免控制台刷屏
butil::logging::LoggingSettings log_setting;
log_setting.logging_dest = butil::logging::LOG_TO_NONE;
butil::logging::InitLogging(log_setting);
// 创建 brpc 服务器实例
brpc::Server server;
// 创建自定义的服务实现对象
EchoServiceImpl echo_service;
// 将服务添加到服务器,SERVER_DOESNT_OWN_SERVICE 表示服务器不管理服务对象生命周期
if (server.AddService(&echo_service, brpc::SERVER_DOESNT_OWN_SERVICE) != 0) {
LOG(ERROR) << "添加服务失败!";
return -1;
}
// 配置服务器选项
brpc::ServerOptions options;
options.idle_timeout_sec = FLAGS_idle_timeout;
options.num_threads = FLAGS_num_threads;
// 启动 RPC 服务,监听指定端口
if (server.Start(FLAGS_listen_port, &options) != 0) {
LOG(ERROR) << "启动 RPC 服务失败!端口:" << FLAGS_listen_port;
return -1;
}
// 阻塞运行服务器,直到收到 Ctrl+C 退出信号
LOG(INFO) << "brpc RPC 服务已启动:端口=" << FLAGS_listen_port;
server.RunUntilAskedToQuit();
return 0;
}
第三步:实现 RPC 客户端代码(rpc_client.cpp)
brpc 封装了客户端所有远程调用逻辑,只需创建通道(Channel)和 Stub 代理,即可像调用本地函数一样调用远程方法,支持同步 / 异步、超时、重试等能力,无需手动处理网络连接。
#include <gflags/gflags.h>
#include <butil/logging.h>
#include <brpc/channel.h>
#include "echo.pb.h"
// 定义命令行参数,配置服务端地址、协议、超时时间
DEFINE_string(server_addr, "127.0.0.1:8000", "RPC服务端地址");
DEFINE_string(protocol, "baidu_std", "brpc 通信协议,默认baidu_std(百度自研高性能协议)");
DEFINE_int32(timeout_ms, 500, "RPC调用超时时间,单位:毫秒");
DEFINE_int32(max_retry, 3, "RPC调用失败重试次数");
int main(int argc, char* argv[]) {
// 解析命令行参数
google::ParseCommandLineFlags(&argc, &argv, true);
// 初始化日志,禁用无用输出
butil::logging::LoggingSettings log_setting;
log_setting.logging_dest = butil::logging::LOG_TO_NONE;
butil::logging::InitLogging(log_setting);
// 创建 brpc 通道(Channel),表示客户端到服务端的通信线路
brpc::Channel channel;
// 配置通道选项
brpc::ChannelOptions options;
options.protocol = FLAGS_protocol; // 设置通信协议
options.timeout_ms = FLAGS_timeout_ms; // 设置调用超时
options.max_retry = FLAGS_max_retry; // 设置失败重试次数
// 初始化通道,连接服务端
if (channel.Init(FLAGS_server_addr.c_str(), &options) != 0) {
std::cerr << "连接 RPC 服务端失败!地址:" << FLAGS_server_addr << std::endl;
return -1;
}
// 创建 Stub 代理(基于 Protobuf 生成),通过通道调用远程服务
// Stub 是远程服务的本地代理,调用其方法等价于调用远程服务方法
example::EchoService_Stub stub(&channel);
// 定义请求、响应对象
example::EchoRequest request;
example::EchoResponse response;
// 定义 brpc 控制对象,用于获取调用状态、错误信息
brpc::Controller cntl;
// 填充请求数据
request.set_message("Hello brpc!");
// 同步调用远程 Echo 方法:最后一个参数为 NULL 表示同步,阻塞等待响应
stub.Echo(&cntl, &request, &response, NULL);
// 检查调用是否成功
if (cntl.Failed()) {
std::cerr << "RPC 调用失败:" << cntl.ErrorText() << std::endl;
return -1;
}
// 打印远程服务的响应结果
std::cout << "收到服务端响应:" << response.message() << std::endl;
// (可选)异步调用示例:非阻塞,通过回调处理响应
example::EchoRequest async_req;
example::EchoResponse async_rsp;
brpc::Controller async_cntl;
async_req.set_message("Hello brpc async!");
// 异步回调函数:处理服务端响应
auto on_echo_done = [&](brpc::Controller* cntl, example::EchoResponse* rsp) {
std::unique_ptr<brpc::Controller> cntl_guard(cntl);
std::unique_ptr<example::EchoResponse> rsp_guard(rsp);
if (cntl->Failed()) {
std::cerr << "异步 RPC 调用失败:" << cntl->ErrorText() << std::endl;
return;
}
std::cout << "收到异步响应:" << rsp->message() << std::endl;
};
// 发起异步调用:通过 NewCallback 注册回调函数,立即返回不阻塞
stub.Echo(&async_cntl, &async_req, &async_rsp,
brpc::NewCallback(on_echo_done, &async_cntl, &async_rsp));
// 等待异步调用完成(实际开发中可处理其他业务,无需主动等待)
while (!async_cntl.Finished()) {
usleep(1000);
}
return 0;
}
第四步:编写 Makefile 并编译运行
brpc 编译简单,无需复杂的依赖配置,只需链接核心库,配合 Protobuf 编译即可,一键完成服务端和客户端的构建。
1. 编写 Makefile
# 定义编译参数和链接库
CXX = g++
CXXFLAGS = -std=c++17 -O2
LDFLAGS = -lbrpc -lprotobuf -lgflags -lssl -lcrypto -lleveldb -lpthread
# 目标文件
TARGETS = rpc_server rpc_client
# Protobuf 生成的代码
PROTO_SRC = echo.pb.cc
PROTO_HDR = echo.pb.h
# 编译所有目标
all: $(TARGETS)
# 编译服务端
rpc_server: rpc_server.cpp $(PROTO_SRC)
$(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS)
# 编译客户端
rpc_client: rpc_client.cpp $(PROTO_SRC)
$(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS)
# 从 proto 文件生成 C++ 代码
$(PROTO_SRC): echo.proto
protoc --cpp_out=./ $<
# 清理编译产物
clean:
rm -f $(TARGETS) $(PROTO_SRC) $(PROTO_HDR)
# 编译
make
# 启动服务端(后台运行)
./rpc_server --listen_port=8000 &
# 运行客户端
./rpc_client --server_addr=127.0.0.1:8000
收到服务端响应:Hello brpc! [brpc 远程回显]
收到异步响应:Hello brpc async! [brpc 远程回显]
同时,brpc 自带 HTTP 调试界面,打开浏览器访问 http://127.0.0.1:8000,即可查看服务状态、请求统计、性能指标,无需额外开发监控工具,线上调试超方便!
当你想新增一个 RPC 方法时,只需在 proto 文件中定义,实现服务方法,无需修改任何底层网络代码;想从客户端调用远程服务,只需创建 Channel 和 Stub,像调用本地函数一样简单;想切换通信协议(如 baidu_std、gRPC、HTTP),只需修改一行配置;想实现异步调用,只需注册回调函数,brpc 自动处理线程调度;想搭建高可用分布式服务,brpc 基于 RAFT 算法实现了 braft 组件,一键支持主从选举、数据同步。
这就是 brpc 的核心价值:把高性能分布式开发的复杂底层逻辑全部封装,让开发者聚焦业务逻辑,用几行代码就能实现百度级别的高吞吐、低延迟 RPC 服务,同时支持多协议、多场景,自带调试、监控、性能分析能力,是 C++ 开发分布式微服务、高性能后台系统的 “不二之选”。
除了核心的 RPC 调用,brpc 还内置了超多工业级特性,满足各种复杂场景:
- 多协议支持:一个端口同时支持 brpc 自研 baidu_std、gRPC、HTTP/HTTPS、Redis、Memcached、Thrift 等,无需部署多个服务;
- 高性能:基于协程(bthread)实现,无锁化设计,单服务可支撑百万 QPS,延迟低至微秒级;
- 灵活的调用方式:支持同步、异步、半同步调用,组合 Channel 实现分库分表、并发访问;
- 完善的容错机制:内置超时、重试、负载均衡(轮询、随机、一致性哈希)、命名服务(DNS、ZooKeeper、etcd);
- 流式传输:支持大块数据的流式上传 / 下载,适合文件传输、视频流等场景;
- 服务端推送:通过长连接实现服务端主动向客户端推送数据,适合消息通知、实时监控;
- 丰富的工具链:自带 CPU、内存、锁竞争分析工具,HTTP 调试界面,日志系统,无需额外集成;
- 跨平台:支持 Linux、macOS 等主流系统,可无缝对接公司内部现有服务。
从快速搭建测试用的轻量级 RPC 服务,到开发支撑亿级请求的百度核心业务系统,brpc 都能完美适配,真正做到简单易用,性能强悍,工业级可靠!
下面我们可以进行安装和全方位的认知!
安装
1. 安装依赖
sudo apt-get install -y git g++ make libssl-dev libprotobuf-dev libprotoc-dev protobuf-compiler libleveldb-dev
2. 安装 brpc
git clone https://github.com/apache/brpc.git
cd brpc/
mkdir build && cd build
cmake -DCMAKE_INSTALL_PREFIX=/usr .. && cmake --build . -j6
make && sudo make install
解释与搭建过程
我们通过简答的 Add 样例来介绍一下 brpc 的服务器/客户端的搭建框架,因为 brpc 是通过 ProtoBuf 进行序列化传输的,正好 ProtoBuf 中涉及到的不仅仅是 message, enum, 还有一个成为 server 服务!
因为其实我是比较信任 brpc 的,所以下面这里我们就仅仅只是介绍如何关闭 brpc 的日志输出,想要往这个方面追求的,可以自行去查阅相关资料!
包含头文件:
#include <butil/logging.h>
brpc 库内有人家自己的日志输出模块(无法替换,除非修改库内源码中所有的日志输出操作后重新编译库进行安装)
namespace logging {
enum LoggingDestination {
LOG_TO_NONE = 0
};
struct BUTIL_EXPORT LoggingSettings {
LoggingSettings();
LoggingDestination logging_dest;
};
bool InitLogging(const LoggingSettings& settings);
}
关闭日志:
logging::LoggingSettings settings;
settings.logging_dest = logging::LOG_TO_NONE;
logging::InitLogging(settings);
我们要先清楚 ProtoBuf 中的 Server 的生成代码!下面我们使用一个简单的实例 proto 文件生成的 pb 文件来进行说明!
ProtoBuf 的服务相关核心类
首先看我们的 cal.proto,这是整个 brpc 通信的基础,定义了数据结构与服务接口:
syntax = "proto3";
package cal;
option cc_generic_services = true;
message AddReq {
int32 num1 = 1;
int32 num2 = 2;
};
message AddRsp {
int32 result = 1;
};
//这是一个http请求,它不需要其他字段(后续 HTTP 相关的,前面可忽略)
message helloReq {
}
message helloRsp{
}
service CalService {
rpc Add(AddReq) returns (AddRsp);
rpc Hello(helloReq) returns (helloRsp);
};
我们现在可以生成其对应的 pb 文件:
protoc --cpp_out=./ ./cal.proto
Protobuf 会自动生成两类核心代码:
数据结构类:AddReq 和 AddRsp
- 对应图中的
class AddReq {...}; class AddRsp {...}; - 提供了
set_num1()/num1()、set_result()/result()等访问器方法,用于序列化和反序列化网络数据。
服务抽象类:CalService
- 对应图中的
class CalService { ... } - 这是一个纯虚基类(我们需要继承),定义了服务的接口,是 brpc 服务端实现业务逻辑的基础。
我们首先要知道 ProtoBuf 的相关核心类:
1. Closure 类(闭包 / 回调)
namespace google {
namespace protobuf {
// 闭包回调类:用于标记RPC处理完成,发送响应
class PROTOBUF_EXPORT Closure {
public:
// 构造函数
Closure() {}
// 虚析构函数
virtual ~Closure();
// 纯虚函数:调用它 = 结束RPC,发送响应给客户端
virtual void Run() = 0;
};
// 辅助创建闭包的工具函数
inline Closure* NewCallback(void (*function)());
} // namespace protobuf
} // namespace google
- Closure 是 Protobuf 提供的回调抽象类
- Run() = 0 是纯虚函数,必须实现
- brpc 内部实现了它,调用 Run () 就会发送响应给客户端(很重要,brpc 采用了 RAII 风格的 ClosureGuard 来自动管理闭包生命周期)
- 不调用 Run (),客户端会一直等待超时
2. RpcController 类(RPC 控制器)
namespace google {
namespace protobuf {
// RPC控制器:管理一次RPC调用的状态、错误、上下文
class PROTOBUF_EXPORT RpcController {
public:
// 判断当前RPC调用是否失败
bool Failed();
// 获取调用失败的错误描述字符串
std::string ErrorText();
};
} // namespace protobuf
} // namespace google
- RpcController 是 Protobuf 提供的请求上下文管理类
- 服务端 / 客户端都会使用
- Failed():判断调用是否失败
- ErrorText():获取错误信息
- brpc 用
brpc::Controller继承并扩展了它
brpc::Controller 继承并扩展了 Protobuf 原始的 RpcController:
- Protobuf 只提供抽象接口(Failed / ErrorText)
- brpc 实现了这套接口,并扩展了 IP、Http、超时、错误码等大量实用功能
- 服务端收到的
controller实际就是brpc::Controller
生成的 CalService 类中,核心的 Add 方法定义如下:
virtual void Add(
PROTOBUF_NAMESPACE_ID::RpcController* controller,
const ::cal::AddReq* request,
::cal::AddRsp* response,
::google::protobuf::Closure* done
);
这四个参数的作用,正好对应图中的说明:
RpcController* controller
- 对于服务端,它是请求上下文控制器。
- 主要用于获取客户端的 HTTP 请求信息(如 Header),也可以用来设置本次请求的错误信息或 HTTP 响应头。
const AddReq* request
- 这是从网络端反序列化后的客户端请求对象。
- 服务端通过它读取客户端传来的
num1和num2等参数。
AddRsp* response
- 这是待填充的服务端响应对象。
- 业务处理完成后,服务端将计算结果(如
result)写入这个对象,由框架负责序列化后返回给客户端。
Closure* done
- 这是一个闭包回调,用于标记本次 RPC 调用的结束。
- 很多 RPC 框架(包括 brpc)都支持异步操作:业务处理可以不在
Add函数内进行,而是交给其他线程。当业务处理完成后,必须手动调用done->Run(),框架才会将response序列化并发送回客户端,本次调用才算真正结束。
所以:
Protobuf 负责生成 AddReq、AddRsp 和 CalService 这些 “骨架” 代码。
用户的核心工作,就是继承 CalService,重写 Add 方法,在里面实现具体的业务逻辑(如加法运算),并通过 Closure 来控制响应的时机。
但是想要通过 brpc 搭建服务器,我们还需要了解相关的 brpc 的服务核心类!
服务核心类
namespace brpc {
// ==============================
// 1. 服务器配置选项
// 作用:设置 brpc 服务端的运行参数
// ==============================
struct ServerOptions {
// 连接空闲超时时间:无数据传输时,多久后关闭连接
// -1 = 永不关闭(默认)
int idle_timeout_sec;
// 服务端工作线程数量
// 默认 = 机器CPU核心数
int num_threads;
// ... 其他扩展配置
};
// ==============================
// 2. 服务所有权枚举
// 作用:告诉服务器【由谁来销毁服务对象】(看这个是全局被管理的,还是局部被管理的,或者有谁就是要管理他)
// ==============================
enum ServiceOwnership {
// 服务器托管服务对象:添加失败时,服务器自动删除服务
SERVER_OWNS_SERVICE,
// 服务对象由用户自己管理:服务器永远不删除服务
SERVER_DOESNT_OWN_SERVICE
};
// ==============================
// 3. brpc 服务器核心类
// 作用:启动服务、监听端口、注册服务、管理连接、运行服务
// ==============================
class Server {
// 注册一个 Proto 服务(如 CalService)到服务器
// service:你实现的服务对象
// ownership:服务所有权(谁负责销毁)
int AddService(google::protobuf::Service* service, ServiceOwnership ownership);
// 启动服务器,监听指定端口
// port:端口号
// opt:ServerOptions 配置
int Start(int port, const ServerOptions* opt);
// 停止服务器
int Stop(int closewait_ms);
// 等待服务器线程退出
int Join();
// 阻塞运行,直到 Ctrl+C 或主动停止
void RunUntilAskedToQuit();
};
// ==============================
// 4. RAII 闭包守卫(最关键!自动发送响应)
// 作用:自动调用 done->Run() 发送响应给客户端
// ==============================
class ClosureGuard {
// 用一个 Protobuf 闭包构造守卫
explicit ClosureGuard(google::protobuf::Closure* done);
// 析构时自动调用 Run(),发送响应
~ClosureGuard() { if (_done) _done->Run(); }
};
// ==============================
// 5. HTTP 头信息类
// 作用:处理 HTTP 请求/响应头
// ==============================
class HttpHeader {
// 设置 Content-Type
void set_content_type(const std::string& type);
// 获取请求头
const std::string* GetHeader(const std::string& key);
// 设置响应头
void SetHeader(const std::string& key, const std::string& value);
// 获取请求 URI
const URI& uri() const;
// 获取/设置 HTTP 方法(GET/POST/PUT...)
HttpMethod method() const;
void set_method(const HttpMethod method);
// 获取/设置 HTTP 状态码
int status_code();
void set_status_code(int status_code);
};
// ==============================
// 6. brpc 控制器(继承自 Proto 的 RpcController)
// 作用:管理一次 RPC 调用的所有上下文(超时、重试、请求、响应、错误、HTTP)
// ==============================
class Controller : public google::protobuf::RpcController {
// 设置超时时间(毫秒)
void set_timeout_ms(int64_t timeout_ms);
// 设置最大重试次数
void set_max_retry(int max_retry);
// 获取响应对象
google::protobuf::Message* response();
// 获取 HTTP 响应头
HttpHeader& http_response();
// 获取 HTTP 请求头
HttpHeader& http_request();
// --------------------------
// 继承自 Proto 原生的接口
// --------------------------
// 判断 RPC 是否失败
bool Failed();
// 获取错误信息
std::string ErrorText();
// 设置 RPC 响应完成后的回调函数
using AfterRpcRespFnType = std::function<void(Controller* cntl,
const google::protobuf::Message* req,
const google::protobuf::Message* res)>;
void set_after_rpc_resp_fn(AfterRpcRespFnType&& fn);
};
} // namespace brpc
ServerOptions 服务配置结构体
ServerOptions 是 brpc 服务端的配置参数集合,用来控制服务器的运行行为。其中:
idle_timeout_sec 表示连接的空闲超时时间,即当一个连接在指定时间内没有任何数据传输时,服务器会自动关闭该连接,默认值为 -1,表示不启用超时关闭机制。
num_threads 用于设置服务器的工作线程数量,默认使用机器的 CPU 核心数,保证最佳的并发处理性能。这个配置会在服务器启动时传入,决定整个服务的运行模式。
ServiceOwnership 服务所有权枚举
ServiceOwnership 是一个权限枚举,用来告知 brpc 服务器由谁来管理服务对象的生命周期。
SERVER_OWNS_SERVICE 表示服务对象由服务器托管,如果服务添加失败,服务器会自动销毁该对象,避免内存泄漏;
SERVER_DOESNT_OWN_SERVICE 表示服务对象由用户自己创建和管理,服务器无论在什么情况下都不会销毁它,这也是我们最常用的选项,因为我们的服务对象通常在 main 函数中创建,生命周期由程序自行控制。
Server 服务器核心类
Server 是 brpc 服务端的核心类,承担服务注册、端口监听、启动运行、停止退出等全部管理工作。
AddService 方法用于将我们继承 Protobuf 生成的纯虚服务类(如 CalService)注册到服务器中,是连接 Protobuf 接口与 brpc 服务器的关键方法。
Start 方法用于启动服务器并绑定指定端口,使用传入的 ServerOptions 配置运行。
Stop 和 Join 用于停止服务并等待线程退出,而 RunUntilAskedToQuit 是最常用的阻塞方法,让服务器一直运行,直到收到 Ctrl+C 退出信号或主动停止,保证服务持续提供 RPC 调用。
ClosureGuard 自动闭包守卫类
ClosureGuard 是 brpc 基于 RAII 机制设计的自动响应工具,专门用于简化 Protobuf 的 Closure 使用。它在构造时接收 RPC 方法传入的 done 闭包对象,在析构时会自动调用 done->Run()。这个机制非常重要,因为 Run() 是真正触发响应发送给客户端的方法,使用 ClosureGuard 可以让开发者完全不需要关心响应时机,只需要专注业务逻辑,避免因忘记调用 Run() 导致客户端卡死或请求泄漏,它直接服务于 Protobuf 生成的 RPC 方法。
HttpHeader HTTP 报文头类
HttpHeader 是 brpc 提供的 HTTP 报文处理工具,用于在请求中获取或设置 HTTP 请求头、响应头、URI 地址、HTTP 请求方法、HTTP 状态码等信息。
brpc 框架同时支持二进制私有协议(如 baidu_std)与通用 HTTP 协议,HttpHeader 就是专门用来处理 HTTP 协议报文的工具类,让同一个服务既能接收高性能 RPC 二进制请求,也能直接接收标准 HTTP 请求,极大提升了框架的通用性。
不过:HttpHeader 并不属于 Server,而是完全隶属于 Controller。因为 Server 只负责启动服务、监听端口和分发请求,并不处理、也不保存任何单次请求的具体信息。每一次 RPC 请求到达服务端时,brpc 框架都会为其创建一个独立的 brpc::Controller 对象,用来管理本次请求的完整上下文,而本次请求对应的所有 HTTP 相关信息(包括请求头、响应头、URI、请求方法、状态码等),都被封装在 Controller 内部的 HttpHeader 中,分别通过 http_request() 获取请求头、http_response() 设置响应头。
也就是说:一次请求对应一个 Controller,一个 Controller 自带一组 HttpHeader,所有与当前请求相关的 HTTP 信息,都必须通过 Controller 获取,而不能直接从 Server 中取得。
Controller RPC 控制器类
Controller 是 brpc 最核心的上下文类,直接继承自 Protobuf 的 RpcController 抽象类,并做了大量功能扩展。它管理一次 RPC 调用的全部生命周期,包括设置超时时间、最大重试次数、获取请求与响应对象、读写 HTTP 头、判断调用是否失败、获取错误信息等。
它实现了 Protobuf 定义的 Failed() 和 ErrorText() 基础接口,并扩展了大量 brpc 独有的实用功能,是服务端和客户端都必须使用的核心对象。在我们的 RPC 方法中,传入的 controller 参数本质上就是 brpc::Controller,它承载了一次请求的所有上下文信息。【使用了多态的特性】
所以我们来搭建一个简单的 rpc 同步的服务器:
sync_server.cc
#include <butil/logging.h>
#include <brpc/server.h>
#include "cal.pb.h"
class CalServerImpl : public cal::CalService {
public:
CalServerImpl(){}
~CalServerImpl(){}
// 重写
void Add(::google::protobuf::RpcController* controller,
const ::cal::AddReq* request,
::cal::AddRsp* response,
::google::protobuf::Closure* done) override {
// 当doneGuard 被释放的时候就会执行 done->Run() 来完成本次的 rpc 调用 -- 通知客户端[响应传回给客户端]
brpc::ClosureGuard doneGuard(done);
int result = request->num1() + request->num2();
response->set_result(result);
}
};
int main(int argc, char* argv[]) {
// 1. 实例化计算服务器(等待注册)
CalServerImpl calServer;
// 2. 定义服务器配置对象
brpc::ServerOptions options;
options.idle_timeout_sec = -1;
// 3. 实例化服务器对象
brpc::Server server;
// 4. 向服务器添加服务
int ret = server.AddService(&calServer, brpc::SERVER_DOESNT_OWN_SERVICE);
if(ret == -1) {
std::cout << "AddServer failed!" << std::endl;
return -1;
}
// 5. 启动服务器
ret = server.Start(9000, &options); // 等价于 "0.0.0.0:9000"
if(ret == -1) {
std::cout << "Start failed!" << std::endl;
return -1;
}
// 5. 等待服务器停止
server.RunUntilAskedToQuit();
return 0;
}
lfz@U22:~/WorkSpace/thirdPartyUse/Brpc$ ./sync_server
W0303 23:36:50.700060 9623 0 /home/lfz/WorkSpace/gitSpace/brpc-1.12.1/src/bthread/bthread.cpp:471] Fail to set concurrency by tag: 0, tag concurrency should be larger than old oncurrency. old concurrency: 9, new concurrency: 4
I0303 23:36:50.713706 9623 0 /home/lfz/WorkSpace/gitSpace/brpc-1.12.1/src/brpc/server.cpp:1200] Server[CalServerImpl] is serving on port=9000.
I0303 23:36:50.716675 9623 0 /home/lfz/WorkSpace/gitSpace/brpc-1.12.1/src/brpc/server.cpp:1203] Check out http://U22:9000 in web browser.
但是想要通过 brpc 实现客户端调用,我们还需要了解相关的 brpc 客户端核心类!
客户端核心类
namespace brpc {
// ==============================
// 1. 客户端信道配置选项
// 作用:设置 brpc 客户端的连接、请求、协议参数
// ==============================
struct ChannelOptions {
// 连接服务器超时时间
int32_t connect_timeout_ms;// Default: 200 (milliseconds)
// RPC 请求整体超时时间
int32_t timeout_ms;// Default: 500 (milliseconds)
// 请求最大重试次数
int max_retry;// Default: 3
// 使用的序列化协议类型
AdaptiveProtocolType protocol;
//....
};
// ==============================
// 2. 客户端通信信道类
// 作用:建立与服务端的连接、管理网络通信、发送/接收 RPC 数据
// ==============================
class Channel : public ChannelBase {
// 初始化客户端信道,绑定服务端地址与配置
int Init(const char* server_addr_and_port, const ChannelOptions* options);
};
} // namespace brpc
ChannelOptions 客户端配置结构体
ChannelOptions 是 brpc 客户端的配置参数集合,专门用来控制客户端与服务端建立连接、发起请求的各项行为。其中:
connect_timeout_ms 表示客户端与服务端建立连接的超时时间,单位是毫秒,默认 200ms。如果在这个时间内无法和服务端建立 TCP 连接,框架会直接判定连接失败。
timeout_ms 表示单次 RPC 请求的整体超时时间,从请求发出开始计时,默认 500ms。如果超过这个时间还没有收到服务端响应,本次调用会直接超时失败。
max_retry 用于设置请求失败后的最大自动重试次数,默认最多重试 3 次,当网络抖动或请求超时,brpc 会自动重试,提升调用成功率。
protocol 用于指定客户端与服务端之间的通信协议,最常用的是 baidu_std,即 brpc 高性能二进制协议,它决定了数据如何序列化和传输。
这些配置会在 Channel 初始化时传入,直接决定客户端的网络行为和调用稳定性。
Channel 客户端通信信道类
Channel 是 brpc 客户端的核心通信类,它负责客户端与服务端之间的网络连接管理、数据收发、协议解析,是所有 RPC 调用的底层通道。
Init 是 Channel 最关键的初始化方法:
- 第一个参数 server_addr_and_port 是服务端的地址与端口,格式为 "ip:port",用来定位目标服务端。
- 第二个参数 options 就是上面的 ChannelOptions 配置,用来指定超时、重试、协议等行为。
- 方法返回 0 表示初始化成功,客户端可以正常发起 RPC 调用;非 0 表示初始化失败。
Channel 本身不直接处理业务数据,而是为 Protobuf 生成的 Stub 调用提供底层网络支持。我们在客户端使用的 CalService_Stub,必须依赖 Channel 才能真正把请求发送到服务端。
这里补充一下对应的 ProtoBuf 核心类介绍:Stub 客户端存根类。
Stub 是由 Protobuf 自动生成的,专门用于客户端发起 RPC 调用的代理类,不需要我们手动实现。
客户端存根类(Stub)
namespace cal {
class CalService_Stub {
public:
explicit CalService_Stub(::google::protobuf::RpcChannel* channel);
void Add(
::google::protobuf::RpcController* controller,
const ::cal::AddReq* request,
::cal::AddRsp* response,
::google::protobuf::Closure* done);
};
}
CalService_Stub 客户端存根类
CalService_Stub 是 Protobuf 根据 .proto 文件自动生成的客户端调用存根类,它是客户端发起 RPC 调用的直接入口。
它的作用非常纯粹:将业务层的方法调用(Add)转换为底层网络数据发送,开发者不需要关心网络传输、序列化、协议封装等细节,只需要像调用本地函数一样调用 Stub 提供的方法,就能完成远程调用。
explicit CalService_Stub(::google::protobuf::RpcChannel* channel);
构造函数必须传入一个已经初始化完成的 brpc::Channel 对象,因为 Stub 本身不处理网络,只依赖 Channel 完成底层通信。Channel 是 Stub 的网络载体,Stub 是 Channel 的业务调用入口。
void Add(
::google::protobuf::RpcController* controller,
const ::cal::AddReq* request,
::cal::AddRsp* response,
::google::protobuf::Closure* done);
这就是客户端发起 RPC 调用的方法,与服务端实现的 Add 方法参数完全一致,一一对应。
- controller:使用
brpc::Controller,管理本次调用的超时、错误、重试、HTTP 头信息。 - request:填充客户端要发送的请求数据(num1、num2)。
- response:用于接收服务端返回的计算结果(result)。
- done:同步调用传
NULL即可,异步调用才需要传入闭包。
调用 stub.Add() 后,客户端会阻塞等待服务端响应,返回后直接从 response 中读取结果。
简单来说:Channel 就是客户端与服务端之间的 “通信高速公路”,Stub 是在这条路上跑的车,RPC 请求就是运输的货物。
所以最终的整体的逻辑就是:
客户端调用 stub.Add() → 网络传输 → 服务端执行你重写的 Add()
服务端:需要继承 + 实现 Add ()
服务端的
Add()是 纯虚函数,你必须写业务逻辑(加法计算)。
客户端:Add () 已经被 Protobuf 自动生成好了!
客户端的
Add()是 已经实现好的调用函数,
这就是一次完整的 RPC 远程过程调用!
- Server:服务端,负责监听端口、接收请求、分发处理
- Channel:客户端,负责连接服务器、发送请求、接收响应
- Controller:两端共用,管理请求上下文、错误、超时、HTTP 信息
- Protobuf:两端共用,定义数据结构与 RPC 接口
那我们也来实现一个对应的同步的客户端:
sync_client.cc:
#include <brpc/channel.h>
#include "cal.pb.h"
int main(int argc, char* argv[]) {
// 1. 实例化 ChannelOptions 进行参数配置
brpc::ChannelOptions options;
options.protocol = "baidu_std";
// 2. 实例化 Channel 信道对象
brpc::Channel channel;
channel.Init("192.168.10.129:9000", &options);
// 3. 实例化 CalService_stub 对象 -- 用于发起 rpc 请求
cal::CalService_Stub stub(&channel);
brpc::Controller cntl;
cal::AddReq request;
cal::AddRsp response;
request.set_num1(10);
request.set_num2(20);
stub.Add(&cntl, &request, &response, NULL);
if (cntl.Failed() == true) {
std::cout << "rpc请求失败: " << cntl.ErrorText() << std::endl;
return -1;
}
std::cout << response.result() << std::endl;
return 0;
}
启动上面服务器,再启动客户端:
lfz@U22:~/WorkSpace/thirdPartyUse/Brpc$ ./sync_client
30
上面通过简单的结合演示,希望你已经稍微懂了一点了,下面就是进阶的啦!
异步
异步客户端与异步服务端:
异步客户端:在发起请求的时候,传入 closure 对象,内部传入一个回调处理函数。
async_client.cc:
#include <brpc/channel.h>
#include <iostream>
#include "cal.pb.h"
// 异步回调函数:收到服务端响应后自动进入这里执行
void OnRpcResponse(brpc::Controller* cntl, cal::AddReq* request, cal::AddRsp* response) {
// 1. 判断RPC是否调用失败
if (cntl->Failed()) {
std::cout << "【异步RPC失败】: " << cntl->ErrorText() << std::endl;
} else {
// 2. 调用成功,打印响应结果
std::cout << "【异步RPC成功】结果 = " << response->result() << std::endl;
}
// 异步场景:必须手动释放申请的Controller/request/response
delete cntl;
delete request;
delete response;
}
int main(int argc, char* argv[]) {
// ====================== 1. 配置与初始化Channel ======================
brpc::ChannelOptions options;
options.protocol = "baidu_std";
brpc::Channel channel;
channel.Init("192.168.10.129:9000", &options);
// ====================== 2. 创建Stub ======================
cal::CalService_Stub stub(&channel);
// ====================== 异步关键:全部在堆上创建 ======================
brpc::Controller* cntl = new brpc::Controller();
cal::AddReq* request = new cal::AddReq();
cal::AddRsp* response = new cal::AddRsp();
// 设置请求参数
request->set_num1(10);
request->set_num2(20);
// ====================== 3. 发起异步RPC调用 ======================
// 重点:最后一个参数不再是NULL,而是传入闭包回调
google::protobuf::Closure* done = brpc::NewCallback(
&OnRpcResponse, cntl, request, response);
// 发起异步调用(调用立刻返回,不会阻塞)
stub.Add(cntl, request, response, done);
// ====================== 4. 等待异步回调完成 ======================
// 客户端主线程不能直接退出,否则请求还没发出去就结束了
getchar();
return 0;
}
异步服务端:将业务处理过程,放在其他执行流中进行,业务处理完毕后,执行 done->Run();(在哪里处理的业务,就在那里执行 run())
async_server.cc:
#include <butil/logging.h>
#include <brpc/server.h>
#include <thread>
#include "cal.pb.h"
class CalServiceImpl : public cal::CalService {
public:
CalServiceImpl() {}
~CalServiceImpl() {}
void Add(::google::protobuf::RpcController* controller,
const ::cal::AddReq* request,
::cal::AddRsp* response,
::google::protobuf::Closure* done) override {
std::thread thr([=](){
//当done_guard被释放的时候执行done->Run()来完成本次rpc调用
brpc::ClosureGuard done_guard(done);
int result = request->num1() + request->num2();
response->set_result(result);
// std::this_thread::sleep_for(std::chrono::seconds(3));
});
thr.detach();
std::cout << "=========================\n";
}
};
int main(int argc, char *argv[])
{
// 1. 实例化计算服务对象
CalServiceImpl cal_service;
// 2. 定义服务器配置对象
brpc::ServerOptions options;
options.idle_timeout_sec = -1;
// 3. 实例化服务器对象
brpc::Server server;
// 4. 向服务器添加服务
int ret = server.AddService(&cal_service, brpc::SERVER_DOESNT_OWN_SERVICE);
if (ret == -1) {
std::cout << "AddService failed" << std::endl;
return -1;
}
// 4. 启动服务器
ret = server.Start(9000, &options);
if (ret == -1) {
std::cout << "Start failed" << std::endl;
return -1;
}
// 5. 等待服务器停止
server.RunUntilAskedToQuit();
return 0;
}
HTTP 相关
// ------------------------------
// URI 类:统一资源标识符,用于解析和操作 HTTP URL
// 作用:管理 URL 的主机、端口、路径、查询参数(?key=value)
// ------------------------------
class URI {
public:
// 查询参数的键值对映射类型(底层为高效哈希表)
typedef butil::FlatMap<std::string, std::string> QueryMap;
// 查询参数迭代器类型
typedef QueryMap::const_iterator QueryIterator;
// 从完整 HTTP URL 中解析并设置 URI(如 "http://127.0.0.1:9000/add?a=1&b=2")
// 成功返回 0,失败返回 -1
int SetHttpURL(const std::string& url);
// 设置 URL 路径(如 /add /api/calc)
void set_path(const std::string& path);
// 设置主机名/IP 地址
void set_host(const std::string& host);
// 设置端口号
void set_port(int port);
// 一次性设置 “主机:端口”(如 "127.0.0.1:9000")
void SetHostAndPort(const std::string& host_and_optional_port);
// 根据 key 删除查询参数
// 返回值:1=删除成功,0=无此key
size_t RemoveQuery(const char* key);
// 获取主机IP/域名
const std::string& host() const { return _host; }
// 获取端口号
int port();
// 获取 URL 路径
const std::string& path();
// 获取用户信息(极少用,如 user:pass@host)
const std::string& user_info();
// 获取原始查询串(如 "a=1&b=2")
const std::string& query() const;
// 根据 key 获取查询参数值,找不到返回 nullptr
const std::string* GetQuery(const std::string& key);
// 添加/修改一个查询参数 key=value
void SetQuery(const std::string& key, const std::string& value);
// 查询参数迭代器(遍历 ? 后面所有参数)
QueryIterator QueryBegin();
QueryIterator QueryEnd();
// 获取查询参数的数量
size_t QueryCount();
};
// ------------------------------
// HTTP 请求方法枚举
// 定义 HTTP 标准请求类型:GET/POST/PUT/DELETE 等
// ------------------------------
enum HttpMethod {
HTTP_METHOD_DELETE = 0, // DELETE 删除资源
HTTP_METHOD_GET = 1, // GET 获取资源(最常用)
HTTP_METHOD_HEAD = 2, // HEAD 仅获取响应头,无正文
HTTP_METHOD_POST = 3, // POST 提交数据(最常用)
HTTP_METHOD_PUT = 4, // PUT 更新资源
};
// 将 HttpMethod 枚举转为字符串(如 GET → "GET")
const char *HttpMethod2Str(HttpMethod http_method);
// 将字符串方法转为枚举(如 "POST" → HTTP_METHOD_POST)
bool Str2HttpMethod(const char* method_str, HttpMethod* method);
// ------------------------------
// HTTP 标准状态码常量定义
// ------------------------------
static const int HTTP_STATUS_OK = 200; // 请求成功
static const int HTTP_STATUS_BAD_REQUEST = 400; // 客户端请求错误
static const int HTTP_STATUS_UNAUTHORIZED = 401; // 未授权
static const int HTTP_STATUS_FORBIDDEN = 403; // 禁止访问
static const int HTTP_STATUS_NOT_FOUND = 404; // 资源不存在
static const int HTTP_STATUS_METHOD_NOT_ALLOWED = 405; // 不允许该请求方法
static const int HTTP_STATUS_INTERNAL_SERVER_ERROR = 500; // 服务端内部错误
// ------------------------------
// HttpHeader 类:HTTP 报文头管理
// 管理:请求方法、URI、响应状态码、请求头/响应头、Content-Type
// ------------------------------
class HttpHeader {
public:
// 获取 Content-Type(如 application/json)
const std::string& content_type();
// 设置 Content-Type
void set_content_type(const std::string& type);
// 根据 key 获取请求/响应头的值,找不到返回 nullptr
const std::string* GetHeader(const std::string& key);
// 设置请求/响应头键值对
void SetHeader(const std::string& key, const std::string& value);
// 获取本次 HTTP 请求的 URI 对象(路径、参数、主机、端口)
const URI& uri() const { return _uri; }
// 获取 HTTP 请求方法(GET/POST...)
HttpMethod method() const { return _method; }
// 设置 HTTP 请求方法
void set_method(const HttpMethod method);
// 获取 HTTP 响应状态码(200/404/500)
int status_code();
// 设置 HTTP 响应状态码
void set_status_code(int status_code);
};
// ------------------------------
// Controller 类:RPC/HTTP 调用上下文控制器
// 管理一次请求的全部信息:超时、重试、请求头、响应头、附件、错误信息
// ------------------------------
class Controller : public google::protobuf::RpcController {
public:
// 设置 RPC/HTTP 请求超时时间(毫秒)
void set_timeout_ms(int64_t timeout_ms);
// 设置最大重试次数
void set_max_retry(int max_retry);
// 重置控制器状态,可复用该 cntl 发起下一次请求
void Reset();
// 获取响应对象(protobuf 消息)
google::protobuf::Message* response();
// 获取【HTTP 响应头】(客户端:读取结果;服务端:设置结果)
HttpHeader& http_response();
// 获取【响应附件】(HTTP 响应正文存放处)
butil::IOBuf& response_attachment();
// 获取【HTTP 请求头】(客户端:设置;服务端:读取)
HttpHeader& http_request();
// 获取【请求附件】(HTTP 请求正文存放处)
butil::IOBuf& request_attachment();
// 判断本次请求是否失败
bool Failed();
// 获取错误信息字符串
std::string ErrorText();
// 请求响应完成后的回调函数类型
using AfterRpcRespFnType = std::function<void(
Controller* cntl,
const google::protobuf::Message* req,
const google::protobuf::Message* res)>;
// 设置响应完成后的回调钩子
void set_after_rpc_resp_fn(AfterRpcRespFnType&& fn);
};
// ------------------------------
// butil::IOBuf 类:高效二进制缓冲区
// brpc 专门用来存放 HTTP 正文、附件、序列化数据
// ------------------------------
namespace butil {
class IOBuf {
public:
// 向缓冲区追加字符串,成功返回0,失败-1
int append(const std::string& s);
// 将缓冲区所有数据转为 std::string
std::string to_string() const;
// 清空缓冲区
void clear();
// 判断是否为空
bool empty() const;
// 获取缓冲区数据长度
size_t length() const;
};
} // namespace butil
在 brpc 搭建的 HTTP 客户端 / 服务端中,所有请求与处理都是通过 brpc::Controller 来完成的:
brpc::Controller::http_response()
- 对于客户端:用于获取响应头部信息
- 对于服务端:用于设置响应头部信息
butil::IOBuf& response_attachment()
- 对于客户端:用于获取响应正文
- 对于服务端:用于设置响应正文
brpc::Controller::http_request()
- 对于客户端:用于设置请求头部信息
- 对于服务端:用于获取请求信息
butil::IOBuf& request_attachment()
- 对于客户端:用于设置请求正文
- 对于服务端:用于获取请求正文
在搭建 HTTP 客户端时,有一处特殊配置:我们需要告诉 brpc 客户端,要发送的是 HTTP 请求,而不是 RPC 请求:
ChannelOptions options;
options.protocol = brpc::PROTOCOL_HTTP;
在搭建 HTTP 服务端时,也有特殊之处:
- brpc 支持通过 proto 文件中定义的服务,来支持对 JSON 格式 HTTP 请求正文的处理。
- 这相当于将 HTTP 请求当作 RPC 请求进行处理:框架会自动将 JSON 请求解析后,放入到
request对象中,而不是放在cntl中。 - 如果请求正文不是 JSON 格式,并且也没有通过 proto 定义服务,则框架会将请求信息直接放在
cntl中。
http_client.cc
#include <brpc/channel.h>
#include "cal.pb.h"
int main(int argc, char *argv[])
{
// 0. 实例化ChannelOptions进行参数配置,设置协议为HTTP协议请求
brpc::ChannelOptions options;
options.protocol = brpc::PROTOCOL_HTTP;
// 1. 实例化Channel信道对象--
brpc::Channel channel;
channel.Init("192.168.10.129:9000", &options);
// 3. 实例化CalService_stub对象--用于发起rpc请求。
brpc::Controller cntl;
cntl.http_request().set_method(brpc::HTTP_METHOD_POST);//设置请求方法
cntl.http_request().uri().set_path("/CalService/Hello"); // 设置请求资源路径 -- 要与服务端对应的服务接口名称对应起来
cntl.http_request().SetHeader("Content-Type", "text/plain");
cntl.request_attachment().append("Hello World");
channel.CallMethod(nullptr, &cntl, nullptr, nullptr, nullptr);
if (cntl.Failed() == false){
std::cout << cntl.response_attachment() << std::endl;
}else {
std::cout << cntl.ErrorText() << std::endl;
}
return 0;
}
http_server.cc
#include <butil/logging.h>
#include <brpc/server.h>
#include "cal.pb.h"
class CalServiceImpl : public cal::CalService {
public:
CalServiceImpl() {}
~CalServiceImpl() {}
void Add(::google::protobuf::RpcController* controller,
const ::cal::AddReq* request,
::cal::AddRsp* response,
::google::protobuf::Closure* done) override {
//当done_guard被释放的时候执行done->Run()来完成本次rpc调用
brpc::ClosureGuard done_guard(done);
int result = request->num1() + request->num2();
response->set_result(result);
}
void Hello(::google::protobuf::RpcController* controller,
const ::cal::helloReq* request,
::cal::helloRsp* response,
::google::protobuf::Closure* done) override {
//别忘了设置Closure管理
brpc::ClosureGuard done_guard(done);
brpc::Controller* cntl = (brpc::Controller*)controller;
const auto& headers = cntl->http_request();
std::cout << "Method:" << brpc::HttpMethod2Str(headers.method()) << std::endl;
std::cout << "Body:" << cntl->request_attachment().to_string() << std::endl;
cntl->response_attachment().append("回显:" + cntl->request_attachment().to_string());
cntl->http_response().set_status_code(200);
}
};
int main(int argc, char *argv[])
{
// 1. 实例化计算服务对象
CalServiceImpl cal_service;
// 2. 定义服务器配置对象
brpc::ServerOptions options;
options.idle_timeout_sec = -1;
// 3. 实例化服务器对象
brpc::Server server;
// 4. 向服务器添加服务
int ret = server.AddService(&cal_service, brpc::SERVER_DOESNT_OWN_SERVICE);
if (ret == -1) {
std::cout << "AddService failed" << std::endl;
return -1;
}
// 4. 启动服务器
ret = server.Start(9000, &options);
if (ret == -1) {
std::cout << "Start failed" << std::endl;
return -1;
}
// 5. 等待服务器停止
server.RunUntilAskedToQuit();
return 0;
}
brpc 最强大、最方便的地方,就是它让开发者几乎不用关心自己写的是 RPC 服务还是 HTTP 服务。我们不需要为 RPC 写一套代码,再为 HTTP 写一套代码;同一套服务接口、同一份业务逻辑,可以同时支持高性能 RPC 调用和标准 HTTP 接口调用。框架底层会自动识别协议、自动处理数据,开发者完全不用关心网络差异,极大降低开发成本。
brpc 能做到 RPC / HTTP 无缝通用,核心依靠的就是 Protobuf 定义的 .proto 服务接口文件。你只需要在 proto 里写好服务和方法,brpc 就会自动把 RPC 接口映射成 HTTP 接口:
- 服务名 → 自动变成 URL 路径
- 方法名 → 自动变成 HTTP 访问路径
- 请求参数 → 自动支持 HTTP JSON 格式
- 响应结果 → 自动返回 JSON
不用写路由、不用写解析、不用处理 HTTP 报文,框架全部自动完成。
brpc 会自动帮我们完成所有 HTTP 相关的工作:
- 自动识别 GET/POST 等请求方法
- 自动检测、解析 JSON 请求体
- 自动把 JSON 转成 Protobuf 对象
- 自动把响应结果转回 JSON 返回给前端
- 自动设置 Content-Type、状态码、响应头
我们只需要实现 proto 里定义的服务方法,不用管是 RPC 还是 HTTP,不用关心数据格式,不用处理路径映射,真正做到 “一次编写,同时提供 RPC + HTTP 服务”。
在 .proto 里定义了:
service CalService {
rpc Add(AddReq) returns (AddRsp);
rpc Hello(helloReq) returns (helloRsp);
}
brpc 会自动把它变成 HTTP 接口:
- 服务名 = 路径第一层
- 方法名 = 路径第二层
所以:
CalService + Hello → /CalService/Hello
CalService + Add → /CalService/Add
支持任意 HTTP 工具调用!
- 路径:POST /CalService/Hello
- 可发:text/plain、json、任意格式
- 服务端会打印:请求方法 + 请求体
- 服务端会返回:
回显:xxx
封装
其实我们这里使用 RPC 不单纯就是为了搭建一个双方的服务器,这有点糟蹋他了,其实我们主要是应用在分布式系统中的,和注册中心搭配来试下服务的注册和发现!
1. RPC 为什么要和注册中心搭配?
在分布式系统里,服务不是孤立的,而是互相调用的。如果客户端直接硬编码服务端地址,会有两个大问题:
- 服务端扩容、缩容、迁移时,客户端都要改代码,非常麻烦。
- 单点故障:如果那个唯一的服务端挂了,整个调用就断了。
所以我们需要一个注册中心,它就像一个 “服务通讯录”:
- 服务端启动时,主动把自己的地址注册到注册中心,告诉大家 “我能提供这个服务”。
- 客户端发起调用前,先去注册中心 “查通讯录”,拿到所有能提供这个服务的节点地址。
- 客户端再通过负载均衡策略(比如 UU 轮询)选一个节点发起 RPC 调用。
这样一来:
- 服务端可以水平扩展,多节点同时提供服务,提高并发能力。
- 某个节点挂了,注册中心会把它从列表里剔除,客户端自动选其他节点,保证高可用。
- 客户端完全不用关心服务端在哪、有多少个,只需要知道 “服务名” 就行。
brpc 本身是一个强大的底层库,但直接用起来还是比较繁琐,比如要自己管理 channel、处理异步回调、自己做服务发现和负载均衡。这次二次封装的目的,就是把这些 “脏活累活” 都包起来,让上层业务只关心 “调用哪个服务、传什么参数、拿到什么结果”,不用管底层细节。
整个系统围绕 “服务发现 + 负载均衡” 来设计:
- 服务端(RPC A):启动后把自己的地址(如
192.168.65.130:9000)注册到注册中心。 - 注册中心:维护一个 “服务名 → 节点地址列表” 的映射表,是所有服务的 “通讯录”。
- 客户端:通过服务发现,从注册中心拿到服务节点列表,再通过负载均衡策略(如 RR 轮询)选择一个节点发起调用。
Channel 管理:自动维护连接池
为了避免每次调用都重新建立连接,我们设计了两层 channel 管理:
Channels类:管理同一个服务下的多个节点 channel,用std::vector存所有 channel,用int idx实现轮询(Round-Robin)负载均衡,用std::unordered_map方便增删节点。SvcChannels类:是更高层的管理者,按服务名(如 "user")来管理不同服务的Channels集合,还支持 “预关注服务”,只管理你关心的服务,避免资源浪费。
Closure 封装:让异步回调更现代
brpc 的异步回调依赖 google::protobuf::Closure,但它和现代 C++ 的 std::function、lambda 不太兼容。所以我们封装了一个 ClosureFactory:
- 用户只需要传一个
std::function<void()>(比如 lambda)。 - 工厂内部会把它包装成
Closure对象,再返回给 brpc 使用。 - 这样既保持了 brpc 的兼容性,又让上层代码更简洁、更现代。
Server 工厂:一键启动服务
为了简化服务端的启动流程,我们做了一个 RpcServerFactory:
- 只需要传入端口号和服务实例(如
CalServiceImpl),就能一键创建并启动一个 brpc 服务。 - 把
Server对象用std::shared_ptr管理,自动处理生命周期,避免内存泄漏。
封装代码:
rpc.h
#pragma once
#include <butil/logging.h>
#include <brpc/server.h>
#include <brpc/channel.h>
#include <mutex>
#include <optional>
namespace rose {
using ChannelPtr = std::shared_ptr<brpc::Channel>;
class Channels {
public:
using ptr = std::shared_ptr<Channels>;
Channels();
// 新增节点
void Insert(const std::string& addr);
// 删除节点
void Remove(const std::string& addr);
// 获取节点信道
ChannelPtr Select();
// 获取节点地址
std::optional<std::string> SelectAddr();
private:
std::mutex m_mtx;
std::string m_serverName;
uint32_t m_index;
std::vector<std::pair<std::string, ChannelPtr>> m_channels;
};
class SvcChannels {
public:
using ptr = std::shared_ptr<SvcChannels>;
SvcChannels() = default;
void SetWatch(const std::string &svcName);
void AddNode(const std::string &svcName, const std::string &addr);
void DelNode(const std::string &svcName, const std::string &addr);
ChannelPtr GetNode(const std::string &svcName);
std::optional<std::string> GetNodeAddr(const std::string &svcName);
private:
Channels::ptr _Channels(const std::string &svcName);
private:
std::mutex m_mtx;
std::unordered_map<std::string, Channels::ptr> m_maps;
};
class ClosureFactory {
public:
using callback_t = std::function<void()>;
static google::protobuf::Closure* Create(callback_t &&cb);
private:
struct Object{
using ptr = std::shared_ptr<Object>;
callback_t callback;
};
static void AsyncCallback(const Object::ptr obj);
};
class RpcServerFactory {
public:
// 默认svc是堆上new出来的对象,将管理权移交给rpc服务器进行管理
static std::shared_ptr<brpc::Server> Create(int port, google::protobuf::Service *svc);
};
}
rpc.cc
#include "rpc.h"
#include "log.h"
namespace rose {
//===============================================================================================
Channels::Channels() : m_index(0) {}
// 新增节点
void Channels::Insert(const std::string& addr) {
std::unique_lock<std::mutex>(m_mtx);
// 实例化 channel 对象
auto channel = std::make_shared<brpc::Channel>();
brpc::ChannelOptions options;
options.protocol = "baidu_std";
options.timeout_ms = 30000;
channel->Init(addr.c_str(), &options);
// 将对象添加到 server 中
m_channels.push_back(std::make_pair(addr, channel));
}
// 删除节点
void Channels::Remove(const std::string& addr) {
std::unique_lock<std::mutex>(m_mtx);
for(auto it = m_channels.begin(); it != m_channels.end(); ++it) {
if(addr == it->first) {
m_channels.erase(it);
break;
}
}
}
// 获取节点信道
ChannelPtr Channels::Select() {
std::unique_lock<std::mutex>(m_mtx);
if(m_channels.size() == 0) return ChannelPtr();
size_t index = (m_index++) % m_channels.size();
return m_channels[index].second;
}
// 获取节点地址
std::optional<std::string> Channels::SelectAddr() {
std::unique_lock<std::mutex>(m_mtx);
if(m_channels.size() == 0) return std::optional<std::string>();
size_t index = (m_index++) % m_channels.size();
return m_channels[index].first;
}
//===============================================================================================
Channels::ptr SvcChannels::_Channels(const std::string &svcName) {
// 限定作用域只是为了保护获取集合的过程,而不需要保护添加结果的过程
std::unique_lock<std::mutex>(m_mtx);
auto it = m_maps.find(svcName);
if(it == m_maps.end()) return Channels::ptr();
return it->second;
}
void SvcChannels::SetWatch(const std::string &svcName) {
// 针对指定服务,初始化构造对象,并添加管理
std::unique_lock<std::mutex>(m_mtx);
auto channels = std::make_shared<Channels>();
m_maps.insert(std::make_pair(svcName, channels));
}
void SvcChannels::AddNode(const std::string &svcName, const std::string &addr) {
// 获取指定服务名的服务集合,找到才进行服务节点的添加,否则则不做任何操作
Channels::ptr channels = _Channels(svcName);
if(!channels) {
LOG_DEBUG("{} 有节点上线: {}, 未找到管理对象", svcName, addr);
return;
}
LOG_DEBUG("添加 {} 服务节点 {} 管理", svcName, addr);
return channels->Insert(addr);
}
void SvcChannels::DelNode(const std::string &svcName, const std::string &addr) {
// 获取集合,通过集合,删除节点
Channels::ptr channels = _Channels(svcName);
if (!channels) {
LOG_DEBUG("{} 有节点下线:{}, 未找到管理对象", svcName, addr);
return;
}
LOG_DEBUG("移除 {} 服务节点 {} 管理", svcName, addr);
return channels->Remove(addr);
}
ChannelPtr SvcChannels::GetNode(const std::string &svcName) {
// 找到"UU轮转"的当前服务
Channels::ptr channels = _Channels(svcName);
if(!channels) return ChannelPtr();
return channels->Select();
}
std::optional<std::string> SvcChannels::GetNodeAddr(const std::string &svcName) {
Channels::ptr channels = _Channels(svcName);
if (!channels) { return std::optional<std::string>(); }
return channels->SelectAddr();
}
//===============================================================================================
google::protobuf::Closure* ClosureFactory::Create(callback_t &&cb) {
auto obj = std::make_shared<Object>();
obj->callback = std::move(cb);
return brpc::NewCallback(&ClosureFactory::AsyncCallback, obj);
}
void ClosureFactory::AsyncCallback(const Object::ptr obj) {
obj->callback();
}
//===============================================================================================
std::shared_ptr<brpc::Server> RpcServerFactory::Create(int port, google::protobuf::Service *svc){
brpc::ServerOptions options;
options.idle_timeout_sec = -1;
auto server = std::make_shared<brpc::Server>();
int ret = server->AddService(svc, brpc::SERVER_OWNS_SERVICE);
if (ret == -1) {
LOG_ERROR("添加RPC服务失败!");
abort();
}
ret = server->Start(port, &options);
if (ret == -1) {
LOG_ERROR("启动服务器失败!");
abort();
}
return server;
}
}
2. 为什么要对 brpc 二次封装?
brpc 本身已经很强大了,但直接用还是有点 “底层”:
- 你要自己管理每个服务的多个 channel,自己实现负载均衡。
- 异步回调用的是
Closure,和现代 C++ 的 lambda、std::function不太搭。 - 服务端启动、服务注册、服务发现这些流程,每次都要写重复代码。
所以二次封装的目的,就是把这些 “脏活累活” 都包起来:
- Channel 管理:自动维护每个服务的连接池,用轮询实现负载均衡,节点增删自动同步。
- Closure 工厂:把 lambda 包装成 brpc 能识别的
Closure,让异步回调更现代。 - Server 工厂:一键创建、启动服务,自动处理生命周期。
最终效果就是:上层业务只需要关心 “调用哪个服务、传什么参数”,不用管底层的服务发现、负载均衡、连接管理,像调用本地函数一样简单,同时又能享受到分布式系统的高可用和高性能。
更多推荐




所有评论(0)