brpc远程过程调用

目录

rpc基本工作流程

存根(Stub)类的作用

工作流程

brpc

brpc使用流程

brpc使用示例

1.编写main.proto文件

2.服务器代码编写


rpc基本工作流程

RPC(Remote Procedure Call,远程过程调用)是一种计算机通信协议,它允许一台计算机(客户端)通过网络调用另一台计算机(服务器)上的函数或方法,而无需了解底层网络技术的细节。

简单来说, RPC 的核心思想是 " 像调用本地函数一样调用远程函数**",它隐藏了网络通信的复杂性(如数据传输、** 序列化 、寻址等),让开发者可以更专注于业务逻辑。

存根(Stub)类的作用

存根(Stub)类是一种关键组件,它扮演着 "代理" 或 "中间层" 的角色,简化了客户端与远程服务之间的通信过程。

  1. 本地代理:Stub 类在客户端侧提供与远程服务完全一致的方法接口,客户端无需知道服务的具体位置和通信细节。

  2. 数据处理:接收客户端的调用参数,将其序列化为网络传输格式(如 protobuf、JSON)。接收远程服务返回的结果,反序列化为客户端可直接使用的格式。

  3. 网络通信:负责与远程服务建立连接、发送请求、接收响应,处理超时、错误等异常情况。

++工作流程++

  1. 客户端调用本地 Stub:客户端通过调用本地的 Stub(存根)类中的方法发起请求,传入相应参数。

  2. 参数序列化:Stub 将调用的方法名、参数等信息转换为可在网络上传输的格式(如二进制、JSON、Protobuf 等)。

  3. 网络传输:客户端通过网络将序列化后的数据发送给服务器。

  4. 服务器 Stub 处理:服务器端的 Stub 接收请求,将数据反序列化为本地可理解的格式。

  5. 调用本地服务:服务器 Stub 调用实际的本地函数或方法,执行相应逻辑。

  6. 返回结果:服务器将执行结果通过同样的流程(序列化→网络传输→反序列化)返回给客户端 Stub,最终客户端得到结果。

RPC 与 HTTP 接口(如 RESTful API)的区别在于:RPC 更注重 "函数调用" 的语义,通常更高效(二进制协议为主);而 HTTP 接口更偏向资源访问,协议可读性强(文本协议为主)。实际开发中可根据需求选择合适的通信方式。

brpc

brpc 是百度开源的一款高性能、低延迟的 RPC 框架,全称为 "Baidu RPC",主要用于构建分布式系统中的高性能服务通信层。它支持多种协议(如 HTTP、protobuf、thrift 等),并针对高并发、低延迟场景进行了优化,广泛应用于百度内部的大规模分布式系统。

特点:

  • 基于 C++ 开发,提供简洁的 API,集成流程简单。

  • 与 protobuf 无缝结合,通过 .proto 文件定义服务接口,自动生成代码。

应用场景:

  • 高并发、低延迟的分布式服务(如微服务架构)。

  • 需要跨语言通信的系统(结合 protobuf )。

  • 对性能和稳定性要求严格的后端服务(如搜索引擎、大数据处理)

Protocol Buffers(protobuf)具备强大的跨语言能力,protobuf 的跨语言特性主要体现在:

语言无关的接口定义:使用.proto文件定义数据结构(message)和服务(service),这种定义方式与具体编程语言无关。

protobuf 编译器(protoc)可以为多种编程语言生成对应的代码(序列化 / 反序列化逻辑、数据访问类等)。目前官方支持的语言包括:

  • C++、Java、Python

  • Go、Ruby、C#、PHP、Dart

  • 此外还有社区支持的更多语言(如 JavaScript、Rust、Swift 等)

++跨语言通信示例:++

  1. 定义一个.proto文件(语言无关)

  2. 用 protoc 分别生成 Java 和 Python 代码

  3. Java 程序创建数据并序列化

  4. 通过网络将二进制数据发送给 Python 程序

  5. Python 程序反序列化并读取数据

++brpc使用流程++

1.定义服务接口(如用protobuf中的service关键字)

2.生成客户端和服务端的框架代码

3.服务端实现接口逻辑,客户端通过sub调用远程服务

4.配置服务器地址,协议类型,超时时间等参数并启动服务

brpc使用示例

++1.编写main.proto文件++

复制代码
syntax="proto3";
package xg;

//这个选项设置为true时,protoc会为我们生成.proto文件中定义的服务(service)生成通用的C++服务框架代码
//包括服务接口类和存根类
option cc_generic_services=true;
message EchoRequest{
    string message=1;
}

message EchoResponse{
    string message=1;
}

service EchoService{
    rpc Echo(EchoRequest) returns (EchoResponse);
}

编译.proto文件,生成main.pb.h和main.pb.cc文件

复制代码
 protoc --cpp_out=. main.proto

