【C++ 脚手架】brpc 的介绍与使用

brpc 介绍与使用

一、什么是 rpc?

RPC(Remote Procedure Call,远程过程调用) 是一种跨网络的函数调用技术 。它的核心目标是:让一台计算机上的程序,能够像调用本地函数一样,直接调用另一台计算机上的函数,而开发者无需手动编写网络通信的细节代码。

RPC 主要应用在分布式架构中,由于引用分布式架构产生了一系列新的问题,而 RPC 正是解决这些问题的关键。

  • 服务发现:RPC 集成服务注册中心(如 etcd),客户端自动从注册中心获取健康的服务实例地址。

  • 高可用:只需关注业务逻辑(我该调用哪个函数,传入什么参数,拿到什么结果)。至于网络通信、错误重试等复杂问题,全部由 RPC 框架解决。

  • 数据一致性:RPC支持跨语言的高性能序列化协议(如 Protobuf),保证数据格式统一且高效。

RPC 解决的核心问题是让构建分布式系统变得像编写本地程序一样简单。

二、什么是 brpc?

bRPC 是一个由百度开源的高性能 RPC(远程过程调用)框架 ,全称是 better RPC (曾被称为 baidu-rpc)。它用 C++ 编写,旨在帮助开发者快速构建高性能、高可靠分布式系统

三、brpc 安装

先安装依赖

bash 复制代码
sudo apt-get install -y git g++ make libssl-dev libprotobuf-dev libprotoc-dev protobuf-compiler libleveldb-dev protobuf-compiler-grpc

安装 brpc

bash 复制代码
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

四、brpc 常见类与接口

4.1 日志输出类与接口

头文件:#include <butil/logging.h>

brpc 库内部有自己的日志输出模块,主要是禁用日志输出,避免被 brpc 的日志干扰。

cpp 复制代码
namespace logging {

enum LoggingDestination {
    LOG_TO_NONE = 0
};

struct BUTIL_EXPORT LoggingSettings {
    LoggingSettings();
    LoggingDestination logging_dest;
};

bool InitLogging(const LoggingSettings& settings);

}

关闭日志输出

cpp 复制代码
#include <butil/logging.h>

logging::LoggingSettings logging_setting;
logging_setting.logging_dest = logging::LOG_TO_NONE;
logging::InitLogging(logging_setting);

4.2 protobuf 类与接口

Closure 是 Protobuf 定义的回调接口,RPC 服务实现必须在处理结束时调用其 Run() 方法,以触发框架完成响应的序列化与网络发送。

brpc::ClosureGuard 是一个基于 RAII 的守卫类,无论函数如何退出,都会自动调用 ClosureRun 方法。

RpcController:是 RPC 调用的上下文载体,维护其成功或失败状态

  • 服务端:RpcControllerFailed() 状态由框架自动维护,服务端只需在需要返回业务错误时才主动调用 SetFailed(),其他异常框架会自动填充。

  • 客户端:通过 RpcController 检查调用结果。

inline Closure* NewCallback(void (*function)()); 只有在 创建异步 RPC 客户端 时才需要 Closure,用于指定 RPC 完成后的回调函数;同步调用传 nullptr 即可

cpp 复制代码
namespace google {
    namespace protobuf {
        class PROTOBUF_EXPORT Closure {
            public:
                Closure() {}
                    virtual ~Closure();
                    virtual void Run() = 0;
        };

        inline Closure* NewCallback(void (*function)());

        class PROTOBUF_EXPORT RpcController {
            bool Failed();
            std::string ErrorText();
        }
    }
}

namespace brpc {
    class ClosureGuard {
        explicit ClosureGuard(google::protobuf::Closure* done);
        ~ClosureGuard() { if (_done) _done->Run(); }
    }
}

4.3 服务端类与接口

头文件:#include <brpc/server.h>

brpc::Server 负责:监听端口、接收请求、调度到对应的 Service、发送响应。

cpp 复制代码
namespace brpc {
    struct ServerOptions {
        //⽆数据传输,则指定时间后关闭连接
        int idle_timeout_sec; // Default: -1 (长连接)
        int num_threads; // Default: #cpu-cores
        //....
    }

    enum ServiceOwnership {
        //添加服务失败时,服务器将负责删除服务对象
        SERVER_OWNS_SERVICE,
        //添加服务失败时,服务器也不会删除服务对象,调用者负责删除服务对象
        SERVER_DOESNT_OWN_SERVICE
    };

    class Server {
        public:
            // 添加服务
            int AddService(google::protobuf::Service* service, ServiceOwnership ownership);
            // 启动服务
            int Start(int port, const ServerOptions* opt);
            int Stop(int closewait_ms/*not used anymore*/);
            int Join();
            // 阻塞等待直到 ctrl+c 按下,或者 stop 和 join 服务器
            void RunUntilAskedToQuit();
    }
}

