alin的学习之路:序列化与protobuf
alin的学习之路:序列化与protobuf
1. 序列化(串行化)
序列化 (Serialization)将对象的状态信息转换为可以存储或传输的形式的过程,与之相对应的过程称之为反序列化(Unserialization)。序列化和反序列化主要用于解决在跨平台和跨语言的情况下, 模块之间的交互和调用,但其本质是为了解决数据传输问题。
- 序列化: 将数据(
数据块(复合数据类型)
)变成字符串的过程- 数据块不能直接在网络环境中进行传输(
字符串除外
)- 反序列化:
将字符串还原为原始数据的过程
- 序列化的应用场景:
- 网络通信
- 数据发送之前: 进行数据的序列化 -> 得到了字符串
- 接收到数据之后: 进行数据的反序列化 -> 将字符串还原为原始数据
- 数据的持久化存储: (
持久化: 将数据从内存保存到磁盘的过程
)
- 在写文件的时候: 进行序列化
- 文件内容读出之后: 进行反序列化
这样做的目的是为了保存文件中数据的在网络传输/文件的的拷贝中不会出现解析错误
序列化的目的: 保证数据的跨平台传输
通俗来说:序列化就是将数据转换为字符串类型进行传递
1.1 为什么要用序列化
不使用序列化会发生如下的问题:
- 字节序的问题
- 网络通信需要使用大端法,但是复合类型无法直接将整个类型进行转换,所以需要一个成员一个成员进行转换,十分繁琐
- 平台不同导致相同的数据类型所占内存大小不同
- 32位
- long :4字节
- 64为
- long :8字节
- 32位
- 语言的差异导致相同的数据类型所占内存大小不同
- java
- char :2字节
- C++
- char :1字节
- java
- 字节对齐
- 字节的对齐是可以自行设置的,默认为4字节
1.2 常用的序列化方式
- Json( JavaScript Object Notation ) -> 是一种数据格式, 不是语言,轻量级的数据序列化的方式
- Protocol Buffer,谷歌
- boost 序列化的类,是C++的扩展库,不是标准C++
2. protobuf
使用protobuf3,要注意protobuf2和protobuf3不兼容
2.1 操作流程
- 准备数据: 非字符串类型的数据, 比如一个结构体, 或者一个类 --> 要被序列化的数据
- 创建一个新的文件, 文件名随意指定, 文件后缀为: .proto
- 根据protobuf的语法, 编辑 .proto 文件
- 具体就指定要序列化的数据格式
- 通过可执行程序 protoc.exe 将编写好的 xxx.proto 文件进行转换 -> 得到两个c++的文件
- 源文件: xxx.pb.cc --> xxx对应的名字和 .proto文件名相同
- 头文件: xxx.pb.h --> xxx对应的名字和 .proto文件名相同
- 需要将生成的c++文件添加到项目中, 通过文件中提供的类实现数据的序列化/反序列化
2.2 .proto文件及其使用
// 要序列化的数据
// 第一种:
int number;// 第二种:
struct Person
{int id;string name;string sex; // man womanint age;
};
protobuf中的数据类型 和 C++ 数据类型对照表:
.proto类型 | **C++**类型 | 备注 |
---|---|---|
double | double | 64位浮点数 |
float | float | 32位浮点数 |
int32 | int (32位) | 32位整数 |
int64 | long (64位) | 64位整数 |
uint32 | unsigned int(32位) | 32位无符号整数 |
uint64 | unsigned long(64位) | 64位无符号整数 |
sint32 | signed int(32位) | 32位整数,处理负数效率比int32更高 |
sint64 | signed long(64位) | 64位整数,处理负数效率比int64更高 |
fixed32 | unsigned int(32位) | 总是4个字节。如果数值总是比总是比228大的话,这个类型会比uint32高效。 |
fixed64 | unsigned long(64位) | 总是8个字节。如果数值总是比总是比256大的话,这个类型会比uint64高效。 |
sfixed32 | int (32位) | 总是4个字节 |
sfixed64 | long (64位) | 总是8个字节 |
bool | bool | 布尔类型 |
string | string | 一个字符串必须是UTF-8编码或者7-bit ASCII编码的文本 |
bytes
|
string |
处理多字节的语言字符、如中文, 建议protobuf中字符型类型使用 bytes
|
enum | enum | 枚举 |
message | object of class | 自定义的消息类型 |
- proto文件的语法
//第一行指定protobuf的版本,注意这一行必须和下面写的一模一样,包括空格,否则会有警告
syntax = "proto3";
//定义要序列化的数据格式
//message 对应 struct 或者 class
message 名字 //这个名字就是类名
{数据类型 成员名 = 编号; //编号要从1开始,每个成员都有唯一的编号,不能重复,递增即可
}
// message末尾可以不写结束的分号,写了也不报错
举个栗子:
syntax = "proto3";//第一种,复合数据类型
message Person
{int32 id = 1;bytes name = 2;bytes sex = 3;int32 age = 4;
}//第二种,单独一个变量
//int number; 这样一个单独的数据块,要进行序列化也必须放入一个类中
message Data
{int32 number = 1;
}
- 将 .proto文件编译转换为C++文件、
# 为了更方便的进行 protoc.exe 的全局访问, 需要将其所在目录设置到环境变量中
# 如果环境变量设置好了, 使用 protoc.exe 可以直接写全名也可以写成 protoc
# 意味着: protoc == protoc.exe
# 语法格式: [在命令行窗口中执行这个命令]
# .proto文件的名字需要携带路径, 保证可以找到这个文件(绝对路径/相对路径)
$ protoc proto文件的名字 --cpp_out=输出C++文件的路径# 举个栗子
$ protoc ./person.proto --cpp_out=.
粗略的阅读生成的头文件
**.pb.h
- 通过
protoc.exe
的转换得到的 头文件中有一个类, 这个类的名字和.proto
文件中message关键字后边指定的名字相同
, .proto文件中的成员就是生成的类的私有成员 - 如果访问生成的类的私有成员呢? —> 调用提供的公共成员函数 --> 这个函数的规律:
- 清空(初始化) 私有成员的值:
clear_变量名()
- 获取类私有成员的值(
使用同名函数
):变量名()
- 给私有成员进行值的设置:
set_变量名(参数)
- 得到类私有成员的
地址
, 通过这块地址读/写当前私有成员变量的值
:mutable_变量名()
- 如果这个变量是数组类型:
- 数组中元素的个数:
变量名_size()
- 添加一块内存, 存储新的元素数据:
add_变量名()
- 数组中元素的个数:
- 清空(初始化) 私有成员的值:
- 通过
probufbuf 提供的常用的序列化/反序列化的 API,需要加载动态库
libprotobuf.dll
:以下函数都是被protobuf中的某一个对象调用的,
- 序列化:
序列化的数据来自于这个对象, 序列化完成之后, 保存到一块内存中, 这块内存不属于protoubf对象
- 反序列化:
将外部数据还原, 还原到这个protobuf对象中存储起来, 访问protoubf对象就可以得到原始数据
- 序列化:
序列化
// 头文件目录: google\protobuf\message_lite.h // --- 将序列化的数据 数据保存到内存中 // 将类对象中的数据序列化为字符串, c++ 风格的字符串, 参数是一个传出参数 bool SerializeToString(std::string* output) const; // 将类对象中的数据序列化为字符串, c 风格的字符串, 参数 data 是一个传出参数 bool SerializeToArray(void* data, int size) const;// ------ 写磁盘文件, 只需要调用这个函数, 数据自动被写入到磁盘文件中 // -- 需要提供流对象/文件描述符关联一个磁盘文件 // 将数据序列化写入到磁盘文件中, c++ 风格 // ostream 子类 ofstream -> 写文件 bool SerializeToOstream(std::ostream* output) const; // 将数据序列化写入到磁盘文件中, c 风格 bool SerializeToFileDescriptor(int file_descriptor) const;
反序列化的api
// 头文件目录: google\protobuf\message_lite.h bool ParseFromString(const std::string& data) ; bool ParseFromArray(const void* data, int size); // istream -> 子类 ifstream -> 读操作 // wo ri // w->写 o: ofstream , r->读 i: ifstream bool ParseFromIstream(std::istream* input); bool ParseFromFileDescriptor(int file_descriptor);
- 注意如果使用Qt进行开发时,需要在.pro文件中指定好protobuf的头文件的路径和动态库的路径
INCLUDEPATH += C:\protobuf\include
LIBS += -LC:\protobuf\lib -lprotobuf
2.3 repeated 限定修饰符
// 要序列化的数据
struct Person
{int id;string name[10];string sex; int age;
};
// 对要序列化的数据进行转换
// person.proto
// 在该文件中对要序列化的结构体进行描述
message Person
{int32 id = 1;repeated bytes name = 2; // 这个name可以作为一个动态数组来使用bytes sex = 3; int32 age = 4;
}
- 每次给这个数组设置值之前都需要提前开辟一块空间,使用
add_变量名
,然后再set_变量名
,反序列化后调用其中的数据也需要加上参数。总之:注意参数
2.4 枚举
在写c++程序的时候, 枚举的使用频率是很高的, 在c++中一般建议使用枚举将宏替换掉
// 要序列化的数据// 枚举
enum Color
{Red = 5, // 可以不给初始值, 默认为0Green,Yellow,Blue
};// 要序列化的数据
struct Person
{int id;string name[10];string sex; int age;// 枚举类型Color color;
};
// 在protobuf的proto文件中处理枚举类型
// 语法格式: 元素之间使用 分号间隔 ;
enum 名字
{元素名 = 0; // 枚举中第一个原素的值必须为0元素名 = x;
}如何修改 /
// 定义枚举类型
enum Color
{Red = 0; // 第一个元素的枚举值必须为0,并且必须指定出来Green = 3; // 其余的元素也要显示的指定出值Yellow = 6;Blue = 9;
}
// 在该文件中对要序列化的结构体进行描述
message Person
{int32 id = 1;repeated bytes name = 2;bytes sex = 3; int32 age = 4;// 枚举类型Color color = 5;
}
2.5 proto文件的导入
假设说 有多个 .proto文件, 每个proto文件中对应一个
message
也就是对应一个类的定义, 如果这几个类直接需要相互引用, 比如:在类A中包含一个B类型的成员
, 这时候需要在一个 proto文件引入另外一个proto文件
proto文件 - address.proto
syntax = "proto3"; // 地址信息 message Address {bytes addr = 1;bytes number = 2; }
proto文件 - person.proto
// 现在的需求: 需要在当前的proto文件中使用一个proto文件中定义的数据 // 需要进行proto文件的包含 // 语法: import "要使用的proto文件的名字";/// 使用 /// syntax = "proto3"; // 使用另外一个proto文件中的数类型, 需要导入这个文件 import "address.proto";// 在该文件中对要序列化的结构体进行描述 // 定义枚举类型 enum Color {Red = 0;Green = 3; // 第一个元素以外的元素值可以随意指定Yellow = 6;Blue = 9; } // 在该文件中对要序列化的结构体进行描述 message Person {int32 id = 1;repeated bytes name = 2;bytes sex = 3; int32 age = 4;// 枚举类型Color color = 5;// 添加地址信息, 使用的是外部proto文件中定义的数据类型Address addr = 6; }
- 注意在代码中进行使用的时候,要先得到得到类私有成员的
地址
, 通过这块地址读/写当前私有成员变量的值
:mutable_变量名()
mutable_变量名()
返回的是一个对应对象的指针,给其中的元素设置值等操作是要使用这个指针来调用函数来操作。
2.6 包
这个包在c++中对应的是
命名空间
, 作用是用来隔离数据
- 比如做项目开发, A编写了一类:
HelloWorld
, B也写了一个类:HelloWorld
, 但是这两个人写的类虽然同名, 但是功能不一样, 直接使用是冲突的, 可以将这两个类放到不同的命名空间中就可以了
// 如果要给生成的c++类指定命名空间, 语法格式:
package 命名空间的名字;
proto文件 - Address.proto
syntax = "proto3"; // 添加命名空间 itcast package itcast;// 地址信息, 这个Address类属于命名空间: itcast message Address {bytes addr = 1;bytes number = 2; }
proto文件 - person.proto
syntax = "proto3"; // 使用另外一个proto文件中的数类型, 需要导入这个文件 import "address.proto"; // 指定命名空间 itheima package itheima;// 一下的类 Person 和枚举 Color 都属于命名空间 itheima // 在该文件中对要序列化的结构体进行描述 // 定义枚举类型 enum Color {Red = 0;Green = 3; // 第一个元素以外的元素值可以随意指定Yellow = 6;Blue = 9; } // 在该文件中对要序列化的结构体进行描述 message Person {int32 id = 1;repeated bytes name = 2;bytes sex = 3; int32 age = 4;// 枚举类型Color color = 5;// 添加地址信息, 使用的是外部proto文件中定义的数据类型// 如果这个外边类型属于某个命名空间, 语法格式:// 命名空间的名字.类名 变量名=编号;itcast.Address addr = 6; }
- 添加完命名空间后,在程序中调用对象需要加上
命名空间::
或者是加上using namespace 命名空间;
注意:在.proto文件中调用其他命名空间中的对象时要写上 命名空间.对象名
2.7 实例代码
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "person.pb.h"
#include "address.pb.h"
#include <qdebug.h>
using namespace std;#if 0
using namespace itcast;
using namespace itheima;
#endifMainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow)
{ui->setupUi(this);//创建对象itcast::Person p;//创建Address的指针itheima::Address *addr = p.mutable_addr();addr->set_addr("天津市东丽区");addr->set_number("10-10-501");//设置值p.set_id(10);//给name数组设置值p.add_name();p.set_name(0, "路飞");p.add_name();p.set_name(1, "乔巴");p.add_name();p.set_name(2, "索隆");p.set_sex("男");p.set_age(18);//设置枚举的值p.set_color(itcast::Color::Green);//序列化string output;p.SerializeToString(&output);//反序列化itcast::Person pp;pp.ParseFromString(output);//得到Address的对象itheima::Address addr1 = pp.addr();qDebug() << "id:" << pp.id()<< "name1:" << pp.name(0).data()<< "name1:" << pp.name(1).data()<< "name1:" << pp.name(2).data()<< "sex:" << pp.sex().data()<< "age:" << pp.age()<< "color:" << pp.color() << endl<< "addr addr:" << addr1.addr().data()<< "addr number:" << addr1.number().data();}MainWindow::~MainWindow()
{delete ui;
}
alin的学习之路:序列化与protobuf相关推荐
- alin的学习之路:共享内存
alin的学习之路:共享内存 1. 概念 共享内存是进程间通信中效率最高的一种方式. 共享内存: 可以被多个进程同时使用的一块内核的内存 有血缘关系的进程 没有血缘关系的进程 这块内存不属于任何的进程 ...
- alin的学习之路:面试题 计算机网络相关
alin的学习之路:面试题 计算机网络相关 介绍下proactor和reactor reactor:同步IO proactor:异步IO Reactor框架中用户定义的操作是在实际操作之前调用的.比如 ...
- alin的学习之路:Qt与多线程
alin的学习之路:Qt与多线程 如果程序在进行复杂的逻辑处理过程中, 对窗口进行操作, 就会出现无响应的情况. 如何解决这样的问题与高并发的问题? 需要使用多线程. 方式1 特点:简单 创建一个自定 ...
- alin的学习之路:面试题 数据库相关
alin的学习之路:面试题 数据库相关 如何提高查询速度? 使用索引 create index 索引名 on 表名(列名1,列名2,--); 数据库索引,事务,事务级别 使用索引可以提高查询效率 事务 ...
- alin的学习之路(数据库篇:二)(select查询,where条件查询,order by排序,单行函数,多行函数,group by分组)
alin的学习之路(数据库篇:二)(select查询,where条件查询,order by排序,单行函数,多行函数,group by分组) 1. SQL语句 1.1 sql语言类型 sql是一门独立的 ...
- alin的学习之路:加密相关知识(加密和解密,常见加密算法,消息验证码HMAC,数字签名)
alin的学习之路:加密相关知识(加密和解密,常见加密算法,消息验证码HMAC,数字签名) 1. 加密和解密 1.1 加密的三要素 原始数据 加密操作: 明文 -> 密文 解密操作: 密文 -& ...
- alin的学习之路(Linux网络编程:十)(http协议,BS模型)
alin的学习之路(Linux网络编程:十)(http协议,BS模型) 需求:使用B/S模型来访问主机中的文件(包括目录) 0. B/S 模型 注意事项 1. 浏览器请求ico 准备一个favic ...
- alin的学习之路(数据库篇:三)(多表查询,子查询,集合运算,数据处理)
alin的学习之路(数据库篇:三)(多表查询,子查询,集合运算,数据处理) 1. 多表查询 1.1 笛卡儿积 笛卡尔积就是两个集合的乘积计算 . 如果多个表进行联合查询, 得到结果是一个笛卡尔积, 举 ...
- alin的学习之路(Linux网络编程:一)(网络模型、帧格式、socket套接字、服务器端实现)
alin的学习之路(Linux网络编程:一)(网络模型.帧格式.socket套接字.服务器端实现) 1. 协议 协议是一组规则,规定了如何发送数据.通信的双发都需要遵守该规则 2. 网络分层结构模型 ...
最新文章
- LeetCode实战:快乐数
- vue点击定位到指定位置_百度地图vue-baidu-map自动定位,鼠标选点并进行逆解析,地区检索,使用案列以及解决方案...
- 生物版AlphaGo发威!DeepMind出手抗疫:预测多种新冠病毒相关蛋白结构
- 【Python】快速设置 pip 源
- This will have no impact if delete.topic.enable is not set to true以及删除kafka中的topic
- python获取pid并杀死_用python记录运行pid,并在需要时kill掉它们的实例
- vector与list的接口介绍与如何使用以及区别,附代码。
- python能编游戏吗_python能做游戏吗
- 一:Java+SpringBoot框架框架的安装和启用
- 多线程编程:线程死锁的原因以及解决方法
- uber大数据_Uber创建了深度神经网络以为其他深度神经网络生成训练数据
- 离均差oracle 函数,Oracle的学习详解(一)
- C++定时器和时间轮
- 人工智能就是计算机科学的英文,AI(人工智能)的英文全称?AI指什么,包含什么?
- aspose-cells-8.52问题记录,excel转pdf,字体,格式
- 计算机专业的入门书籍(第一篇博客)
- python 获取MP4视频第一帧 | Python工具类
- 给视频智能配音怎么弄?一步一步让你学会配音操作
- 十六、Swift 可选值链条 Optional Chaining
- IOS个人账户转公司账户,TPshop APP提交审核
热门文章
- Netgear R6220桥接组网设置
- 最美的七律却不讲规则
- 中冠百年|投资理财,千万不要犯这些错误
- led matlab仿真,大功率LED照明电源研究及基于Matlab的仿真
- nodejs+puppeteer+chromium爬取异步数据页面(英雄联盟英雄资料列表页+详情页)
- 小米手机防盗nbsp;方法、安全及可…
- NFC OMA 访问
- 4类程序员直呼好用的嵌入式开发辅助工具
- 不要问程序员什么是“对象”,也不要给他介绍“对象”
- 你想不到,韩国女团最喜欢的英文单词是这些