alin的学习之路:序列化与protobuf

1. 序列化(串行化)

序列化 (Serialization)将对象的状态信息转换为可以存储或传输的形式的过程,与之相对应的过程称之为反序列化(Unserialization)。序列化和反序列化主要用于解决在跨平台和跨语言的情况下, 模块之间的交互和调用,但其本质是为了解决数据传输问题

  • 序列化: 将数据(数据块(复合数据类型))变成字符串的过程
  • 数据块不能直接在网络环境中进行传输(字符串除外)
  • 反序列化: 将字符串还原为原始数据的过程
  • 序列化的应用场景:
  • 网络通信
    • 数据发送之前: 进行数据的序列化 -> 得到了字符串
    • 接收到数据之后: 进行数据的反序列化 -> 将字符串还原为原始数据
  • 数据的持久化存储: (持久化: 将数据从内存保存到磁盘的过程)
    • 在写文件的时候: 进行序列化
    • 文件内容读出之后: 进行反序列化
    • 这样做的目的是为了保存文件中数据的在网络传输/文件的的拷贝中不会出现解析错误

序列化的目的: 保证数据的跨平台传输

通俗来说:序列化就是将数据转换为字符串类型进行传递

1.1 为什么要用序列化

不使用序列化会发生如下的问题:

  1. 字节序的问题

    • 网络通信需要使用大端法,但是复合类型无法直接将整个类型进行转换,所以需要一个成员一个成员进行转换,十分繁琐
  2. 平台不同导致相同的数据类型所占内存大小不同
    • 32位

      • long :4字节
    • 64为
      • long :8字节
  3. 语言的差异导致相同的数据类型所占内存大小不同
    • java

      • char :2字节
    • C++
      • char :1字节
  4. 字节对齐
    • 字节的对齐是可以自行设置的,默认为4字节

1.2 常用的序列化方式

  1. Json( JavaScript Object Notation ) -> 是一种数据格式, 不是语言,轻量级的数据序列化的方式
  2. Protocol Buffer,谷歌
  3. boost 序列化的类,是C++的扩展库,不是标准C++

2. protobuf

使用protobuf3,要注意protobuf2和protobuf3不兼容

2.1 操作流程

  1. 准备数据: 非字符串类型的数据, 比如一个结构体, 或者一个类 --> 要被序列化的数据
  2. 创建一个新的文件, 文件名随意指定, 文件后缀为: .proto
  3. 根据protobuf的语法, 编辑 .proto 文件
    • 具体就指定要序列化的数据格式
  4. 通过可执行程序 protoc.exe 将编写好的 xxx.proto 文件进行转换 -> 得到两个c++的文件
    • 源文件: xxx.pb.cc --> xxx对应的名字和 .proto文件名相同
    • 头文件: xxx.pb.h --> xxx对应的名字和 .proto文件名相同
  5. 需要将生成的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相关推荐

  1. alin的学习之路:共享内存

    alin的学习之路:共享内存 1. 概念 共享内存是进程间通信中效率最高的一种方式. 共享内存: 可以被多个进程同时使用的一块内核的内存 有血缘关系的进程 没有血缘关系的进程 这块内存不属于任何的进程 ...

  2. alin的学习之路:面试题 计算机网络相关

    alin的学习之路:面试题 计算机网络相关 介绍下proactor和reactor reactor:同步IO proactor:异步IO Reactor框架中用户定义的操作是在实际操作之前调用的.比如 ...

  3. alin的学习之路:Qt与多线程

    alin的学习之路:Qt与多线程 如果程序在进行复杂的逻辑处理过程中, 对窗口进行操作, 就会出现无响应的情况. 如何解决这样的问题与高并发的问题? 需要使用多线程. 方式1 特点:简单 创建一个自定 ...

  4. alin的学习之路:面试题 数据库相关

    alin的学习之路:面试题 数据库相关 如何提高查询速度? 使用索引 create index 索引名 on 表名(列名1,列名2,--); 数据库索引,事务,事务级别 使用索引可以提高查询效率 事务 ...

  5. alin的学习之路(数据库篇:二)(select查询,where条件查询,order by排序,单行函数,多行函数,group by分组)

    alin的学习之路(数据库篇:二)(select查询,where条件查询,order by排序,单行函数,多行函数,group by分组) 1. SQL语句 1.1 sql语言类型 sql是一门独立的 ...

  6. alin的学习之路:加密相关知识(加密和解密,常见加密算法,消息验证码HMAC,数字签名)

    alin的学习之路:加密相关知识(加密和解密,常见加密算法,消息验证码HMAC,数字签名) 1. 加密和解密 1.1 加密的三要素 原始数据 加密操作: 明文 -> 密文 解密操作: 密文 -& ...

  7. alin的学习之路(Linux网络编程:十)(http协议,BS模型)

    alin的学习之路(Linux网络编程:十)(http协议,BS模型) 需求:使用B/S模型来访问主机中的文件(包括目录) 0. B/S 模型 注意事项 1. 浏览器请求ico ​ 准备一个favic ...

  8. alin的学习之路(数据库篇:三)(多表查询,子查询,集合运算,数据处理)

    alin的学习之路(数据库篇:三)(多表查询,子查询,集合运算,数据处理) 1. 多表查询 1.1 笛卡儿积 笛卡尔积就是两个集合的乘积计算 . 如果多个表进行联合查询, 得到结果是一个笛卡尔积, 举 ...

  9. alin的学习之路(Linux网络编程:一)(网络模型、帧格式、socket套接字、服务器端实现)

    alin的学习之路(Linux网络编程:一)(网络模型.帧格式.socket套接字.服务器端实现) 1. 协议 协议是一组规则,规定了如何发送数据.通信的双发都需要遵守该规则 2. 网络分层结构模型 ...