4.4 客户端类与接口

头文件:#include <brpc/channel.h>

brpc::Channel 是 bRPC 客户端的通信通道,负责:管理连接、序列化请求、发送 RPC、接收响应。

创建异步客户端调用时需要 NewCallback 将回调函数包装成 Closure,以便 RPC 完成后自动调用;同步调用传 NULL 即可。

cpp 复制代码
namespace brpc {
    enum ProtocolType : int {
        PROTOCOL_UNKNOWN = 0,
        PROTOCOL_BAIDU_STD = 1,
        PROTOCOL_STREAMING_RPC = 2,
        PROTOCOL_HULU_PBRPC = 3,
        PROTOCOL_SOFA_PBRPC = 4,
        PROTOCOL_RTMP = 5,
        PROTOCOL_THRIFT = 6,
        PROTOCOL_HTTP = 7,
        PROTOCOL_PUBLIC_PBRPC = 8
        // ... 
    }

    struct ChannelOptions {
        //请求连接超时时间
        int32_t connect_timeout_ms; // Default: 200 (milliseconds)
        //rpc请求超时时间
        int32_t timeout_ms; // Default: 500 (milliseconds)
        //最⼤重试次数
        int max_retry; // Default: 3
        //序列化协议类型 options.protocol = "baidu_std";
        AdaptiveProtocolType protocol;
        //....
    }

    class Channel : public ChannelBase {
        // 初始化信道,连接目标服务器,成功返回0;
        int Init(
            const char* server_addr_and_port, //192.168.xx.xx:9000
            const ChannelOptions* options
        );
        // 调用 RPC 方法(由 Stub 内部调用)
        void CallMethod(const google::protobuf::MethodDescriptor* method,
            google::protobuf::RpcController* controller,
            const google::protobuf::Message* request,
            google::protobuf::Message* response,
            google::protobuf::Closure* done);
    };

    inline ::google::protobuf::Closure* NewCallback(void (*function)());

    class Controller : public google::protobuf::RpcController {
        public:
            void set_timeout_ms(int64_t timeout_ms);
            void set_max_retry(int max_retry);
            void Reset();
            bool Failed();
            std::string ErrorText();
    }
}

五、brpc 使用案例

5.1 搭建同步 rpc 客户端与服务器

定义 cal.proto 文件,并编译 cal.proto

proto 复制代码
syntax = "proto3";

package cal;

option cc_generic_services = true;

message CalculateRequest {
    int32 x = 1;
    int32 y = 2;
}

message CalculateResponse {
    int32 result = 1;
}

service CalculateService {
    rpc calculate(CalculateRequest) returns (CalculateResponse);
}

rpc_server.cc

在 RPC 代码中,StubImpl(或 ServiceImpl)分别代表 RPC 通信的两端:

  • Stub(存根/代理):客户端的代理人。它负责伪装成远程服务,让你在本地像调用普通函数一样调用它,而它内部则默默地帮你把请求打包、发送给服务器,并等待结果返回。

  • Impl / ServiceImpl(服务实现):服务端真正的执行者。它是一个真正的、包含业务逻辑的类。服务器收到请求后,会调用这个类的对应方法来执行计算,并返回结果。

