功能强大的protobuf,初识rpc框架

最近在学习分布式系统,学一点皮毛分布式系统,了解一下分布式系统。

目前我对分布式的理解就是一个子模块请求调用某个服务,通过zookper知道调用那个服务,目前的zookper还没有学习,这份事例假设程序已经知道调用的服务在那台进程/主机上,代码太多了,本文采取伪代码形式,主要是讲清楚protobuf中的作用以及rpc框架的思路

详解protobuf

syntax = "proto3";

package fixbug;

option cc_generic_services = true;

message ResultCode{
    int32 errcode = 1;// 0为成功
    bytes errmsg = 2;
}

message LoginRequest{
    bytes name = 1;
    bytes pwd = 2;
}

message LoginResponse{
    ResultCode result = 1;// 类的对象
    bool sucess = 2;
}


message RegisterRequest{
    uint32 id = 1;
    bytes name = 2;
    bytes pwd = 3;
}

message RegisterResponse{
    ResultCode result = 1;// 类的对象
    bool sucess = 2;
}

// protobuf中service的函数才是rpc提供的函数
service UserServiceRpc{
    rpc Login(LoginRequest)returns(LoginResponse);
    rpc Register(RegisterRequest)returns(RegisterResponse);
}

protobuf中的UserServiceRpc类型是service,他会生成两个类,一个类UserServiceRpc另一个类是UserServiceRpc_Stub。Service是基类,UserServiceRpc继承了Service,UserServiceRpc_Stub继承了UserServiceRpc。
在这里插入图片描述
大概长这个样子的,新学的UML画图,嘻嘻UML在线画图链接
UserService是服务端定义的类,我们暂时不用理会。目前知道类与类之间的关系即可。

客户端需要两个类RpcChannle和UserServiceRpc_Stub,但是RpcChannle是protobuf提供的,但是他是一个抽象类,需要我们继承重写RpcChannle中的CallMethod函数。类的关系大概是这个样子的
在这里插入图片描述
main.cpp创建一个请求对象,设置好要请求的值。通过UserServiceRpc_Stub来调用,调用Login函数,protobuf内部通过descriptor就能感知客户端调用的是那一个函数。stub调用Login函数之后,就会调用客户端自定义的RpcChannel类中的callmethod的函数

在这里插入图片描述
main.cpp

int main(int argc,char** argv){
    /*
    	..... // 加载配置项呀,获得rpc服务的ip和端口号
    */

    // 没有参数的构造函数,只能set去设置值
    fixbug::LoginRequest request;
    request.set_name("zhangsan");
    request.set_pwd("123456");

    // 到时候获得响应
    fixbug::LoginResponse response;

    // stub给远程调用请求者使用的
    fixbug::UserServiceRpc_Stub stub(new MpRpcChannel());
    
    
    stub.Login(nullptr,&request,&response,nullptr);//RpcChannel::callMethod 集中来做所有rpc方法调用的参数序列化和网络发送 

    // 一次rpc调用完成,读调用结果
    if(response.result().errcode() == 0){
        std::cout << "rpc login response:" << response.sucess() << std::endl;
    }
    else{
        std::cout << "rpc login response error : " << response.result().errmsg() << std::endl;
    }
    
    return 0;
}

重写CallMethod这个方法,准备发送给服务端调用请求,需要发送的有请求的是那个类?请求类中的那个函数?请求这个类中的要携带那些参数?这三行代码帮我们解决前两个问题,参数通过request这个对象序列化来解决,然后发送给服务端,再等待服务端发回来的数据,然后将返回来的response反序列化变成对象,返回给main函数,至此客户端的问题我们就梳理完成了!

  const google::protobuf::ServiceDescriptor* sd = method->service();
    std::string service_name = sd->name();
    std::string method_name = method->name();
#include "MpRpcChannel.h"
#include "google/protobuf/descriptor.h"
#include "google/protobuf/message.h"
#include "rpcheader.pb.h"
#include "MprpcApplication.h"

#include <unistd.h>

#include <string>
#include <sys/types.h>          
#include <sys/socket.h>
#include <arpa/inet.h>