通过service定义的EchoService服务,主要生成了如下两个了类:EchoService和EchoService_Stub

EchoService这个类中有一个Echo虚函数,和main.proto文件对应。这个类需要服务端实现,我们需要定义一个类继承自EchoService,并重写Echo这个方法,也就是实现内部的业务处理逻辑。

这个Echo函数的参数中,有两个参数:request和response,表示对请求request进行处理完成后,将结果再写入到response中,最后发送给客户端。

EchoService_Stub这个类是客户端使用的存根(stub)类,也是客户端进行远程调用的类。这个类也实现了一个Echo函数,客户端调用Echo方法 向服务器发起请求,获取响应。就好像是客户端在本地调用服务器的方法一样。

在这两个类中,Echo方法的参数都一样,上面解释了request和response参数,接下来解释controller和done参数。

::PROTOBUF_NAMESPACE_ID::RpcController* controller和::google::protobuf::Closure* done这两个类型是protobuf内部自己实现的。部分源码如下:

复制代码
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() ;
}
}
}
  1. RpcController的作用:我们在进行rpc请求的时候,是没有返回值的,返回类型是void。这个类的作用就是判断rpc请求是否出错(Failed接口),如果出错,出错原因是什么(ErrorText接口)。

  2. Closure的作用:

    由于对于请求的业务处理,不一定是在Echo方法中的,也就是说Echo方法执行结束不代表业务处理完成,所以此时就无法构造响应。所以在这个类中有一个run方法,当我们将业务处理完成,响应构造好之后再调用run方法,就可以将响应发送给客户端了。

    1. 对于客户端这边: 这个类在异步请求的时候会使用到,如果是同步请求不会涉及到。所谓的异步请求是指我们的rpc操作是异步执行的。当客户端收到响应,对于响应该如何处理?所以这个类支持我们设置一个回调函数,来进行响应的处理。客户端收到响应后,就会根据我们设置的回调函数自动进行处理。

    2. 对于服务器这边: 由于对于请求的业务处理,不一定是在Echo方法中的,也就是说Echo方法执行结束不代表业务处理完成,所以此时就无法构造响应。所以在这个类中有一个run方法,当我们将业务处理完成,响应构造好之后再调用run方法,就可以将响应发送给客户端了。

++2.服务器代码编写++

  1. 创建rpc服务子类继承pb中的EchoService服务类,并实现内部的业务处理逻辑(Echo)。

  2. 创建rpc服务器类,搭建服务器。

  3. 向服务器类中添加子服务对象。

  4. 启动服务器

    #include <iostream>
    #include <string>
    #include <butil/logging.h> //brpc中的日志输出模块
    #include <brpc/server.h>
    #include "main.pb.h"

    //1.创建子类继承自EchoService类,并完成业务处理
    class EchoServicelmp:public xg::EchoService
    {
    public:
    EchoServicelmp(){}
    ~EchoServicelmp(){}
    void Echo(google::protobuf::RpcController* controller,
    const ::xg::EchoRequest* request,
    ::xg::EchoResponse* response,
    ::google::protobuf::Closure* done)
    {
    brpc::ClosureGuard rpc_guard(done);//RAII思想,析构时会调用done->run()
    std::cout<<"收到消息:"<<request->message()<<std::endl;
    std::string str=request->message()+"这是响应!!!";
    response->set_message(str);
    //done->run();
    }
    };

    int main(int argc,char* argv[])
    {
    logging::LoggingSettings settings;
    settings.logging_dest=logging::LoggingDestination::LOG_TO_NONE;//关闭brpc的日志输出
    logging::InitLogging(settings);
    //2.构造服务器对象
    brpc::Server server;
    //向服务器对象中,新增EchoService服务
    EchoServicelmp echo_service;
    int ret=server.AddService(&echo_service,brpc::ServiceOwnership::SERVER_DOESNT_OWN_SERVICE);
    if(ret==-1)
    {
    std::cout<<"添加 echo_service服务失败"<<std::endl;
    return -1;
    }
    //3.启动服务器
    brpc::ServerOptions options;//配置服务器的启动参数,如超时时间
    options.idle_timeout_sec=-1;//连接超时时间,超时后会自动关闭连接,-1表示一直等待
    options.num_threads=1;//线程数量
    ret=server.Start(8080,&options);
    if(ret==-1)
    {
    std::cout<<"服务器启动失败"<<std::endl;
    return 2;
    }
    server.RunUntilAskedToQuit();//休眠等待运行的结束
    return 0;
    }