server.AddService(&calculate_service... 这个服务对象就是用于告诉服务器,哪个请求应该用哪个函数进行业务处理。

cpp 复制代码
#include <butil/logging.h>
#include <brpc/server.h>
#include "cal.pb.h"
#include <iostream>

class CalculateServiceImpl : public cal::CalculateService {
    public:
    void calculate
    (
        ::google::protobuf::RpcController* controller,
        const ::cal::CalculateRequest* request,     // 客户端的请求信息
        ::cal::CalculateResponse* response, // 业务处理完毕后填充响应信息
        ::google::protobuf::Closure* done   // 很多 rpc 框架中都支持异步操作,业务处理的过程并非要放到calculate函数中实现,本次请求的结束与否,不由calculate函数的生命周期决定,只有当 done->Run() 的时候才表示处理完毕
    ) override {
        // 函数需要用户自己实现:业务处理
        brpc::ClosureGuard done_guard(done);
        response->set_result(request->x() + request->y());
    }
};

int main() {

    // 0.关闭brpc的日志输出
    logging::LoggingSettings logging_setting;
    logging_setting.logging_dest = logging::LOG_TO_NONE;
    logging::InitLogging(logging_setting);
    // 1.实例化计算服务对象
    CalculateServiceImpl calculate_service;
    // 2.定义服务器配置对象
    brpc::ServerOptions options;
    options.idle_timeout_sec = -1;
    // 3.实例化服务器对象
    brpc::Server server;
    // 4.向服务器添加服务
    int res = server.AddService(&calculate_service , brpc::SERVER_DOESNT_OWN_SERVICE);
    if(res == -1) {
        std::cout << "addService failed" << std::endl;
        return -1;
    }
    // 5.启动服务器
    server.Start(9000 , &options);
    // 6.等待服务器停止
    server.RunUntilAskedToQuit();

    return 0;
}

rpc_client.cc

cpp 复制代码
#include <brpc/channel.h>
#include "cal.pb.h"

int main(int argc, char *argv[])
{
    // 0. 实例化ChannelOptions进行参数配置
    brpc::ChannelOptions options;
    options.protocol = "baidu_std";
    // 1. 实例化Channel信道对象
    brpc::Channel chennel;
    chennel.Init("172.16.168.129:9000", &options);
    // 3. 实例化CalService_stub对象--用于客户端发起rpc远程调用请求。
    cal::CalculateService_Stub stub(&chennel);
    brpc::Controller* cntl = new brpc::Controller;
    cal::CalculateRequest* request = new cal::CalculateRequest;
    cal::CalculateResponse* response = new cal::CalculateResponse;
    request->set_x(1024);
    request->set_y(2048);
    /*
        void calculate(::PROTOBUF_NAMESPACE_ID::RpcController* controller, // 对于客户端判断 rpc 请求是否成功
                       const ::cal::CalculateRequest* request, // 客户端请求信息
                       ::cal::CalculateResponse* response,  // 服务端的响应信息
                       ::google::protobuf::Closure* done);  // 若 done 设置为 nullptr ,则表示本次请求时同步阻塞,否则可以实例化 done 对象,设置回调函数,进行异步非阻塞调用
    */
    stub.calculate(cntl, request, response, nullptr);
    if (cntl->Failed() == true) {
        std::cout << "rpc请求失败: " << cntl->ErrorText() << std::endl;
        return -1;
    }
    std::cout << response->result() << std::endl;
    delete cntl;
    delete request;
    delete response;
    getchar();

    return 0;
}

Makefile

makefile 复制代码
.PHONY:all
all: rpc_client rpc_server

rpc_server: rpc_server.cc cal.pb.cc 
	g++ -std=c++17 -o $@ $^ -lbrpc -lleveldb -lprotobuf -lpthread -ldl -lssl -lcrypto -lgflags

rpc_client: rpc_client.cc cal.pb.cc  
	g++ -std=c++17 -o $@ $^ -lbrpc -lleveldb -lprotobuf -lpthread -ldl -lssl -lcrypto -lgflags

%.pb.cc: %.proto
	protoc --cpp_out=. $^

.PHONY:clean
clean:
	rm -f rpc_server rpc_client

5.2 搭建异步 rpc 客户端与服务器

async_rpc_server.cc

cpp 复制代码
#include <butil/logging.h>
#include <brpc/server.h>
#include <iostream>
#include <thread>
#include "cal.pb.h"

class CalculateServiceImpl : public cal::CalculateService {
    public:
    void calculate
    (
        ::google::protobuf::RpcController* controller,
        const ::cal::CalculateRequest* request,
        ::cal::CalculateResponse* response,
        ::google::protobuf::Closure* done
    ) override {
        // 创建一个线程来完成异步操作
        std::thread t([=](){
            brpc::ClosureGuard done_guard(done);
            response->set_result(request->x() + request->y());
            std::this_thread::sleep_for(std::chrono::seconds(3));
        });
        t.detach();
        std::cout << "==================" << std::endl;
    }
};

int main() {

    // 0.关闭brpc的日志输出
    logging::LoggingSettings logging_setting;
    logging_setting.logging_dest = logging::LOG_TO_NONE;
    logging::InitLogging(logging_setting);
    // 1.实例化计算服务对象
    CalculateServiceImpl calculate_service;
    // 2.定义服务器配置对象
    brpc::ServerOptions options;
    options.idle_timeout_sec = -1;
    // 3.实例化服务器对象
    brpc::Server server;
    // 4.向服务器添加服务
    int res = server.AddService(&calculate_service , brpc::SERVER_DOESNT_OWN_SERVICE);
    if(res == -1) {
        std::cout << "addService failed" << std::endl;
        return -1;
    }
    // 5.启动服务器
    server.Start(9000 , &options);
    // 6.等待服务器停止
    server.RunUntilAskedToQuit();

    return 0;
}

async_rpc_client

cpp 复制代码
#include <brpc/channel.h>
#include <brpc/callback.h>
#include "cal.pb.h"

void callback(
    brpc::Controller* cntl,
    cal::CalculateRequest* request,
    cal::CalculateResponse* response
) {
    std::shared_ptr<brpc::Controller> cntl_guard(cntl);
    std::shared_ptr<cal::CalculateRequest> request_guard(request);
    std::shared_ptr<cal::CalculateResponse> response_guard(response);
    if (cntl_guard->Failed() == true) {
        std::cout << "rpc请求失败: " << cntl_guard->ErrorText() << std::endl;
        return;
    }
    std::cout << response_guard->result() << std::endl;
}

int main(int argc, char *argv[])
{
    // 0. 实例化ChannelOptions进行参数配置b   
    brpc::ChannelOptions options;
    options.protocol = "baidu_std";
    // 1. 实例化Channel信道对象
    brpc::Channel chennel;
    chennel.Init("172.16.168.129:9000", &options);
    // 3. 实例化CalService_stub对象--用于发起rpc请求。
    cal::CalculateService_Stub stub(&chennel);
    brpc::Controller* cntl = new brpc::Controller;
    cntl->set_timeout_ms(4000);
    cal::CalculateRequest* request = new cal::CalculateRequest;
    cal::CalculateResponse* response = new cal::CalculateResponse;
    request->set_x(1024);
    request->set_y(2048);
    google::protobuf::Closure* closure = brpc::NewCallback(callback , cntl , request , response);
    stub.calculate(cntl, request, response, closure);
    std::cout << "=========" << std::endl;

    getchar();

    return 0;
}

Makefile

makefile 复制代码
.PHONY:all
all: rpc_client rpc_server async_rpc_server async_rpc_client

rpc_server: rpc_server.cc cal.pb.cc 
	g++ -std=c++17 -o $@ $^ -lbrpc -lleveldb -lprotobuf -lpthread -ldl -lssl -lcrypto -lgflags

rpc_client: rpc_client.cc cal.pb.cc  
	g++ -std=c++17 -o $@ $^ -lbrpc -lleveldb -lprotobuf -lpthread -ldl -lssl -lcrypto -lgflags

async_rpc_server: async_rpc_server.cc cal.pb.cc 
	g++ -std=c++17 -o $@ $^ -lbrpc -lleveldb -lprotobuf -lpthread -ldl -lssl -lcrypto -lgflags

async_rpc_client: async_rpc_client.cc cal.pb.cc  
	g++ -std=c++17 -o $@ $^ -lbrpc -lleveldb -lprotobuf -lpthread -ldl -lssl -lcrypto -lgflags

%.pb.cc: %.proto
	protoc --cpp_out=. $^

.PHONY:clean
clean:
	rm -f rpc_server rpc_client async_rpc_client async_rpc_server

解决 brpc::NewCallback 不能传递 lambda 表达式

cpp 复制代码
struct CallbackContainer {
    std::function<void()> callback;
};

// 异步函数,引用可能会导致悬空引用问题
void callback(CallbackContainer cc) {
    cc.callback();
}

int main() {

    //...
    CallbackContainer cc;
    cc.callback = [=](){
        std::shared_ptr<brpc::Controller> cntl_guard(cntl);
        std::shared_ptr<cal::CalculateRequest> request_guard(request);
        std::shared_ptr<cal::CalculateResponse> response_guard(response);
        if (cntl_guard->Failed() == true) {
            std::cout << "rpc请求失败: " << cntl_guard->ErrorText() << std::endl;
            return;
        }
        std::cout << response_guard->result() << std::endl;
    };
    google::protobuf::Closure* closure = brpc::NewCallback(callback , std::move(cc));

    return 0;
}
  • 值接收 + std::move :会发生移动构造,std::move(cc)cc 转换为右值,把 cc 内部的资源转移到 NewCallback 的参数中。

  • 右值引用接收 + std::move :不会发生移动,std::move 只是将左值转换为右值引用类型,并没有真正移动任何东西。

相关推荐
小肝一下2 小时前
每日两道力扣,day4
c++·算法·leetcode·职场和发展
paeamecium3 小时前
【PAT甲级真题】- Talent and Virtue (25)
数据结构·c++·算法·pat
Mr_Xuhhh3 小时前
蓝桥杯复习清单真题(C++版本)
c++·算法·蓝桥杯
tankeven3 小时前
HJ163 时津风的资源收集
c++·算法
森G3 小时前
40、对话框---------事件系统
c++·qt
旖-旎3 小时前
分治(计算右侧小于当前元素的个数)(7)
c++·学习·算法·leetcode·排序算法·归并排序
迷海3 小时前
C++内存对齐
开发语言·c++
炘爚3 小时前
C++(流类:istream /ostream/istringstream /ostringstream)
开发语言·c++·算法
!停3 小时前
C++入门—内存管理
java·jvm·c++