void MpRpcChannel::CallMethod(const MethodDescriptor* method,
                          RpcController* controller, const Message* request,
                          Message* response, Closure* done)
{
    const google::protobuf::ServiceDescriptor* sd = method->service();
    std::string service_name = sd->name();
    std::string method_name = method->name();

    // 获取参数的序列化字符串长度args_size
    std::string args_str;
    uint32_t args_size = 0;
    if(request->SerializeToString(&args_str)){
        args_size = args_str.size();
    }
    else
    {
        std::cout << "serialize request error!" << std::endl;
        return ;
    }

    //定义rpc的请求header   
    mprpc::RpcHeader rpcHeader;
    rpcHeader.set_service_name(service_name);
    rpcHeader.set_method_name(method_name);
    rpcHeader.set_args_size(args_size);
    std::string rpc_header_str;
    
    uint32_t header_size = 0;
    if(rpcHeader.SerializeToString(&rpc_header_str)){
        header_size = rpc_header_str.size();
    }
    else{
        std::cout << "serialize rpc header error!" << std::endl;
        return ;
    
    }

    // 组织待发送的rpc请求的字符串
    std::string send_rpc_str;
    
    send_rpc_str.insert(0,std::string((char*)&header_size,4));
    send_rpc_str += rpc_header_str;
    send_rpc_str += args_str;


    std::cout << "================" << std::endl;
    std::cout << "header_size" << header_size << std::endl;
    std::cout << "rpc_header_str" << rpc_header_str << std::endl;
    std::cout << "service_name" << service_name << std::endl;
    std::cout << "method_name" << method_name << std::endl;
    std::cout << "args_str" << args_str << std::endl;

    //使用tcp编程,完成rpc方法的远程调用

    int clientfd = ::socket(AF_INET,SOCK_STREAM,0);
    if(clientfd == -1){
        std::cout << "create socket errno:" << errno << std::endl;
        exit(EXIT_FAILURE);
    }
    std::string ip = MprpcApplication::GetInstance().getConfig().Load("rpcServerIp");
    uint16_t port = std::atoi(MprpcApplication::GetInstance().getConfig().Load("rpcServerPort").c_str());
    struct  sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    ::inet_pton(AF_INET,ip.c_str(),&addr.sin_addr);

    if(-1 == ::connect(clientfd,(sockaddr*)&addr,sizeof(addr))){
        std::cout << "connect error! errno: " << errno << std::endl;
        close(clientfd);
        exit(EXIT_FAILURE);
    }

    // 发送rpc请求
    if(-1 == send(clientfd,send_rpc_str.c_str(),send_rpc_str.size(),0)){
        std::cout << "send error" << errno << std::endl;
        close(clientfd);
        return ;
    } 

    // 接收rpc请求的响应值
    char buf[1024]={0};
    int recv_size = 0;
    if(-1 == (recv_size = ::recv(clientfd,buf,sizeof(buf),0))){
        std::cout << "recv error " << std::endl;
        close(clientfd);
        return ;
    }

    // std::string response_str(buf);// 遇到\0后面的数据就存不下来了
    if(!response->ParseFromArray(buf,recv_size)){
        std::cout << "parse error! response_str: " << buf << std::endl;
        close(clientfd);
        return ;
    }
    else{
        std::cout << "rpc 反序列化成功" << std::endl;
    }
    close(clientfd);
}

服务端需要有两个protobuf,一个是提供具体的服务,就是上面的那个protobuf,一个是rpc框架的porotobuf,要有请求的类名、请求的函数名、请求参数的个数,还有请求的参数的值,相当于要接收两个protobuf文件

mprpc的protobuf文件

syntax = "proto3";

package mprpc;

message RpcHeader{
    bytes service_name = 1;
    bytes method_name = 2;
    uint32 args_size = 3;
}

main.cpp这边

#include <iostream>
#include <string>
#include "user.pb.h"
#include "MprpcApplication.h"
#include "RpcProvider.h"
using namespace std;
using namespace fixbug;

/**
*   UserService原来是一个本地服务,提供了两个进程内的本地方法,Login和GetFrienLists
*   继承proto文件中的service的抽象类中的方法,内部通过框架来进行操作
*/

class UserService:public UserServiceRpc{ // 使用rpc服务发布端()
public:
    bool Login(std::string name,std::string pwd){
        cout << "doing local service:Login" << endl;
        cout << "name: " << name << "pwd: " << endl;
        return false;
    }
    /**  
    *   重写基类UserService的虚函数 下面这些方法都是框架直接调用的
    *   1、caller ==》 login(LoginRequest) => muduo => callee
    *   2、然后调用下面这个虚函数Login方法
    */
    void Login(::google::protobuf::RpcController* controller,
                       const ::fixbug::LoginRequest* request,
                       ::fixbug::LoginResponse* response,
                       ::google::protobuf::Closure* done)override
    {
        // 框架给业务上报了请求参数LoginRequest,应用获取相应数据做本地业务,已经做好了序列化了
        string name = request->name();
        string pwd = request->pwd();
        bool login_result = Login(name,pwd);

        // 把结果发回去,对象序列化也交给框架做了
        ResultCode* code = response->mutable_result();
        code->set_errcode(-1);
        code->set_errmsg("request faile");
        response->set_sucess(login_result);

        // 执行回调操作,在RpcProvider中提供了这个done
        done->Run();
    }
};

