【视频点播系统】BRpc 介绍及使用

BRpc 介绍及使用

  • [一. Rpc 介绍](#一. Rpc 介绍)
  • [二. BRpc 介绍](#二. BRpc 介绍)
  • [三. BRpc 安装](#三. BRpc 安装)
  • [四. BRpc 类与接口](#四. BRpc 类与接口)
    • [1. 日志输出类与接口](#1. 日志输出类与接口)
    • [2. ProtoBuf 类与接口](#2. ProtoBuf 类与接口)
    • [3. 服务端类与接口](#3. 服务端类与接口)
    • [4. HTTP 类与接口](#4. HTTP 类与接口)
    • [5. 客户端类与接口](#5. 客户端类与接口)
  • [五. BRpc 使用样例](#五. BRpc 使用样例)
    • [1. 目录结构](#1. 目录结构)
    • [2. 项目构建](#2. 项目构建)
    • [3. 代码实现](#3. 代码实现)
      • [3.1 .proto 文件](#3.1 .proto 文件)
      • [3.2 同步 RPC 客户端、同步 RPC 服务端](#3.2 同步 RPC 客户端、同步 RPC 服务端)
      • [3.3 异步 RPC 客户端、异步 RPC 服务端](#3.3 异步 RPC 客户端、异步 RPC 服务端)
      • [3.4 HTTP 客户端、HTTP 服务端](#3.4 HTTP 客户端、HTTP 服务端)
  • [六. BRpc 封装](#六. BRpc 封装)
    • [1. 设计与实现](#1. 设计与实现)
      • [1.1 目录结构](#1.1 目录结构)
      • [1.2 代码实现](#1.2 代码实现)
    • [2. 使用样例](#2. 使用样例)
      • [2.1 目录结构](#2.1 目录结构)
      • [2.2 项目构建](#2.2 项目构建)
      • [2.3 代码实现](#2.3 代码实现)

一. Rpc 介绍

RPC (Remote Procedure Call) 远程过程调用,简单来说就是客户端在不知道调用细节的情况下,调用远程计算机上的某个功能就像调用本地功能一样,其主要目标就是让构建分布式计算 (应用) 更容易,在提供强大的远程调用能力时不损失本地调用的语义简洁性。

二. BRpc 介绍

  • BRPC (Baidu RPC) 是百度开源的一款高性能 C++ RPC 框架,主要用于构建大规模分布式系统中的服务通信。它以低延迟、高吞吐为设计目标,广泛应用于对性能和稳定性要求极高的后端场景。
  • BRPC 支持多种通信协议,包括百度自研 RPC、HTTP/HTTPS、gRPC、Thrift、Redis 协议等,同一服务可同时对外提供多种接口。框架内置连接复用、超时控制、重试、负载均衡、限流和监控统计等工程能力,运维友好。BRPC 还引入了轻量级用户态线程 bthread,能够高效处理大量并发 I/O 请求。整体而言,BRPC 是一款偏工程化、成熟稳定、适合 C++ 高并发服务的 RPC 框架。

三. BRpc 安装

先安装依赖

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

安装 brpc

bash 复制代码
# 下载源码
git clone https://github.com/apache/brpc.git
# 切换目录
cd brpc/
mkdir build
cd build
# 生成 Makefile
cmake -DCMAKE_INSTALL_PREFIX=/usr .. && cmake --build . -j6
# 编译代码
make
# 安装
sudo make install

由于在环境搭建中已经安装了 BRpc,这里不需要再次安装了。

四. BRpc 类与接口

1. 日志输出类与接口

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

brpc 库内有人家自己的日志输出模块 (无法替换,除非修改库内源码中所有的日志输出操作后重新编译库进行安装),这里主要是了解他的日志级别,将不需要的日志输出给禁用掉,避免其运行时的大量日志输出影响我们的视线。

cpp 复制代码
namespace logging {
    // 日志目的地枚举:指定日志输出的目标
    enum LoggingDestination {
        LOG_TO_NONE = 0
    };
    // 日志设置结构体:用于配置日志输出的目标
    struct BUTIL_EXPORT LoggingSettings {
        LoggingSettings(); // 构造函数:初始化日志设置
        LoggingDestination logging_dest; // 日志输出目标
    };
    bool InitLogging(const LoggingSettings& settings); // 初始化日志系统
}

2. ProtoBuf 类与接口

cpp 复制代码
namespace google {
    namespace protobuf {
        // 闭包类
        class PROTOBUF_EXPORT Closure {
        public:
            Closure() {} // 构造函数
            virtual ~Closure(); // 析构函数
            virtual void Run() = 0; // 在 RPC 调用完成时调用,表明 RPC 调用已完成
        };
        // PRC 控制器类
        class PROTOBUF_EXPORT RpcController {
        public:
            bool Failed(); // 判断 RPC 调用是否失败
            std::string ErrorText() ; // 获取 RPC 调用发生错误的文本
        };
        // 异步调用所使用的回调函数类
        template <typename F, typename... Args>
        google::protobuf::Closure* NewCallback(F&& f, Args&&... args) {...}
    }
}

3. 服务端类与接口

cpp 复制代码
namespace brpc {
    // 服务器选项 
    struct ServerOptions {
        int idle_timeout_sec; // 无数据传输,则指定时间后关闭连接,默认值为60秒
        int num_threads; // 线程数,默认值为10
        //....
    }
    // 服务所有权 
    enum ServiceOwnership {
        SERVER_OWNS_SERVICE, // 添加服务失败时,服务器会删除服务对象
        SERVER_DOESNT_OWN_SERVICE // 添加服务失败时,服务器不会删除服务对象
    };
    // 服务器类 
    class Server {
        // service 服务对象,告诉服务器哪个请求应该用哪个函数进行处理
        // ownership 服务所有权,决定服务器是否在添加服务失败时删除服务对象
        int AddService(google::protobuf::Service* service, ServiceOwnership ownership); // 添加服务
        int Start(int port, const ServerOptions* opt); // 启动服务器
        int Stop(int closewait_ms); // 停止服务器
        int Join(); // 等待服务器线程退出
        void RunUntilAskedToQuit(); // 运行直到被要求退出
    }
    // 闭包守卫类 
    class ClosureGuard {
        explicit ClosureGuard(google::protobuf::Closure* done); // 构造函数
        ~ClosureGuard() { if (_done) _done->Run(); } // 析构函数:如果存在闭包,则运行闭包,表明 RPC 请求处理完成
    }
}

4. HTTP 类与接口

cpp 复制代码
// URI 类:用于解析和操作 URI
class URI {
    typedef butil::FlatMap<std::string, std::string> QueryMap;
    typedef QueryMap::const_iterator QueryIterator;
    int SetHttpURL(const std::string& url); // 设置 HTTP URL
    void set_path(const std::string& path); // 设置路径
    void set_host(const std::string& host); // 设置主机
    void set_port(int port); // 设置端口
    void SetHostAndPort(const std::string& host_and_optional_port); // 设置主机和端口
    size_t RemoveQuery(const char* key); // 删除查询参数
    const std::string& host() const; // 获取主机
    int port() const; // 获取端口
    const std::string& path() const; // 获取路径
    const std::string& user_info() const; // 获取用户信息
    const std::string& query() const; // 获取查询参数
    const std::string* GetQuery(const std::string& key) // 获取查询参数值
    void SetQuery(const std::string& key, const std::string& value); // 设置查询参数值
    QueryIterator QueryBegin() const; // 查询参数迭代器 begin
    QueryIterator QueryEnd() const; // 查询参数迭代器 end
    size_t QueryCount() const; // 查询参数数量
};

// HTTP 方法枚举
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 方法
};

const char *HttpMethod2Str(HttpMethod http_method); // HTTP 方法枚举转换为字符串
bool Str2HttpMethod(const char* method_str, HttpMethod* method); // 字符串转换为 HTTP 方法枚举
static const int HTTP_STATUS_OK = 200; // HTTP 状态码:成功
static const int HTTP_STATUS_BAD_REQUEST = 400; // HTTP 状态码:错误请求
static const int HTTP_STATUS_UNAUTHORIZED = 401; // HTTP 状态码:未授权
static const int HTTP_STATUS_FORBIDDEN = 403; // HTTP 状态码:禁止访问
static const int HTTP_STATUS_NOT_FOUND = 404; // HTTP 状态码:未找到
static const int HTTP_STATUS_METHOD_NOT_ALLOWED = 405; // HTTP 状态码:方法不允许
static const int HTTP_STATUS_INTERNAL_SERVER_ERROR = 500; // HTTP 状态码:内部服务器错误

// HTTP 头类:用于解析和操作 HTTP 头
class HttpHeader {
    const std::string& content_type() const; // 获取内容类型
    void set_content_type(const std::string& type); // 设置内容类型
    const std::string* GetHeader(const std::string& key) const; // 获取 HTTP 头值
    void SetHeader(const std::string& key, const std::string& value); // 设置 HTTP 头值
    const URI& uri() const; // 获取 URI
    HttpMethod method() const; // 获取 HTTP 方法
    void set_method(const HttpMethod method); // 设置 HTTP 方法
    int status_code() const; // 获取 HTTP 状态码
    void set_status_code(int status_code); // 设置 HTTP 状态码
}

// Controller 类:用于保存 RPC 请求是否成功的状态
class Controller : public google::protobuf::RpcController {
    void set_timeout_ms(int64_t timeout_ms); // 设置超时时间
    void set_max_retry(int max_retry); // 设置最大重试次数
    void Reset(); // 重置控制器状态
    google::protobuf::Message* response(); // 获取响应消息指针
    HttpHeader& http_response(); // 获取 HTTP 响应头引用
    butil::IOBuf& response_attachment(); // 获取响应附件 IOBuf 引用
    HttpHeader& http_request(); // 获取 HTTP 请求头引用
    butil::IOBuf& request_attachment(); // 获取请求附件 IOBuf 引用
    bool Failed(); // 判断 RPC 请求是否失败
    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); // 设置 RPC 响应回调函数
}

5. 客户端类与接口

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,
        PROTOCOL_NOVA_PBRPC = 9,
        PROTOCOL_REDIS = 10,
        PROTOCOL_NSHEAD_CLIENT = 11,
        PROTOCOL_NSHEAD = 12,
        PROTOCOL_HADOOP_RPC = 13,
        PROTOCOL_HADOOP_SERVER_RPC = 14,
        PROTOCOL_MONGO = 15,
        PROTOCOL_UBRPC_COMPACK = 16,
        PROTOCOL_DIDX_CLIENT = 17,
        PROTOCOL_MEMCACHE = 18,
        PROTOCOL_ITP = 19,
        PROTOCOL_NSHEAD_MCPACK = 20,
        PROTOCOL_DISP_IDL = 21,
        PROTOCOL_ERSDA_CLIENT = 22,
        PROTOCOL_UBRPC_MCPACK2 = 23,
        PROTOCOL_CDS_AGENT = 24,
        PROTOCOL_ESP = 25,
        PROTOCOL_H2 = 26
    };
    // 通道选项 
    struct ChannelOptions {
        int32_t connect_timeout_ms; // 请求连接超时时间,默认值为200毫秒
        int32_t timeout_ms; // rpc请求超时时间,默认值为500毫秒
        int max_retry; // 最大重试次数,默认值为3次
        AdaptiveProtocolType protocol; // 序列化协议类型,默认值为baidu_std
        //....
    }
    // 通道类 
    class Channel : public ChannelBase {
        // 初始化通道
        int Init(const char* server_addr_and_port, const ChannelOptions* options);
        // 调用方法
        void CallMethod(const google::protobuf::MethodDescriptor* method,
                        google::protobuf::RpcController* controller,
                        const google::protobuf::Message* request,
                        google::protobuf::Message* response,
                        google::protobuf::Closure* done);
    };
}
// 异步调用所使用的回调函数类
template <typename F, typename... Args>
google::protobuf::Closure* NewCallback(F&& f, Args&&... args) {...}

五. BRpc 使用样例

1. 目录结构

bash 复制代码
brpc/
|-- async_client.cc
|-- async_server.cc
|-- cal.pb.cc
|-- cal.pb.h
|-- cal.proto
|-- http_client.cc
|-- http_server.cc
|-- makefile
|-- sync_client.cc
|-- sync_server.cc

2. 项目构建

bash 复制代码
# makefile
all: sync_server sync_client async_server async_client http_server http_client
sync_server: sync_server.cc cal.pb.cc
	g++ -o $@ $^ -std=c++17 -lbrpc -lleveldb -lprotobuf -lpthread -ldl -lssl -lcrypto -lgflags
sync_client: sync_client.cc cal.pb.cc
	g++ -o $@ $^ -std=c++17 -lbrpc -lleveldb -lprotobuf -lpthread -ldl -lssl -lcrypto -lgflags
async_server: async_server.cc cal.pb.cc
	g++ -o $@ $^ -std=c++17 -lbrpc -lleveldb -lprotobuf -lpthread -ldl -lssl -lcrypto -lgflags
async_client: async_client.cc cal.pb.cc
	g++ -o $@ $^ -std=c++17 -lbrpc -lleveldb -lprotobuf -lpthread -ldl -lssl -lcrypto -lgflags
http_server: http_server.cc cal.pb.cc
	g++ -o $@ $^ -std=c++17 -lbrpc -lleveldb -lprotobuf -lpthread -ldl -lssl -lcrypto -lgflags
http_client: http_client.cc cal.pb.cc
	g++ -o $@ $^ -std=c++17 -lbrpc -lleveldb -lprotobuf -lpthread -ldl -lssl -lcrypto -lgflags

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

.PHONY: clean
clean:
	rm -f sync_server sync_client async_server async_client http_server http_client

3. 代码实现

3.1 .proto 文件

cpp 复制代码
// cal.proto
syntax = "proto3"; // 描述语法版本
package cal; // 声明包名称(C++对应的是命名空间)
option cc_generic_services = true; // 默认情况 protoc 命令并不会针对 service 服务生成对应 rpc 代码,需要开启选项才会进行生成

// 定义 RPC 类型
message AddReq {
    int32 num1 = 1;
    int32 num2 = 2;
}
message AddRsp {
    int32 result = 1;
}
// 定义 HTTP 类型
message HelloReq {}
message HelloRsp {}

// 定义 RPC 服务
service CalService {
    rpc Add(AddReq) returns (AddRsp);
    rpc Hello(HelloReq) returns (HelloRsp);
}

执行 make 命令,生产 cal.pb.cc 和 cal.pb.h 文件,如下:

cpp 复制代码
// cal.pb.h
namespace cal {
    // AddReq 请求对象
    class AddReq PROTOBUF_FINAL : public ::PROTOBUF_NAMESPACE_ID::Message {
    public:
        enum : int {
            kNum1FieldNumber = 1,
            kNum2FieldNumber = 2,
        };
        // int32 num1 = 1;
        void clear_num1(); // 清除 num1 字段的值
        ::PROTOBUF_NAMESPACE_ID::int32 num1() const; // 获取 num1 字段的值
        void set_num1(::PROTOBUF_NAMESPACE_ID::int32 value); // 设置 num1 字段的值
        // int32 num2 = 2;
        void clear_num2(); // 清除 num2 字段的值
        ::PROTOBUF_NAMESPACE_ID::int32 num2() const; // 获取 num2 字段的值
        void set_num2(::PROTOBUF_NAMESPACE_ID::int32 value); // 设置 num2 字段的值
    };
    // AddRsp 响应对象
    class AddRsp PROTOBUF_FINAL : public ::PROTOBUF_NAMESPACE_ID::Message {
    public:
        enum : int {
            kResultFieldNumber = 1,
        };
        // int32 result = 1;
        void clear_result(); // 清除 result 字段的值
        ::PROTOBUF_NAMESPACE_ID::int32 result() const; // 获取 result 字段的值
        void set_result(::PROTOBUF_NAMESPACE_ID::int32 value); // 设置 result 字段的值
    };
    // HelloReq 请求对象
    class HelloReq PROTOBUF_FINAL : public ::PROTOBUF_NAMESPACE_ID::Message {};
    // HelloRsp 响应对象
    class HelloRsp PROTOBUF_FINAL : public ::PROTOBUF_NAMESPACE_ID::Message {};
    // 服务器使用的类
    class CalService : public ::PROTOBUF_NAMESPACE_ID::Service {
    public:
        // controller 用于获取 HTTP 请求信息以及设置 HTTP 响应信息
        // request 为 RPC 请求参数
        // response 为 RPC 响应参数
        // done 当 RPC 调用完成时,服务器会调用 done->Run(),表示 RPC 调用完成
        virtual void Add(::PROTOBUF_NAMESPACE_ID::RpcController* controller,
                        const ::cal::AddReq* request,
                        ::cal::AddRsp* response,
                        ::google::protobuf::Closure* done);
        virtual void Hello(::PROTOBUF_NAMESPACE_ID::RpcController* controller,
                       const ::cal::HelloReq* request,
                       ::cal::HelloRsp* response,
                       ::google::protobuf::Closure* done);
        // 调用方法
        void CallMethod(const ::PROTOBUF_NAMESPACE_ID::MethodDescriptor* method,
                    ::PROTOBUF_NAMESPACE_ID::RpcController* controller,
                    const ::PROTOBUF_NAMESPACE_ID::Message* request,
                    ::PROTOBUF_NAMESPACE_ID::Message* response,
                    ::google::protobuf::Closure* done);
    };

    // 客户端使用的类
    class CalService_Stub : public CalService {
    public:
        // channel 为 RPC 通道指针,ownership 为通道所有权
        CalService_Stub(::PROTOBUF_NAMESPACE_ID::RpcChannel* channel);
        CalService_Stub(::PROTOBUF_NAMESPACE_ID::RpcChannel* channel, ::PROTOBUF_NAMESPACE_ID::Service::ChannelOwnership ownership);
        
        // controller 用于设置 HTTP 请求信息/获取 RPC 请求是否成功的状态
        // request 为 RPC 请求参数
        // response 为 RPC 响应参数
        // done 取值为 NULL 时,客户端同步 RPC 调用;取值不为 NULL 时,客户端异步 RPC 调用
        void Add(::PROTOBUF_NAMESPACE_ID::RpcController* controller,
                const ::cal::AddReq* request,
                ::cal::AddRsp* response,
                ::google::protobuf::Closure* done);
        void Hello(::PROTOBUF_NAMESPACE_ID::RpcController* controller,
                       const ::cal::HelloReq* request,
                       ::cal::HelloRsp* response,
                       ::google::protobuf::Closure* done);
    };
}

3.2 同步 RPC 客户端、同步 RPC 服务端

同步调用是指 client 会阻塞收到 server 端的响应或发生错误。

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

int main() 
{
    // 1.实例化 ChannelOptions 信道选项对象
    brpc::ChannelOptions options;
    options.protocol = brpc::PROTOCOL_BAIDU_STD; // 设置为百度标准 RPC 协议
    // 2.实例化 Channel 信道对象
    brpc::Channel channel;
    channel.Init("192.168.174.128:9000", &options);
    // 3.实例化 CalService_Stub 客户端对象,用于发起 RPC 请求
    cal::CalService_Stub stub(&channel);
    // 4.实例化 Controller 控制器对象,用于保存 RPC 请求是否成功的状态
    brpc::Controller cntl;
    // 5.实例化 AddReq 请求对象、AddRsp 响应对象
    cal::AddReq req;
    cal::AddRsp rsp;
    req.set_num1(10);
    req.set_num2(20);
    // 6.发起 RPC 同步请求
    stub.Add(&cntl, &req, &rsp, nullptr); // 阻塞,等待 RPC 响应返回
    // 7.判断 RPC 请求是否失败
    if (cntl.Failed()) {
        std::cout << "RPC 同步请求失败,原因:" << cntl.ErrorText() << std::endl;
        return -1;
    }
    // 8.打印 RPC 响应结果
    std::cout << "RPC 同步请求结果:" << rsp.result() << std::endl;
    
    return 0;
}
cpp 复制代码
// sync_server.cc
#include <butil/logging.h>
#include <brpc/server.h>
#include "cal.pb.h"

// 创建 CalServiceImpl 类来实现 CalService 服务
class CalServiceImpl : public cal::CalService {
public:
    CalServiceImpl() {}
    ~CalServiceImpl() {}
    // 重写 Add 方法
    virtual 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);
    }
};

int main(int argc, char* argv[])
{
    // 1.实例化计算服务对象
    CalServiceImpl cal_service;
    // 2.定义服务器配置对象
    brpc::ServerOptions options;
    options.idle_timeout_sec = -1; // 设置为 -1 表示不超时
    // 3.实例化服务器对象
    brpc::Server server;
    // 4.向服务器添加计算服务
    int ret = server.AddService(&cal_service, brpc::SERVER_DOESNT_OWN_SERVICE); // 服务对象不由服务器管理释放
    if (ret == -1) {
        std::cerr << "添加计算服务失败" << std::endl;
        return -1;
    }
    // 5.启动服务器
    ret = server.Start(9000, &options);
    if (ret == -1) {
        std::cerr << "启动服务器失败" << std::endl;
        return -1;
    }
    // 6.等待服务器退出
    server.RunUntilAskedToQuit();

    return 0;
}

3.3 异步 RPC 客户端、异步 RPC 服务端

异步调用是指 client 注册一个响应处理回调函数,当调用一个 RPC 接口时立即返回,不会阻塞等待响应,当 server 返回响应时会调用传入的回调函数处理响应。

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

// 当 RPC 异步调用完成时(也就是执行done->Run()时),会调用回调函数 Callback 将结果输出
void Callback(brpc::Controller* cntl, cal::AddReq* req, cal::AddRsp* rsp) {
    std::unique_ptr<brpc::Controller> cntl_guard(cntl);
    std::unique_ptr<cal::AddReq> req_guard(req);
    std::unique_ptr<cal::AddRsp> rsp_guard(rsp);
    if (cntl->Failed()) {
        std::cout << "RPC 异步请求失败,原因:" << cntl->ErrorText() << std::endl;
        return;
    }
    std::cout << "RPC 异步请求结果:" << rsp->result() << std::endl;
}

int main() 
{
    // 1.实例化 ChannelOptions 信道选项对象
    brpc::ChannelOptions options;
    options.protocol = brpc::PROTOCOL_BAIDU_STD; // 设置为百度标准 RPC 协议
    // 2.实例化 Channel 信道对象
    brpc::Channel channel;
    channel.Init("192.168.174.128:9000", &options);
    // 3.实例化 CalService_Stub 客户端对象,用于发起 RPC 请求
    cal::CalService_Stub stub(&channel);
    // 4.实例化 Controller 控制器对象,用于保存 RPC 请求是否成功的状态
    brpc::Controller* cntl = new brpc::Controller();
    cntl->set_timeout_ms(4000); // 设置 RPC 异步请求超时时间为 4000 毫秒
    // 5.实例化 AddReq 请求对象、AddRsp 响应对象
    cal::AddReq* req = new cal::AddReq();
    cal::AddRsp* rsp = new cal::AddRsp();
    req->set_num1(10);
    req->set_num2(20);
    // 6.创建异步回调函数
    google::protobuf::Closure* closure = brpc::NewCallback(Callback, cntl, req, rsp);
    // 7.发起 RPC 异步请求
    stub.Add(cntl, req, rsp, closure); // 非阻塞,立即返回
    std::cout << "--------------------" << std::endl;
    getchar(); // 等待用户输入,防止程序退出
    
    return 0;
}
cpp 复制代码
// async_server.cc
#include <thread>
#include <butil/logging.h>
#include <brpc/server.h>
#include "cal.pb.h"

// 创建 CalServiceImpl 类来实现 CalService 服务
class CalServiceImpl : public cal::CalService {
public:
    CalServiceImpl() {}
    ~CalServiceImpl() {}
    // 重写 Add 方法
    virtual void Add(::google::protobuf::RpcController* controller,
                    const ::cal::AddReq* request,
                    ::cal::AddRsp* response,
                    ::google::protobuf::Closure* done) override {
        std::thread th([=](){
            // 当 done_guard 被释放时执行 done->Run() 表明 RPC 异步请求完成
            brpc::ClosureGuard done_guard(done);
            std::this_thread::sleep_for(std::chrono::seconds(3)); // 模拟 RPC 调用耗时 3 秒
            int result = request->num1() + request->num2();
            response->set_result(result);
        });
        th.detach(); // 分离线程,线程结束后由系统回收资源,创建者线程不再关心它
        std::cout << "====================" << std::endl;
    }
};

int main(int argc, char* argv[])
{
    // 1.实例化计算服务对象
    CalServiceImpl cal_service;
    // 2.定义服务器配置对象
    brpc::ServerOptions options;
    options.idle_timeout_sec = -1; // 设置为 -1 表示不超时
    // 3.实例化服务器对象
    brpc::Server server;
    // 4.向服务器添加计算服务
    int ret = server.AddService(&cal_service, brpc::SERVER_DOESNT_OWN_SERVICE); // 服务对象不由服务器管理释放
    if (ret == -1) {
        std::cerr << "添加计算服务失败" << std::endl;
        return -1;
    }
    // 5.启动服务器
    ret = server.Start(9000, &options);
    if (ret == -1) {
        std::cerr << "启动服务器失败" << std::endl;
        return -1;
    }
    // 6.等待服务器退出
    server.RunUntilAskedToQuit();

    return 0;
}

总结:

  • 同步客户端 + 同步服务器:客户端发起同步 RPC 请求 (阻塞) → 服务器在 worker 线程中同步执行 → 执行完成立即 done->Run() 发送响应 → 客户端解除阻塞,RPC 结束。
  • 同步客户端 + 异步服务器:客户端发起同步 RPC 请求 (阻塞) → 服务器 worker 线程收到请求 → 启动异步线程并立刻 return → 异步线程完成业务逻辑 → 调用 done->Run() 发送响应 → 客户端解除阻塞,RPC 结束。
  • 异步客户端 + 同步服务器:客户端发起异步 RPC 请求 (非阻塞,立即返回) → 服务器在 worker 线程中同步执行 → 执行完成立即 done->Run() 发送响应 → 客户端 IO/bthread (内部框架线程) 收到响应后,触发回调函数获取结果 → RPC 结束。
  • 异步客户端 + 异步服务器:客户端发起异步 RPC 请求 (非阻塞,立即返回) → 服务器 worker 线程收到请求 → 启动异步线程并立刻 return → 异步线程完成业务逻辑 → 调用 done->Run() 发送响应 → 客户端 IO/bthread (内部框架线程) 收到响应后,触发回调函数获取结果 → RPC 结束。

3.4 HTTP 客户端、HTTP 服务端

  • 在搭建客户端的时候,特殊的是,需要告诉客户端我们要发送的是 HTTP 请求,而不是 RPC 请求。
  • 在搭建服务端的时候,特殊的是,BRpc 支持通过 .proto 文件中定义服务,来支持对 JSON 格式 HTTP 请求正文的处理。相当于将 HTTP 请求当做 RPC 请求进行处理,将 JSON 请求解析后放入 requset 对象中,而不是 cntl 对象中。如果请求正文不是 JSON 格式,并且也没有通过 .proto 文件定义服务, 则将请求信息放在 cntl 对象中。
cpp 复制代码
// http_client.cc
#include <brpc/channel.h>
#include "cal.pb.h"

int main() 
{
    // 1.实例化 ChannelOptions 信道选项对象
    brpc::ChannelOptions options;
    options.protocol = brpc::PROTOCOL_HTTP; // 设置为 HTTP 协议
    // 2.实例化 Channel 信道对象
    brpc::Channel channel;
    channel.Init("192.168.174.128:9000", &options);
    // 3.实例化 Controller 控制器对象,并设置 HTTP 请求信息
    brpc::Controller cntl;
    cntl.http_request().set_method(brpc::HTTP_METHOD_POST); // 设置为 POST 方法
    cntl.http_request().uri().set_path("/CalService/Hello"); // 设置请求路径
    cntl.http_request().SetHeader("Content-Type", "text/plain"); // 设置请求头
    cntl.request_attachment().append("Hello World"); // 设置请求正文
    // 4.发起 HTTP 请求
    channel.CallMethod(nullptr, &cntl, nullptr, nullptr, nullptr);
    // 5.打印 HTTP 响应正文
    if (cntl.Failed()) {
        std::cout << "HTTP 请求失败,原因:" << cntl.ErrorText() << std::endl;
        return -1;
    }
    std::cout << cntl.response_attachment() << std::endl;
    
    return 0;
}
cpp 复制代码
// http_server.cc
#include <butil/logging.h>
#include <brpc/server.h>
#include "cal.pb.h"

// 创建 CalServiceImpl 类来实现 CalService 服务
class CalServiceImpl : public cal::CalService {
public:
    CalServiceImpl() {}
    ~CalServiceImpl() {}
    // 重写 Add 方法
    virtual 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);
    }
    // 重写 Hello 方法
    virtual void Hello(::google::protobuf::RpcController* controller,
                       const ::cal::HelloReq* request,
                       ::cal::HelloRsp* response,
                       ::google::protobuf::Closure* done) override {
        // 当 done_guard 被释放时执行 done->Run() 表明 RPC 同步请求完成
        brpc::ClosureGuard done_guard(done);
        brpc::Controller* cntl = static_cast<brpc::Controller*>(controller);
        const brpc::HttpHeader& req = cntl->http_request(); // 获取 HTTP 请求
        // 打印请求行
        std::cout << "请求方法: " << brpc::HttpMethod2Str(req.method()) << std::endl;
        std::cout << "请求URI: " << req.uri() << std::endl;
        // 打印请求正文
        std::cout << "请求正文: " << cntl->request_attachment() << std::endl;
        // 设置响应正文、状态码
        cntl->response_attachment().append("回显:" + cntl->request_attachment().to_string());
        cntl->http_response().set_status_code(brpc::HTTP_STATUS_OK);
    }
};

int main(int argc, char* argv[])
{
    // 1.实例化计算服务对象
    CalServiceImpl cal_service;
    // 2.定义服务器配置对象
    brpc::ServerOptions options;
    options.idle_timeout_sec = -1; // 设置为 -1 表示不超时
    // 3.实例化服务器对象
    brpc::Server server;
    // 4.向服务器添加计算服务
    int ret = server.AddService(&cal_service, brpc::SERVER_DOESNT_OWN_SERVICE); // 服务对象不由服务器管理释放
    if (ret == -1) {
        std::cerr << "添加计算服务失败" << std::endl;
        return -1;
    }
    // 5.启动服务器
    ret = server.Start(9000, &options);
    if (ret == -1) {
        std::cerr << "启动服务器失败" << std::endl;
        return -1;
    }
    // 6.等待服务器退出
    server.RunUntilAskedToQuit();

    return 0;
}

发送非 JSON 格式 HTTP 请求正文:

发送 JSON 格式 HTTP 请求正文:

六. BRpc 封装

1. 设计与实现

关键点:Rpc 通常与注册中心搭配使用的!

  • 一个逻辑服务可以由多个主机节点(服务实例)共同提供,以实现负载均衡和高可用。
  • 服务端在启动时,会向注册中心注册自己所提供的服务信息。
  • 注册中心负责维护"服务 → 实例列表"的映射关系,并持续感知实例的上下线状态。
  • 客户端在发起 RPC 调用前,先向注册中心查询可用的服务实例列表。
  • 客户端从实例列表中选择一个节点(通常通过负载均衡策略,如随机、轮询、一致性哈希等),并向该节点发起 RPC 请求。
  • 当服务实例发生变更(上线、下线、故障)时,注册中心会更新实例列表,客户端可动态感知或重新获取,从而实现透明的服务治理。

二次封装的目的:

  • 封装 Channels 管理类:将所有能够提供指定服务的节点,提前创建好 Channel 给管理起来,以便于随时获取进行使用。
    • Channels 集合类:一个服务有一个 Channels 集合 (因为一个服务,可能由很多个主机节点能够提供该服务),因此针对不同的服务,将它们各自的 Channels 集合管理起来,以 RR 轮转的负载均衡思想执行某个主机节点上的服务。
    • ChannelsManage 管理类:管理的是服务对应的 Channels 集合,不关心的服务不需要管理。
  • 封装 Closure 工厂类:主要针对异步请求时仿函数对象与 BRpc 不适配的情况。使用者提供一个函数/仿函数对象,我们返回一个 Closure 对象即可。
  • 封装 Server 工厂类:主要针对 Server 对象的创建便捷化。

1.1 目录结构

bash 复制代码
source/
|-- xzyrpc.cc
|-- xzyrpc.h

1.2 代码实现

cpp 复制代码
// xzyrpc.h
#pragma once
#include <butil/logging.h>
#include <brpc/server.h>
#include <brpc/channel.h>
#include <mutex>

namespace xzyrpc {
    using ChannelPtr = std::shared_ptr<brpc::Channel>;
    // Channels 集合类:用于管理一个服务由多个主机节点提供的 channel
    class Channels {
    public:
        Channels();
        ~Channels();
        using ptr = std::shared_ptr<Channels>;
        // 新增节点:根据 addr 新增一个 channel
        void insert(const std::string& addr);
        // 移除节点:根据 addr 删除一个 channel
        void remove(const std::string& addr);
        // 获取节点:轮询获取一个 channel 用于负载均衡提供服务
        ChannelPtr select();
    private:
        std::mutex _mtx; // 互斥锁
        uint32_t _idx; // 轮询下标
        std::string _server_name; // 服务名称
        std::vector<ChannelPtr> _channels; // 一个服务可以由多个主机节点提供
        std::unordered_map<std::string, ChannelPtr> _map; // addr 映射 channel
    };
    // ChannelsManager 管理类:用于管理多个服务由多个主机节点提供的 channel
    class ChannelsManager {
    public:
        ChannelsManager();
        ~ChannelsManager();
        // 设置服务监控:根据 server_name 初始化一个 Channels 集合,添加到 _map 中进行管理
        void setWatch(const std::string& server_name);
        // 新增节点:根据 server_name 和 addr 新增一个 channel
        void addNode(const std::string& server_name, const std::string& addr);
        // 删除节点:根据 server_name 和 addr 删除一个 channel
        void delNode(const std::string& server_name, const std::string& addr);
        // 获取节点:根据 server_name 获取一个 channel
        ChannelPtr getNode(const std::string& server_name);
    private:
        // 根据 server_name 获取 Channels 集合
        Channels::ptr _Channels(const std::string& server_name);
    private:
        std::mutex _mtx; // 互斥锁
        std::unordered_map<std::string, Channels::ptr> _map; // server_name 映射 Channels 集合
    };
    // ClosureFactory 工厂类:用于根据回调函数创建一个 Closure 类的实例
    class ClosureFactory {
    public:
        using callback_t = std::function<void()>;
        // 创建 Closure 类的实例:根据回调函数创建一个 Closure 类的实例
        static google::protobuf::Closure* create(callback_t &&cb);
    private:
        struct Object {
            using ptr = std::shared_ptr<Object>;
            callback_t callback;
        };
        // 异步回调函数:用于在 Closure 类的实例完成时调用回调函数
        static void asyncCallback(const Object::ptr obj);
    };
    // RpcServerFactory 工厂类:用于创建 RpcServer 类的实例
    class RpcServerFactory {
    public:
        // 创建 RpcServer 类的实例:根据端口号和服务对象创建一个 RpcServer 类的实例
        static std::shared_ptr<brpc::Server> create(int port, google::protobuf::Service* service);
    };
}
cpp 复制代码
// xzyrpc.cc
#include "xzyrpc.h"
#include "xzylog.h"

namespace xzyrpc {
    Channels::Channels(): _idx(0) {}
    Channels::~Channels() {}
    using ptr = std::shared_ptr<Channels>;
    // 新增节点:根据 addr 新增一个 channel
    void Channels::insert(const std::string& addr) {
        // 1.加锁:确保线程安全
        std::unique_lock<std::mutex> lock(_mtx);
        // 2.根据 addr 和 options 初始化一个 Channel 对象
        ChannelPtr channel = std::make_shared<brpc::Channel>();
        brpc::ChannelOptions options;
        options.protocol = brpc::PROTOCOL_BAIDU_STD;
        channel->Init(addr.c_str(), &options);
        // 3.将 channel 插入到 _channels 中
        _channels.push_back(channel);
        // 4.将 addr 映射到 channel
        _map.insert(std::make_pair(addr, channel));
    }
    // 移除节点:根据 addr 删除一个 channel
    void Channels::remove(const std::string& addr) {
        // 1.加锁:确保线程安全
        std::unique_lock<std::mutex> lock(_mtx);
        // 2.根据 addr 从 _map 中获取 channel
        auto it = _map.find(addr);
        if (it == _map.end()) {
            WRN("删除节点时,节点 {} 不存在", addr);
            return;
        }
        // 3.从 _map 中删除 addr 映射
        _map.erase(it);
        // 4.从 _channels 中删除 channel
        for (auto vit = _channels.begin(); vit != _channels.end(); ++vit) {
            if (*vit == it->second) {
                vit = _channels.erase(vit);
                break;
            }
        }
    }
    // 获取节点:轮询获取一个 channel 用于负载均衡提供服务
    ChannelPtr Channels::select() {
        // 1.加锁:确保线程安全
        std::unique_lock<std::mutex> lock(_mtx);
        // 2.轮询获取一个 channel
        if (_channels.empty()) {
            return nullptr;
        }
        ChannelPtr channel = _channels[_idx];
        _idx = (_idx + 1) % _channels.size();
        return channel;
    }

    ChannelsManager::ChannelsManager() {}
    ChannelsManager::~ChannelsManager() {}
    // 根据 server_name 获取 Channels 集合
    Channels::ptr ChannelsManager::_Channels(const std::string& server_name) {
        // 1.加锁:确保线程安全
        std::unique_lock<std::mutex> lock(_mtx);
        // 2.根据 server_name 从 _map 中获取 Channels 集合
        auto it = _map.find(server_name);
        if (it == _map.end()) {
            return Channels::ptr();
        }
        return it->second;
    }
    // 设置服务监控:根据 server_name 初始化一个 Channels 集合,添加到 _map 中进行管理
    void ChannelsManager::setWatch(const std::string& server_name) {
        // 1.加锁:确保线程安全
        std::unique_lock<std::mutex> lock(_mtx);
        // 2.根据 server_name 初始化一个 Channels 集合
        Channels::ptr channels = std::make_shared<Channels>();
        // 3.将 channels 插入到 _map 中
        _map.insert(std::make_pair(server_name, channels));
    }
    // 新增节点:根据 server_name 和 addr 新增一个 channel
    void ChannelsManager::addNode(const std::string& server_name, const std::string& addr) {
        // 1.根据 server_name 获取 Channels 集合
        Channels::ptr channels = _Channels(server_name);
        if (channels == nullptr) {
            return;
        }
        // 2.根据 addr 新增一个 channel
        channels->insert(addr);
    }
    // 删除节点:根据 server_name 和 addr 删除一个 channel
    void ChannelsManager::delNode(const std::string& server_name, const std::string& addr) {
        // 1.根据 server_name 获取 Channels 集合
        Channels::ptr channels = _Channels(server_name);
        if (channels == nullptr) {
            return;
        }
        // 2.根据 addr 删除一个 channel
        channels->remove(addr);
    }
    // 获取节点:根据 server_name 获取一个 channel
    ChannelPtr ChannelsManager::getNode(const std::string& server_name) {
        // 1.根据 server_name 获取 Channels 集合
        Channels::ptr channels = _Channels(server_name);
        if (channels == nullptr) {
            return ChannelPtr();
        }
        // 2.根据轮询策略获取一个 channel
        return channels->select();
    }

    // 创建 Closure 类的实例:根据回调函数创建一个 Closure 类的实例
    google::protobuf::Closure* ClosureFactory::create(callback_t &&cb) {
        Object::ptr obj = std::make_shared<Object>();
        obj->callback = std::move(cb);
        return brpc::NewCallback(&ClosureFactory::asyncCallback, obj);
    }
    // 异步回调函数:用于在 Closure 类的实例完成时调用回调函数
    void ClosureFactory::asyncCallback(const Object::ptr obj) {
        obj->callback();
    }
    
    // 创建 RpcServer 类的实例:根据端口号和服务对象创建一个 RpcServer 类的实例
    std::shared_ptr<brpc::Server> RpcServerFactory::create(int port, google::protobuf::Service* service) {
        // 1.初始化 ServerOptions
        brpc::ServerOptions options;
        options.idle_timeout_sec = -1; // 禁用空闲超时
        // 2.创建 Server 实例
        std::shared_ptr<brpc::Server> server = std::make_shared<brpc::Server>();
        int ret = server->AddService(service, brpc::SERVER_OWNS_SERVICE); // 由服务端负责释放 service
        if (ret == -1) {
            ERR("添加服务失败!");
            abort(); // 终止程序
        }
        ret = server->Start(port, &options);
        if (ret == -1) {
            ERR("启动服务失败!");
            abort();
        }
        return server;
    }
}

2. 使用样例

2.1 目录结构

bash 复制代码
test/
|-- brpc
    |-- cal.pb.cc
    |-- cal.pb.h
    |-- cal.proto
    |-- makefile
    |-- rpc_client.cc
    |-- rpc_server.cc

2.2 项目构建

bash 复制代码
# makefile
all: rpc_server rpc_client
rpc_server: rpc_server.cc cal.pb.cc ../../source/xzyrpc.cc ../../source/xzylog.cc
	g++ -o $@ $^ -std=c++17 -lbrpc -lleveldb -lprotobuf -lpthread -ldl -lssl -lcrypto -lgflags -lfmt -lspdlog
rpc_client: rpc_client.cc cal.pb.cc ../../source/xzyrpc.cc ../../source/xzylog.cc
	g++ -o $@ $^ -std=c++17 -lbrpc -lleveldb -lprotobuf -lpthread -ldl -lssl -lcrypto -lgflags -lfmt -lspdlog

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

.PHONY: clean
clean:
	rm -f rpc_server rpc_client cal.pb.cc

2.3 代码实现

cpp 复制代码
// rpc_client.cc
#include "../../source/xzyrpc.h"
#include "../../source/xzylog.h"
#include "cal.pb.h"

int main(int argc, char* argv[]) 
{
    // 1.创建 ChannelsManager 实例
    xzyrpc::ChannelsManager channels_manager;
    // 2.关注 calculate 服务
    channels_manager.setWatch("calculate");
    // 3.添加 calculate 服务节点
    channels_manager.addNode("calculate", "192.168.174.128:9000");
    // 4.获取 calculate 服务节点
    xzyrpc::ChannelPtr channel = channels_manager.getNode("calculate");
    if (channel == nullptr) {
        ERR("没有可供 calculate 服务节点可供使用");
        abort();
    }
    // 5.创建 Controller、AddReq、AddRsp、Closure 实例
    brpc::Controller* cntl = new brpc::Controller();
    cal::AddReq* req = new cal::AddReq();
    req->set_num1(10);
    req->set_num2(20);
    cal::AddRsp* rsp = new cal::AddRsp();
    google::protobuf::Closure* closure = xzyrpc::ClosureFactory::create([=](){
       std::unique_ptr<brpc::Controller> cntl_guard(cntl);
       std::unique_ptr<cal::AddReq> req_guard(req);
       std::unique_ptr<cal::AddRsp> rsp_guard(rsp);
       if (cntl_guard->Failed() == true) {
           ERR("RPC 调用失败: %s", cntl_guard->ErrorText());
           return;
       }
       std::cout << "RPC 调用结果:" << rsp_guard->result() << std::endl;
    });
    // 6.创建 CalService_Stub 实例
    cal::CalService_Stub stub(channel.get());
    // 7.发起 RPC 调用
    stub.Add(cntl, req, rsp, closure);
    std::cout << "====================" << std::endl;
    getchar();

    return 0;   
}
cpp 复制代码
// rpc_server.cc
#include "../../source/xzyrpc.h"
#include "cal.pb.h"

// 创建 CalServiceImpl 类来实现 CalService 服务
class CalServiceImpl : public cal::CalService {
public:
    CalServiceImpl() {}
    ~CalServiceImpl() {}
    // 重写 Add 方法
    virtual 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);
    }
};

int main(int argc, char* argv[]) 
{
    // 1.创建 CalServiceImpl 实例
    CalServiceImpl* cal_server = new CalServiceImpl();
    // 2.创建 RpcServer 实例
    std::shared_ptr<brpc::Server> server = xzyrpc::RpcServerFactory::create(9000, cal_server);
    // 3.启动 RPC 服务器
    server->RunUntilAskedToQuit();

    return 0;   
}
相关推荐
啟明起鸣2 小时前
【C++ 性能提升技巧】C++ 的引用、值类型、构造函数、移动语义与 noexcept 特性,可扩容的容器
开发语言·c++
故以往之不谏2 小时前
函数--值传递
开发语言·数据结构·c++·算法·学习方法
卢锡荣2 小时前
Type-c OTG数据与充电如何进行交互使用应用讲解
c语言·开发语言·计算机外设·电脑·音视频
A懿轩A2 小时前
【Java 基础编程】Java 变量与八大基本数据类型详解:从声明到类型转换,零基础也能看懂
java·开发语言·python
2301_811232982 小时前
低延迟系统C++优化
开发语言·c++·算法
我能坚持多久2 小时前
D20—C语言文件操作详解:从基础到高级应用
c语言·开发语言
橘子师兄3 小时前
C++AI大模型接入SDK—ChatSDK封装
开发语言·c++·人工智能·后端
上天_去_做颗惺星 EVE_BLUE3 小时前
Docker高效使用指南:从基础到实战模板
开发语言·ubuntu·docker·容器·mac·虚拟环境