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

news2024/11/27 14:33:56

文章目录

  • 一、protobuf的好处
  • 二、如何创建proto
  • 三、编译生成的C++类
  • UserServiceRpc
  • UserServiceRpc_Stub
  • 四、序列化和反序列化
    • 序列化
    • 反序列化
  • 粘包问题解决
    • 调用者组包
    • 提供者解包


一、protobuf的好处

1、protobuf是二进制存储的,xml和json都是文本存储的。故protobuf占用带宽较低
2、protobuf不需要存储额外的信息。
json如何存储数据?键值对。例:Name:”zhang san”, pwd: “12345”。
protobuf存储数据的方式:“zhang san” “123456”(无额外信息)

二、如何创建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分布式网络通信框架的代码。

粘包问题解决

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;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/742305.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

综合布线系统(PDS)

综合布线系统&#xff08;PDS&#xff09; 综合布线系统的基本标准 ● TIA/EIA-568A/B&#xff1a;商业大楼电信布线标准 ● EIA/TIA-569:电信通道和空间的商业大楼标准 ● EIA/TIA-570&#xff1a;住宅和N型商业电信布线标准 ● TIA/EIA-606&#xff1a;商业大楼电信基础设施…

windows便签推荐哪款?

随着科技技术的进步&#xff0c;越来越多的人喜欢使用便签软件&#xff0c;因为它们能帮助我们快速记录和管理重要的信息和任务。在快节奏的现代生活中&#xff0c;便签软件成为了我们生活和工作中不可或缺的工具。特别是对于经常使用电脑的用户来说&#xff0c;一款优秀的便签…

戴尔外星人x16r1原装Win11系统带F12还原功能

戴尔外星人x16r1原装Win11系统带F12还原功能 电脑恢复到新机状态&#xff0c;完成&#xff1a; 1.系统恢复到预装系统&#xff0c;与新机买来状态完全一致&#xff1b; 2.隐藏恢复分区&#xff0c;戴尔与外星人相同&#xff0c;可以用来开机F12进入supportassis os recovery…

选择排序

选择排序 排序步骤&#xff08;有n个数需要排序&#xff09; 在一组序列中找到最大/小的元素&#xff0c;将其与序列的起始位置交换&#xff1b;此时可进一步缩小排序范围&#xff0c;将改序列的起始位置移出&#xff1b;寻找剩余范围序列中的最大/小值&#xff0c;与此时序列…

deeplabv3+源码之慢慢解析 第二章datasets文件夹(1)voc.py--voc_cmap函数和download_extract函数

系列文章目录&#xff08;更新中&#xff09; 第一章deeplabv3源码之慢慢解析 根目录(1)main.py–get_argparser函数 第一章deeplabv3源码之慢慢解析 根目录(2)main.py–get_dataset函数 第一章deeplabv3源码之慢慢解析 根目录(3)main.py–validate函数 第一章deeplabv3源码之…

JS UMD规范实现