int main(int argc,char** argv){
    // 调用框架的初始化操作,端口号和ip地址
    MprpcApplication::Init(argc,argv);
    // 把UserService对象发布到rpc节点上
    RpcProvider provider;
    provider.NotifyService(new UserService());
    
    // 启动一个rpc服务结点 Run以后进程阻塞状态,等待远程的rpc调用请求
    provider.Run();
}

RpcProvider.cpp

NotifyService函数感知这个类是那个类,获取类的名字,这个类的方法名字都有什么,都记录在哈希表当中。

Run函数就是启动网络通信,如果一台机器不同进程可以考虑其他进程通信的手段,但是不同机器中通信只能采取网络通信了。这里我用的网络库是自己改写的muduo网络库,也可以用正宗的陈硕大牛写的muduo网络库。

OnMessage函数,收到调用的请求了,解析两个protobuf文件,再根据以前记录的哈希表,找到要请求的类、类中的函数、函数所需要的参数,都一并给到位

#include "RpcProvider.h"
#include "MprpcApplication.h"
#include "rpcheader.pb.h"


#include <mymuduo/InetAddress.h>
#include <mymuduo/Callbacks.h>


void RpcProvider::NotifyService(google::protobuf::Service* service){
    ServiceInfo service_info;
    
    // 获取要服务的对象描述信息
    const google::protobuf::ServiceDescriptor* pserviceDescriptor = service->GetDescriptor();
    // 获取服务的名字 有invaild的问题基本上就是找不到头文件了
    std::string service_name = pserviceDescriptor->name();

    std::cout << "service_name: " << service_name << std::endl; 

    // 获取服务对象service的方法的数量
    int methodCnt = pserviceDescriptor->method_count();

    for(int i = 0;i < methodCnt;++i){
        // 返回第几号方法,只能看不能修改,获取服务对象指定下标的服务方法描述
        const google::protobuf::MethodDescriptor* pmethodDesc = pserviceDescriptor->method(i);
        std::string method_name = pmethodDesc->name();
        service_info.m_methodMap_.insert({method_name,pmethodDesc});

        std::cout << "method_name: " << method_name << std::endl; 
    }
    service_info.m_service_ = service;
    m_serviceMap_.insert({service_name,service_info});
}

void RpcProvider::Run(){
    std::string ip = MprpcApplication::GetInstance().getConfig().Load("rpcServerIp");
    uint16_t port = std::atoi(MprpcApplication::GetInstance().getConfig().Load("rpcServerPort").c_str());
    InetAddress address(port,ip);

    TcpServer server(&m_eventLoop_,address,"RpcProvider");

    // 绑定连接回调函数和消息读写回调方法  分离网络代码和业务代码
    server.setConnectCallback(std::bind(&RpcProvider::OnConnection,this,std::placeholders::_1));
    
    server.setMessageCallback(std::bind(&RpcProvider::OnMessage,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));

    // 设置muduo网络库的线程数量
    server.setThreadNum(4);

    // 启动网络服务
    server.start();
    m_eventLoop_.loop();
}

