RPC分布式网络通信框架(一)—— protobuf的使用


一、protobuf的好处

常见序列化和反序列化协议有XML、JSON、protobuf,相比于其他protobuf更有优势:
1、protobuf是二进制存储的,xml和json都是文本存储的。故protobuf占用带宽较低
2、protobuf不需要存储额外的信息。
json如何存储数据?键值对。例:Name:”zhang san”, pwd: “12345”。
protobuf存储数据的方式:“zhang san” “123456”(无额外信息)
3、protobuf跨平台语言支持。
可以直接在同构和异构系统中进行调用。异构系统指的是有的RPC进程是C++写的服务,有的RPC进程是Golang 或者Java 写的服务,但因为都是基于统一的protobuf 协议进行通信的,所以直接可以进行远程RPC通信。
4、protobuf序列化和反序列化效率高速度快且序列化后体积比XML和JSON都小很多,适合网络传输。
在这里插入图片描述
参考链接

二、如何创建proto

1、定义版本和声明,第三个为生成service所需要的声明(service服务类和rpc方法描述默认不生成)

syntax = "proto3";
package fixbug;
option cc_generic_services = true;

2、定义远端调用函数的input参数和return参数

message ResultCode
{
    int32 errcode = 1; 
    bytes errmsg = 2;
}

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

message LoginResponse
{
    ResultCode result = 1;
    bool sucess = 2;
}

注意,建议将string类型替换为bytes类型,因为bytes直接存二进制文件,效率更高一点,如果用string,最后还要将其转换为字节数据,而bytes则不需要。最后结果和上面相同

3、生成rpc方法的类型
在protobuf里面定义描述rpc方法的类型 – service

service UserServiceRpc
{
    rpc Login(LoginRequest) returns(LoginResponse);
}

三、编译生成的C++类

编译生成cpp和h文件
protoc test.proto --cpp_out=./

1、message具体生成的c++类如下
在这里插入图片描述

2、rpc方法的类型生成的类
test.proto代码

service UserServiceRpc
{
    rpc Login(LoginRequest) returns(LoginResponse);
}

生成类如下:
在这里插入图片描述

可以看出,一共生成两个类

UserServiceRpc

一个供callee–>rpc服务提供者使用。继承goole::protobuf::Service得到
class UserServiceRpc : public google::protobuf::Service

UserServiceRpc_Stub

一个供caller–>rpc服务的调用者使用。继承UserServiceRpc得到
class UserServiceRpc_Stub : public UserServiceRpc

成员函数很干净,一切的源头只需要一个RpcChannel类
RpcChannel类中只需要重写一个CallMethod方法,如下
在这里插入图片描述

四、序列化和反序列化

序列化:对象转为字节序列称为对象的序列化
反序列化:字节序列转为对象称为对象的反序列化

protobuf跨平台语言支持,序列化和反序列化效率高速度快,且序列化后体积比XML和JSON都小很多,适合网络传输。

注意:序列化和反序列化可能对系统的消耗较大,因此原则是:远程调用函数传入参数和返回值对象要尽量简单,具体来说应避免:

远程调用函数传入参数和返回值对象体积较大,如传入参数是List或Map,序列化后字节长度较长,对网络负担较大
远程调用函数传入参数和返回值对象有复杂关系,传入参数和返回值对象有复杂的嵌套、包含、聚合关系等,性能开销大
远程调用函数传入参数和返回值对象继承关系复杂,性能开销大

序列化

1、定义生成的头文件
#include "test.pb.h"

2、函数调用方打包数据

LoginRequest reqA;
req.set_name("zhang san");
req.set_pwd("123456");

3、将打包好的LoginRequest reqA;数据交给protobuf进行序列化

std::string send_str;
// 进行序列化,框架干的事情
if (req.SerializeToString(&send_str))
{
	// 序列化成功后 再发送
    std::cout << send_str.c_str() << std::endl;
}

反序列化

此时数据被发送到被调用方,被调用方反序列化刚刚发送过来的send_str

LoginRequest reqB;
// 从send_str反序列化一个login请求对象
if (reqB.ParseFromString(send_str))  
{
	// 以下代码不属于框架内的代码
    std::cout << reqB.name() << std::endl;
    std::cout << reqB.pwd() << std::endl;
}

需要注意,所有不涉及抽象层,设计具体的业务的代码,都不属于RPC分布式网络通信框架的代码。

粘包问题解决

TCP是字节流协议,因此需自己处理拆包粘包问题,即自定义数据传输格式。
方案:
rpc服务调用者和rpc服务提供者发送或解析函数的输入数据时,需要共同参照一个proto数据包格式RpcHeader,如下所示:
在这里插入图片描述

syntax = "proto3";
package mprpc;

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

rpc服务调用者和rpc服务提供者都需要遵循该格式组装数据或是解析数据。

调用者组包

首先调取服务名和方法名,之后序列化调用函数的输入,得到序列化后输入的长度。

将服务名,方法名,输入的长度按照预设定的protobuf message再次序列化得到rpc_header_str;
最后将rpc_header_str头部插入固定4字节的rpc_header_str.size(),尾部插入序列化后的调用函数输入,得到send_rpc_str。

const google::protobuf::ServiceDescriptor* sd = method->service();
std::string service_name = sd->name(); // service_name
std::string method_name = method->name(); // method_name

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

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

uint32_t header_size = 0;
std::string rpc_header_str;
if (rpcHeader.SerializeToString(&rpc_header_str))
{
    header_size = rpc_header_str.size();
}
else
{
    controller->SetFailed("serialize rpc header error!");
    return;
}

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

// 打印调试信息
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; 
std::cout << "============================================" << std::endl;

提供者解包

根据头的长度,得到rpc_header_str的长度,使用substr将rpc_header_str从网络数据包中宅出来,并将数据头反序列化。
之后根据输入的长度反序列化输入args_str,成功拿到输入。

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; 
std::cout << "============================================" << std::endl;
  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Protobuf-rpc是一种基于protobuf的远程方法调用(RPC)框架。它提供了服务器端和客户端的实现,其中服务器端仅支持Java,而客户端则支持Objective-C和Java。RPC是一种封装了网络协议和序列化、反序列化功能的通信框架,而protobuf-rpc使用protobuf实现了序列化和反序列化的功能。通过protobuf-rpc,客户端可以像调用本地方法一样调用远程接口方法,实现了透明调用机制,让使用者不必显示区分本地调用和远程调用。这使得开发人员可以很方便地在分布式系统中进行远程方法调用。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [protobuf-rpc:protobuf-rpc 是一个基于 Google ProtocolBuffer 的 RPC 框架。 目前 protobuf-rpc 支持 ...](https://download.csdn.net/download/weixin_42123296/19257793)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [protobuf+RPC技术](https://blog.csdn.net/weixin_27015375/article/details/114350163)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [python如何通过protobuf实现rpc](https://download.csdn.net/download/weixin_38599545/13771570)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秋雨qy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值