MPRPC项目(第八天,(消费者视角)RpcChannel的调用过程以及实现)

一、实现目标

这里实现的就是左边黑圈里的过程:

开始调用->序列化->发送->接收响应结果->反序列化

右边的_stub是专门给rpc服务调用者来使用的,想要调用rpc服务,也应该用在user.proto定义好的

  • 定义了一个名为 Login 的远程过程调用方法
  • 输入参数:LoginRequest - 包含登录请求信息
  • 返回值:LoginResponse - 包含登录响应结果
cpp 复制代码
service UserServiceRpc{
    // Login handles user authentication with name and password
    rpc Login(LoginRequest) returns(LoginResponse);
}

二、caller下calluserservice.cc实现

cpp 复制代码
#include<iostream>
#include "user.pb.h"
#include"mprpcapplication.h"
#include"mprpcchannel.h"

int main(int argc,char **argv){
    //整个程序启动后,想用mprpc框架调用rpc服务,一定要先调用框架的初始化函数(只初始化一次)
    MprpcApplication::Init(argc,argv);

    //演示远程调用发布的rpc方法 Login
    fixbug::UserServiceRpc_Stub stub(new MprpcChannel());
    //stub.Login();

    //rpc方法的请求参数
    fixbug::LoginRequest request;
    request.set_name("zhang san");
    request.set_password("123456");

    //RPC方法的响应
    fixbug::LoginResponse response;
    
    //发起rpc方法的调用  同步的rpc调用过程  MprpcChannel::callmethod
    stub.Login(nullptr, &request, &response, nullptr);

    //一次rpc调用完整,读取调用结果
    //0表示成功,非0表示错误    如404,not found
    if(response.result().errcode() == 0){
        std::cout<<"rpc login resonse success: "<<response.success()<<std::endl;
    }else{
        std::cout<<"rpc login resonse failed: "<<response.result().errcode()<<" msg: "<<response.result().errmsg()<<std::endl;
    }

    return 0;
}

1、要先初始化整个rpc框架

cpp 复制代码
MprpcApplication::Init(argc,argv);
  • 初始化RPC应用程序
  • 解析命令行参数获取配置文件路径
  • 加载配置文件中的服务器IP、端口等配置信息

2、调用

cpp 复制代码
//演示远程调用发布的rpc方法 Login
    fixbug::UserServiceRpc_Stub stub(new MprpcChannel());
    //stub.Login();

    //rpc方法的请求参数
    fixbug::LoginRequest request;
    request.set_name("zhang san");
    request.set_password("123456");

    //RPC方法的响应
    fixbug::LoginResponse response;
    
    //发起rpc方法的调用  同步的rpc调用过程  MprpcChannel::callmethod
    stub.Login(nullptr, &request, &response, nullptr);
  1. fixbug::UserServiceRpc_Stub 就像是外卖平台的客户端APP

  2. stub 就是你手机上打开的外卖APP

  3. MprpcChannel() 就像是你的手机网络(4G/5G/WiFi)

最上面就是"打开外卖APP,连接到网络",最下面就是下单点餐。

3、获取响应结果

cpp 复制代码
 //一次rpc调用完整,读取调用结果
    //0表示成功,非0表示错误    如404,not found
    if(response.result().errcode() == 0){
        std::cout<<"rpc login resonse success: "<<response.success()<<std::endl;
    }else{
        std::cout<<"rpc login resonse failed: "<<response.result().errcode()<<" msg: "<<response.result().errmsg()<<std::endl;
    }

三、mprpcchannel.h

cpp 复制代码
#pragma once

#include<google/protobuf/service.h>
#include <google/protobuf/descriptor.h>
#include <google/protobuf/message.h>

class MprpcChannel:public google::protobuf::RpcChannel{
public:
    void CallMethod(const google::protobuf::MethodDescriptor* method,
                     google::protobuf::RpcController* controller,
                     const google::protobuf::Message* request,
                     google::protobuf::Message* response,
                     google::protobuf::Closure* done
    );
};