void RpcProvider::OnConnection(const TcpConnectionPtr& conn){
    if(!conn->connected()){
        // 和rpc client的连接断开了
        conn->shutdown();
    }
}
/**
*   RpcProvider 和 RpcConsumer协商之间通信用的protobufu数据类型
*   service_name method_name args定义proto的message类型,进行数据的序列化和反序列化
*   有4个字节的头记录头的长度
*/
// 响应请求方请求的操作,并且在把他发送回去
void RpcProvider::OnMessage(const TcpConnectionPtr& coon,Buffer* buffer,Timestamp t){
   
    // 网络上接收的远程rpc调用请求的字符流   里头的字符有调用的函数名字和参数
    std::string recv_buf = buffer->retrieveAllAsString();

    // 从字符流中读取前4个字节的内容
    uint32_t header_size = 0;
    recv_buf.copy((char*)&header_size,4,0);

    // 根据header_size读取数据头的原始字符流,反序列化数据,得到rpc请求的详细信息
    std::string rpc_header_str = recv_buf.substr(4,header_size);
    mprpc::RpcHeader rpcHeader;

    std::string service_name;
    std::string method_name;
    uint32_t args_size;

    if(rpcHeader.ParseFromString(rpc_header_str)){
        // 数据头反序列化成功
        service_name = rpcHeader.service_name();
        method_name = rpcHeader.method_name();
        args_size = rpcHeader.args_size();
    }
    else{
        // 数据头反序列失败
        std::cout << "rpc_header_str:" << rpc_header_str << "parse error" << std::endl;
        return ;
    }
    // 获取rpc方法参数的字符流数据
    std::string args_str = recv_buf.substr(4+header_size,args_size); 

    std::cout << "================" << std::endl;
    std::cout << "header_size" << header_size << std::endl;
    std::cout << "rpc_header_str" << rpc_header_str << std::endl;
    std::cout << "service_name" << service_name << std::endl;
    std::cout << "method_name" << method_name << std::endl;
    std::cout << "args_str" << args_str << std::endl;

    // 获取service对象和method对象
    auto it = m_serviceMap_.find(service_name);
    if(it == m_serviceMap_.end()){
        std::cout << "service name is not exits" << std::endl;
        return ;
    }


    auto mit = it->second.m_methodMap_.find(method_name);
    if(mit ==  it->second.m_methodMap_.end()){
        std::cout << "method name is not exits " << std::endl;
        return ;
    }
    
    google::protobuf::Service* service = it->second.m_service_;// 获取service对象
    const google::protobuf::MethodDescriptor* method = mit->second;// 获取method对象

    // 生成rpc方法调用的请求request和response参数
    google::protobuf::Message* request = service->GetRequestPrototype(method).New();

    // 进行反序列化
    if(!request->ParseFromString(args_str)){
        std::cout << "request parse error content: " << args_str << std::endl;
        return ;
    }
    google::protobuf::Message* response = service->GetResponsePrototype(method).New();

    // 给下面的method方法的调用,绑定一个Closure的回调函数,done用于服务端调用
    google::protobuf::Closure* done =  google::protobuf::NewCallback<RpcProvider,
                            const TcpConnectionPtr&,google::protobuf::Message*>
                            (this,&RpcProvider::SendRpcResponse,coon,response);

    // 在框架上根据远端rpc请求,调用当前rpc节点发布的方法
    // new UserService().login(controller,request,response,done);
    // 相当于回调函数,调用rpc服务端的函数。
    service->CallMethod(method,nullptr,request,response,done);
}

void RpcProvider::SendRpcResponse(const TcpConnectionPtr& conn,google::protobuf::Message* response){
    std::string response_str;
    // 序列化
    if(response->SerializeToString(&response_str)){
        conn->send(response_str);
    }
    else{
        std::cout << "serialize response_str error" << std::endl;
    }
    conn->shutdown();// 发送完自动断开
    

}



远程请求调用端过于理想化,想着是一定成功,那么失败的化要怎么处理?
之前的代码如下

 stub.Login(nullptr,&request,&response,nullptr);

现在我们重写下这个类,让这个类来判断一下请求过程中是否有错误产生

MprRpcController mprRpcController;
stub.Login(&mprRpcController,&request,&response,nullptr);/

MprRpcController.h

#pragma once
#include <google/protobuf/service.h>
#include <string>

class MprRpcController:public google::protobuf::RpcController
{
public:
   void Reset() override;

  
   bool Failed() const override;


  std::string ErrorText() const override;

  void StartCancel() override;


  void SetFailed(const std::string& reason) override;


  bool IsCanceled() const override;

  void NotifyOnCancel(google::protobuf::Closure* callback)override;

private:
    bool m_failed_ = false;
    std::string m_errText_ = "";
};


MprRpcController.cpp,用不到的地方就没有写

#include "MprRpcController.h"
// 置成最初的状态,false表示未发生错误
void MprRpcController::Reset() {
    m_failed_ = false;
    m_errText_ = "";
}

// 意思是是否失败
bool MprRpcController::Failed() const {
    return m_failed_;
}


std::string MprRpcController::ErrorText()const {
    return m_errText_;
}

void MprRpcController::StartCancel() {}


void MprRpcController::SetFailed(const std::string& reason) {
    m_failed_ = true;
    m_errText_ = reason;
}


bool MprRpcController::IsCanceled() const { return true;}

void MprRpcController::NotifyOnCancel(google::protobuf::Closure* callback){}

比如套接字创建失败了,那就直接设置失败的内容,如下图所示
在这里插入图片描述

在请求端加上这个判断,出错了就不再进行了,没有出错的再继续执行
在这里插入图片描述

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值