++3.客户端代码编写++

  1. 创建通信信道

  2. 实例化pb中的EchoService_Stub类

  3. 调用EchoService_Stub类中对应的接口发起rpc请求

  4. 获取响应,进行处理

    #include <iostream>
    #include <memory>
    #include <thread>
    #include <chrono>
    #include <brpc/channel.h>
    #include "main.pb.h"
    // 同步请求
    // int main(int argc, char *argv[])
    // {
    // // 1.构造channel信道对象,连接服务器
    // brpc::ChannelOptions options;
    // options.connect_timeout_ms = -1; // 连接超时时间
    // options.timeout_ms = -1; // rpc请求等待超时时间
    // options.max_retry = 3; // 请求重试次数
    // options.protocol = "baidu_std"; // 序列化协议,默认使用baidu_std

    // brpc::Channel channel;
    // int ret = channel.Init("127.0.0.1:8080", &options);
    // if (ret == -1)
    // {
    // std::cout << "信道初始化失败" << std::endl;
    // return -1;
    // }

    // // 2.构造EchoService_Stub对象,用于rpc调用
    // xg::EchoService_Stub echo_stub(&channel);
    // xg::EchoRequest req;
    // req.set_message("hello world");

    // // RpcController 是 protobuf 定义的抽象类,用于控制 RPC 调用的上下文
    // // brpc 中通过其实现类 brpc::Controller 扩展了更多功能(如设置超时、获取远程地址等)
    // brpc::Controller ctl; // 控制器,用于控制brpc调用的上下文(如超时、错误信息、元数据等)
    // xg::EchoResponse resp;
    // echo_stub.Echo(&ctl, &req, &resp, nullptr); // 同步请求
    // if (ctl.Failed() == true)
    // {
    // std::cout << "rpc调用失败: " << ctl.ErrorText() << std::endl;
    // return -2;
    // }
    // std::cout << "收到响应:" << resp.message() << std::endl;
    // return 0;
    // }

    void callback(brpc::Controller *ctl, xg::EchoResponse *resp)
    {
    std::unique_ptrbrpc::Controller ctl_guard(ctl);
    std::unique_ptrxg::EchoResponse resp_guard(resp);
    if (ctl_guard->Failed() == true)
    {
    std::cout << "rpc调用失败: " <<ctl_guard->ErrorText() << std::endl;
    return;
    }
    std::cout << "收到响应:" << resp_guard->message() << std::endl;
    }

    // 异步请求
    int main(int argc, char *argv[])
    {
    // 1.构造channel信道对象,连接服务器
    brpc::ChannelOptions options;
    options.connect_timeout_ms = -1; // 连接超时时间
    options.timeout_ms = -1; // rpc请求等待超时时间
    options.max_retry = 3; // 请求重试次数
    options.protocol = "baidu_std"; // 序列化协议,默认使用baidu_std

    复制代码
     brpc::Channel channel;
     int ret = channel.Init("127.0.0.1:8080", &options);
     if (ret == -1)
     {
         std::cout << "信道初始化失败" << std::endl;
         return -1;
     }
    
     // 2.构造EchoService_Stub对象,用于rpc调用
     xg::EchoService_Stub echo_stub(&channel);
     xg::EchoRequest req;
     req.set_message("hello world");
    
     // RpcController 是 protobuf 定义的抽象类,用于控制 RPC 调用的上下文
     // brpc 中通过其实现类 brpc::Controller 扩展了更多功能(如设置超时、获取远程地址等)
     brpc::Controller *ctl = new brpc::Controller(); // 控制器,用于控制brpc调用的上下文(如超时、错误信息、元数据等)
     xg::EchoResponse *resp = new xg::EchoResponse();
     // 异步请求
     auto closure = google::protobuf::NewCallback(callback, ctl, resp);
     echo_stub.Echo(ctl, &req, resp, closure);
    
     std::cout<<"异步调用结束"<<std::endl;
     std::this_thread::sleep_for(std::chrono::seconds(3));
     return 0;

    }

相关推荐
想你依然心痛2 小时前
Spark大数据分析与实战笔记(第五章 HBase分布式数据库-05)
数据库·分布式·spark
关关长语2 小时前
Docker在Linux中离线部署
linux·docker·容器
青草地溪水旁2 小时前
设计模式(C++)详解—享元模式(2)
c++·设计模式·享元模式
郝学胜-神的一滴3 小时前
QT与Spring Boot通信:实现HTTP请求的完整指南
开发语言·c++·spring boot·后端·qt·程序人生·http
小陈又菜3 小时前
【C++】Template:深入理解特化与分离编译,破解编译难题
开发语言·c++·template·类模板
_不会dp不改名_3 小时前
C++开源库使用:nlohmann/json
c++·json·mfc
Tisfy3 小时前
LeetCode 3508.设计路由器:STL套STL——有什么需求就设计什么数据结构
c++·leetcode·题解·设计·哈希表
夜猫逐梦3 小时前
【C++】Visual Studio+CMake 开发 C++ 入门指南:从环境搭建到项目实战
开发语言·c++·visual studio
tumu_C3 小时前
无用知识研究:用sfinae实现函数模板的overload [一]
开发语言·c++·算法