UMD实现范例 (function (root, factory) {if (typeof module object && typeof module.exports object) {console.log(是commonjs模块规范&#xff0c;nodejs环境);var depModule require(./umd-module-depended);module.exports factory(depModule);} else if (t…

ElasticSearch入门教程--集群搭建和版本比较

文章目录 一、ElasticSearch 集群二、Elasticsearch的核心概念2.1、分片&#xff08;Shards&#xff09;2.2、副本&#xff08;Replicas&#xff09;2.3、路由计算2.4、倒排索引 三、Kibana简介四、Spring Data ElasticSearch 一、ElasticSearch 集群 Elasticsearch 集群有一个…

python pytorch 纯算法实现前馈神经网络训练(数据集随机生成)-续

python pytorch 纯算法实现前馈神经网络训练&#xff08;数据集随机生成&#xff09;-续 上一次的代码博主看了&#xff0c;有两个小问题其实&#xff0c;一个是&#xff0c;SGD优化的时候&#xff0c;那个梯度应该初始化为0&#xff0c;还一个是我并没有用到随机生成batch。 …

Flowable边界事件-定时边界事件

定时边界事件 定时边界事件一、定义1. 图形标记2. 完整的流程图3. XML标记 二、测试用例2.1 定时边界事件xml文件2.2 定时边界事件测试用例 总结 定时边界事件 一、定义 时间达到设定的时间之后触发事件 由于定时边界事件和开始定时事件几乎差不多&#xff0c;四种情况我就不一…

14、双亲委托模型

双亲委托模型 先直接来看一幅图 双亲委派模型的好处&#xff1a; 主要是为了安全性&#xff0c;避免用户自己编写的类动态替换Java的一些核心类&#xff0c;比如 String。 同时也避免了类的重复加载&#xff0c;因为JVM中区分不同类&#xff0c;不仅仅是根据类名&#xff0c…

React 新版官方文档 (二) useState 用法详解

背景 本文默认读者对 useState 有最为基本的了解&#xff0c;比如知道他的写法应当是怎样的&#xff0c;下面着重介绍部分重要的、在开发过程中会踩的坑和一些特性&#xff0c;最后动手实现一个最基本的 useState 代码 useState ⭐️ 注意事项: 状态只在下次更新时异步变化&…

Shiro教程(一):入门概述与基本使用

Shiro 第一章&#xff1a;入门概述 1.1 Shiro是什么 Apache.Shiro是一个功能强大且易于使用的Java安全&#xff08;权限&#xff09;框架。Shiro可以完成&#xff1a;认证、授权、加密、会话管理、与Web集成、缓存等。借助Shiro可以快速轻松地保护任何应用程序——从最小的移…

用于3D渲染和平面设计应该怎么选择显卡?

首先了解快速解决3D渲染本地配置不足&#xff0c;节省硬件成本的方法&#xff1a; 本地电脑资源不足&#xff0c;在不增加额外的硬件成本投入的情况下&#xff0c;想要快速提升渲染速度&#xff0c;可使用渲云云渲染&#xff0c;且可批量渲染&#xff0c;批量出结果&#xff0…

centos7密码忘记恢复方法

首先启动系统看到如下界面&#xff1a; 然后按"e"键&#xff0c;看到下面的界面 然后使用"↓"按键移动光标&#xff0c; 移动到linux16 将上图中红色箭头指向的ro替换成下图中画红线的内容&#xff1a; ro替换成rw init/sysroot/bin/sh。 然后按CTRLX进入…

【ABAP】数据类型(五)「结构体概要」

💂作者简介: THUNDER王,一名热爱财税和SAP ABAP编程以及热爱分享的博主。目前于江西师范大学本科在读,同时任汉硕云(广东)科技有限公司ABAP开发顾问。在学习工作中,我通常使用偏后端的开发语言ABAP,SQL进行任务的完成,对SAP企业管理系统,SAP ABAP开发和数据库具有较…

Spring Cloud | No URLs will be polled as dynamic configuration sources.

添加config.properties文件就行了&#xff0c;内容为空的都可以 加上该文件再次运行

基于STM32设计的简易手机

一、项目介绍 基于STM32设计的简易手机可以作为智能手表的模型进行开发&#xff0c;方便老人和儿童佩戴。项目主要是为了解决老年人或儿童使用智能手表时可能遇到的困难&#xff0c;例如操作困难、功能复杂等问题。 在这个项目中&#xff0c;采用了STM32F103RCT6主控芯片和SI…

Effective Java(第三版)目录

本书的目标是帮助读者更加有效地使用Java编程语言及其基本类库java.lang、java.util和java.io&#xff0c;以及子包java.util.concurrent和java.util.function等。本书也会时不时地讨论到其他的类库。 本书一共包含90个条目&#xff0c;每个条目讨论一条规则。这些规则…

驱动 day8 作业

1.在内核模块中启用定时器&#xff0c;定时1s,让led1 一秒亮、一秒灭 2.基于gpio子系统完成LED灯驱动的注册&#xff0c;应用程序测试 1.mychrdev_timer.c #include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/io…