什么是 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 google 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 还内置了超多工业级特性,满足各种复杂场景:

  1. 多协议支持:一个端口同时支持 brpc 自研 baidu_std、gRPC、HTTP/HTTPS、Redis、Memcached、Thrift 等,无需部署多个服务;
  2. 高性能:基于协程(bthread)实现,无锁化设计,单服务可支撑百万 QPS,延迟低至微秒级;
  3. 灵活的调用方式:支持同步、异步、半同步调用,组合 Channel 实现分库分表、并发访问;
  4. 完善的容错机制:内置超时、重试、负载均衡(轮询、随机、一致性哈希)、命名服务(DNS、ZooKeeper、etcd);
  5. 流式传输:支持大块数据的流式上传 / 下载,适合文件传输、视频流等场景;
  6. 服务端推送:通过长连接实现服务端主动向客户端推送数据,适合消息通知、实时监控;
  7. 丰富的工具链:自带 CPU、内存、锁竞争分析工具,HTTP 调试界面,日志系统,无需额外集成;
  8. 跨平台:支持 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 会自动生成两类核心代码:

数据结构类AddReqAddRsp

  • 对应图中的 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

  • 这是从网络端反序列化后的客户端请求对象
  • 服务端通过它读取客户端传来的 num1num2 等参数。

AddRsp* response

  • 这是待填充的服务端响应对象
  • 业务处理完成后,服务端将计算结果(如 result)写入这个对象,由框架负责序列化后返回给客户端。

Closure* done

  • 这是一个闭包回调,用于标记本次 RPC 调用的结束。
  • 很多 RPC 框架(包括 brpc)都支持异步操作:业务处理可以不在 Add 函数内进行,而是交给其他线程。当业务处理完成后,必须手动调用 done->Run(),框架才会将 response 序列化并发送回客户端,本次调用才算真正结束。

所以:

Protobuf 负责生成 AddReqAddRspCalService 这些 “骨架” 代码。

用户的核心工作,就是继承 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 配置运行。

StopJoin 用于停止服务并等待线程退出,而 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 工厂:一键创建、启动服务,自动处理生命周期。

最终效果就是:上层业务只需要关心 “调用哪个服务、传什么参数”,不用管底层的服务发现、负载均衡、连接管理,像调用本地函数一样简单,同时又能享受到分布式系统的高可用和高性能。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