这里定义一个MprpcChannel类,继承google::protobuf::RpcChannel类,实现该虚函数

不管调用什么方法如这里stub.Login(),都会转换为对callmethod这一个方法调用

比如这里第一个参数就是Login

四、mprpcchannel.cc

该函数实现

1、先获取所有

2、将请求的rpc参数组装 数组的序列化(进行序列化后依然是string类型,cout遇到可打印的ASCLL字符就打印 )

3、发送rpc请求 wait

4、接收rpc响应 反序列化

先上完整代码

cpp 复制代码
#include "mprpcchannel.h"
#include <string>
#include "rpcheader.pb.h"
#include <sys/types.h>         
#include <sys/socket.h>
#include <error.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "mprpcapplication.h"
#include <unistd.h>

/*
rpc请求的数组组装   数组的序列化
发送rpc请求 wait
接收rpc响应   反序列化
*/
void MprpcChannel::CallMethod(const google::protobuf::MethodDescriptor* method,
                                google::protobuf::RpcController* controller,
                                const google::protobuf::Message* request,
                                google::protobuf::Message* response,
                                google::protobuf::Closure* done){
    //header_size +service_name + method_name + args_size + args_str
    const google::protobuf::ServiceDescriptor* sd = method->service();
    std::string service_name = sd->name();
    std::string method_name = method->name();

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

    //定义rpc的请求header
    mprpc::RpcHeader rpcHeader;
    rpcHeader.set_service_name(service_name);
    rpcHeader.set_method_name(method_name);
    rpcHeader.set_args_size(args_size);

    //将header序列化
    uint32_t header_size = 0;
    std::string rpc_header_str;
    if(rpcHeader.SerializeToString(&rpc_header_str)){
        header_size = rpc_header_str.size();
        
    }else{
        std::cout<<"serialize header failed"<<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<<"rpc_header_str: "<<rpc_header_str<<std::endl;
    std::cout<<"header_size: "<<header_size<<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;
    std::cout<<"send_rpc_str: "<<send_rpc_str<<std::endl;
    std::cout<<"=============================="<<std::endl;

    //使用tcp编程完成rpc方法的远程调用
    int clientfd = socket(AF_INET,SOCK_STREAM,0);
    if(clientfd == -1){
        std::cout<<"create socket failed"<<errno<<std::endl;
        return;
    }

    //设置服务端的ip和port
    struct sockaddr_in server_addr;
    std::string ip = MprpcApplication::GetInstance().GetConfig().Load("rpcserverip");
    uint16_t port = atoi(MprpcApplication::GetInstance().GetConfig().Load("rpcserverport").c_str());
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(port);
    server_addr.sin_addr.s_addr = inet_addr(ip.c_str());

    //连接rpc服务节点
    int ret = connect(clientfd,(struct sockaddr*)&server_addr,sizeof(server_addr));
    if(ret == -1){
        std::cout<<"connect failed"<<errno<<std::endl;
        close(clientfd);
        exit(EXIT_FAILURE);
    }

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

    //接收rpc响应
    char recv_buf[1024] = {0};
    int recv_size = 0;
    if((recv_size=recv(clientfd,recv_buf,1024,0)) == -1){
        std::cout<<"recv rpc response failed"<<errno<<std::endl;
        close(clientfd);
        return;
    }
    //反序列化  在复制给response_str时,遇到\0就不复制了,
    // std::string response_str(recv_buf,0,recv_size);
    // if(!response->ParseFromString(response_str)){
    if(!response->ParseFromArray(recv_buf,recv_size)){
        std::cout<<"response parse error"<<errno<<std::endl;
        close(clientfd);
        return;
    }
    
    close(clientfd);
}

1、两边的协议

服务端(RpcProvider)解析逻辑[4字节header_size] + [rpc_header_str] + [args_str] 解析,且从 rpc_header_str 里的 RpcHeader 对象取 args_size 来读 args_str。

所以客户端也应该按照这个格式发送。

二、拼接、发送

cpp 复制代码
 //拼接发送的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;

这里拼接的都是序列化后的

cpp 复制代码
//发送rpc请求
    if(send(clientfd,send_rpc_str.c_str(),send_rpc_str.size(),0) == -1){
        std::cout<<"send rpc request failed"<<errno<<std::endl;
        return;
    }

这里使用.ctr()是因为

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

  • buf 参数需要 const void*(或 const char*
  • C语言API不接受C++的 std::string 对象

三、建立连接

cpp 复制代码
//使用tcp编程完成rpc方法的远程调用
    int clientfd = socket(AF_INET,SOCK_STREAM,0);
    if(clientfd == -1){
        std::cout<<"create socket failed"<<errno<<std::endl;
        return;
    }

    //设置服务端的ip和port
    struct sockaddr_in server_addr;
    std::string ip = MprpcApplication::GetInstance().GetConfig().Load("rpcserverip");
    uint16_t port = atoi(MprpcApplication::GetInstance().GetConfig().Load("rpcserverport").c_str());
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(port);
    server_addr.sin_addr.s_addr = inet_addr(ip.c_str());

    //连接rpc服务节点
    int ret = connect(clientfd,(struct sockaddr*)&server_addr,sizeof(server_addr));
    if(ret == -1){
        std::cout<<"connect failed"<<errno<<std::endl;
        close(clientfd);
        exit(EXIT_FAILURE);
    }

先初始化好,然后用connect函数连接

四、接收响应

cpp 复制代码
//接收rpc响应
    char recv_buf[1024] = {0};
    int recv_size = 0;
    if((recv_size=recv(clientfd,recv_buf,1024,0)) == -1){
        std::cout<<"recv rpc response failed"<<errno<<std::endl;
        close(clientfd);
        return;
    }
    //反序列化  在复制给response_str时,遇到\0就不复制了,
    // std::string response_str(recv_buf,0,recv_size);
    // if(!response->ParseFromString(response_str)){
    if(!response->ParseFromArray(recv_buf,recv_size)){
        std::cout<<"response parse error"<<errno<<std::endl;
        close(clientfd);
        return;
    }

这里定义的recv_buf是数组,

通过注释里的方法,response_str只有\n,这是因为该构造函数希望以"\0"结尾

这样写也行

cpp 复制代码
//反序列化  在复制给response_str时,遇到\0就不复制了,
     std::string response_str(recv_buf,recv_size);
     if(!response->ParseFromString(response_str)){
    // if(!response->ParseFromArray(recv_buf,recv_size)){
        std::cout<<"response parse error"<<errno<<std::endl;
        close(clientfd);
        return;
    }

五、当前成果展示

相关推荐
阿蒙Amon9 分钟前
C#每日面试题-Array和ArrayList的区别
java·开发语言·c#
SmartRadio27 分钟前
ESP32添加修改蓝牙名称和获取蓝牙连接状态的AT命令-完整UART BLE服务功能后的完整`main.c`代码
c语言·开发语言·c++·esp32·ble
且去填词40 分钟前
Go 语言的“反叛”——为什么少即是多?
开发语言·后端·面试·go
知乎的哥廷根数学学派1 小时前
基于生成对抗U-Net混合架构的隧道衬砌缺陷地质雷达数据智能反演与成像方法(以模拟信号为例,Pytorch)
开发语言·人工智能·pytorch·python·深度学习·机器学习
yeziyfx2 小时前
kotlin中 ?:的用法
android·开发语言·kotlin
charlie1145141912 小时前
嵌入式的现代C++教程——constexpr与设计技巧
开发语言·c++·笔记·单片机·学习·算法·嵌入式
古城小栈2 小时前
Rust 网络请求库:reqwest
开发语言·网络·rust
hqwest2 小时前
码上通QT实战12--监控页面04-绘制6个灯珠及开关
开发语言·qt·qpainter·qt事件·stackedwidget
i橡皮擦2 小时前
TheIsle恐龙岛读取游戏基址做插件(C#语言)
开发语言·游戏·c#·恐龙岛·theisle
bing.shao3 小时前
golang 做AI任务执行
开发语言·人工智能·golang