最新文章

  1. LeetCode实战:快乐数
  2. vue点击定位到指定位置_百度地图vue-baidu-map自动定位,鼠标选点并进行逆解析,地区检索,使用案列以及解决方案...
  3. 生物版AlphaGo发威!DeepMind出手抗疫:预测多种新冠病毒相关蛋白结构
  4. 【Python】快速设置 pip 源
  5. This will have no impact if delete.topic.enable is not set to true以及删除kafka中的topic
  6. python获取pid并杀死_用python记录运行pid,并在需要时kill掉它们的实例
  7. vector与list的接口介绍与如何使用以及区别,附代码。
  8. python能编游戏吗_python能做游戏吗
  9. 一:Java+SpringBoot框架框架的安装和启用
  10. 多线程编程:线程死锁的原因以及解决方法
  11. uber大数据_Uber创建了深度神经网络以为其他深度神经网络生成训练数据
  12. 离均差oracle 函数,Oracle的学习详解(一)
  13. C++定时器和时间轮
  14. 人工智能就是计算机科学的英文,AI(人工智能)的英文全称?AI指什么,包含什么?
  15. aspose-cells-8.52问题记录,excel转pdf,字体,格式
  16. 计算机专业的入门书籍(第一篇博客)
  17. python 获取MP4视频第一帧 | Python工具类
  18. 给视频智能配音怎么弄?一步一步让你学会配音操作
  19. 十六、Swift 可选值链条 Optional Chaining
  20. IOS个人账户转公司账户,TPshop APP提交审核

热门文章

  1. Netgear R6220桥接组网设置
  2. 最美的七律却不讲规则
  3. 中冠百年|投资理财,千万不要犯这些错误
  4. led matlab仿真,大功率LED照明电源研究及基于Matlab的仿真
  5. nodejs+puppeteer+chromium爬取异步数据页面(英雄联盟英雄资料列表页+详情页)
  6. 小米手机防盗nbsp;方法、安全及可…
  7. NFC OMA 访问
  8. 4类程序员直呼好用的嵌入式开发辅助工具
  9. 不要问程序员什么是“对象”,也不要给他介绍“对象”
  10. 你想不到,韩国女团最喜欢的英文单词是这些