protobuf序列化
protobuf
protobuf是一种开源跨平台的序列化数据结构的协议。其对于存储资料或在网络上进行通信的程序是很有用的
处理PROTOBUF_NAMESPACE_OPEN错误
#define PROTOBUF_NAMESPACE_OPEN \
namespace google \
{ \
namespace protobuf \
{
#define PROTOBUF_NAMESPACE_CLOSE \
} \
}
#define PROTOBUF_NAMESPACE_ID google::protobuf
#define PROTOBUF_CONSTEXPR
#define PROTOBUF_ATTRIBUTE_REINITIALIZES
#define PROTOBUF_NODISCARD [[nodiscard]]
#define PROTOBUF_ALWAYS_INLINE
using namespace google;
处理无法编译
序列化
序列化 (Serialization)将对象的状态信息转换为可以存储或传输的形式的过程,与之相对应的过程称之为反序列化(Unserialization)。序列化和反序列化主要用于解决在跨平台和跨语言的情况下, 模块之间的交互和调用,但其本质是为了解决数据传输问题。
实现数据序列化:
- 要有原始数据
复合类型
-> 最常见的情况- 基础数据类型
- 通过某些方式 -> 另外一种形式的数据
- 得到的数据干啥? ->
目的: 进行分发, 分发到不同的终端/平台, 保证不同的平台能正确解析
网络传输
—>内存到内存磁盘拷贝
—>u盘拷贝—>内存到磁盘再到内存
序列化目的不是为了加密, 为的是数据的跨平台传输
序列化的整体过程:- 发送端
- 原始数据 -> 序列化 (编码) -> 特殊格式的字符串
- 发送这个字符串
- 接收端:
- 接收数据
- 特殊格式的字符串 -> 反序列化 (解码) -> 原始数据
- 对原始数据进行处理
网络通信中的问题分析
- 发送过程中遇到的一些问题?
- 平台不同
- 32bit / 64bit
- long
- 平台不同, 某些数据类型占用的内存大小不同
- 32bit / 64bit
- 如果不是字符串, 需要进行字节序转换
- 字符串没有字节序问题, 字符在内存中只占一个字节
- 如果发送的是结构体
struct Test { int number; char buf[12]; long sex; }; Test t; send()/write() send(fd, (void*)t, sizeof(t), 0);
- 大小端问题
- 语言不同
- 语言不同数据类型占用的内存有可能不同
- c -> char -> 1字节
- java -> char -> 2字节
- 字节对齐问题
常用的序列化方式
-
XML( Extensible Markup Language )类似于html
XML是一种常用的序列化和反序列化协议,具有跨机器,跨语言等优点。XML历史悠久,其1.0版本早在1998年就形成标准,并被广泛使用至今。
XML的最初产生目标是对互联网文档进行标记,所以它的设计理念中就包含了对于人和机器都具备可读性。 但是,当这种标记文档的设计被用来序列化对象的时候,就显得冗长而复杂。
XML基本格式:
<?xml version="1.0" encoding="utf-8"?> <Library> <Type name="小说"> <Book author="J.K.ROWLING" price="12$">哈利波特1</Book> <Book author="J.K.ROWLING" price="12$">哈利波特2</Book> <Book author="J.K.ROWLING" price="12$">哈利波特3</Book> <Book author="J.K.ROWLING" price="12$">哈利波特4</Book> </Type> <Type name="历史"> <Book author="司马迁" price="20$">史记</Book> </Type> </Library>
-
Json( JavaScript Object Notation )
JSON起源于弱类型语言Javascript,它的产生来自于一种称之为"关联数组(Associative array)"的概念,其本质是就是采用"键值对"的方式来描述对象。
JSON格式保持了XML的人眼可读的优点,非常符合工程师对对象的理解。
相对于XML而言,序列化后的数据更加简洁(XML所产生序列化之后文件的大小接近JSON的两倍),而且其协议比较简单,解析速度比较快。
JSON格式具备Javascript的先天性支持,所以被广泛应用于Web browser的应用常景中,是Ajax的事实标准协议。
更多资料可查看:http://json.org/
// json是一种数据格式, 不是语言, 和平台语言无关 // json数组 [整形, 浮点型, 布尔类型, 字符串, json数组, json对象] [12, 12.44, true, "hello", [1,2,3]] // json对象 { "key":"value" } json对象中是n个键值对 key: 必须是字符串 value: 整形 浮点型 布尔 字符串 json数组 json对象 注意事项: 在一个文件中只能存储一个大的数组或者对象, 但是可以嵌套使用 原素和原始之间使用逗号间隔(一个键值对视为一个元素) 最后一个元素后边没有逗号 { "lilei":"112334", "tom":"helolll", "lucy":"xxxxyyyy" } ["张三", "历史"] { "张三":{ "father":"张三丰", "mother":"xxxx", "sister""xxx", "favorite":["足球", "乒乓", "游泳"] } "李四":{ } }
-
Protocol Buffer
-
ASN.1 抽象语法标记(Abstract Syntax Notation One)
-
boost 序列化的类
编写proto文件并且生成对应的类
- 操作流程:
- 准备数据
- 复合类型: 结构体/ 类
- 基础类型
- 创建一个新文件
xxx.proto
- 将我们要序列化的数据 -> 写入到proto文件
- 有语法格式
- 通过一个命令
protoc
将xxx.proto
文件生成一个c++的类
- 对应一个头文件/ 源文件
- 操作命令-> 在window终端中:
protoc xxx.proto --cpp_out=./
- 直接使用这个类
- 里边有对数据操作的api
- 读数据 api
- 方法名字
变量名()
- 写数据 api
- 方法名字:
set_变量名(arg)
debug和release介绍
debug是在开发过程中使用的
release是在发行的时候使用的
在release模式下打断点不停
protobuf解析基本数据类型案例
#include <iostream>
#include "Persion.pb.h"
using namespace std;
/*
message Person
{
int64 id = 1;
bytes name = 2;
string sex = 3;
int64 age = 4;
}
*/
int main()
{
//1.创建person对象,并且初始化
Person p;
p.set_id(1001);
p.set_name("路飞");
p.set_sex("man");
p.set_age(18);
//2.将person对象序列化 -- 字符串
string output;
p.SerializeToString(&output);//返回值是bool 因此参数是传出参数
cout << "序列化后的数据是:" << output << endl;
//3.数据传输
//一台电脑传入另外一台电脑 接收数据拿到的还是output
//4.接收数据,解析(output) --解码
//4.1创建对象
Person p1;
//4.2解析
p1.ParseFromString(output);
//5.处理原始数据 -- 打印数据信息
cout << "id = " << p1.id() << endl;
cout << "name = " << p1.name() << endl;
cout << "sex = " << p1.sex() << endl;
cout << "age = " << p1.age() << endl;
system("pause");
return 0;
}
protobuf解析数组案例
// 要求name有多个 -> 数组
syntax = "proto3";
message Persion
{
int32 id = 1; // 编号从1开始
// vector<string> name;
repeated bytes name = 2; // name可以在程序中创建多个, 在程序中作为动态数组来使用
string sex = 3;
int32 age = 4;
}
生成的头文件:
void set_name(int index, const std::string& value);
void set_name(int index, std::string&& value);
void set_name(int index, const char* value);
void set_name(int index, const void* value, size_t size);
std::string* add_name();
void add_name(const std::string& value);
void add_name(std::string&& value);
void add_name(const char* value);
void add_name(const void* value, size_t size);
在main函数里:
当调用 add_name() 时,虽然得到了一个指向新添加字符串的指针,但指针本身并不会 “往后移动”。其实,add_name() 每次调用后会返回一个指向新添加元素的指针,而不是修改原有指针的地址。每次调用 add_name() 时,都会向内部的 std::vectorstd::string(或类似的容器)添加一个新元素,然后返回该元素的地址。
#include <iostream>
#include "Persion.pb.h"
using namespace std;
/*
message Person
{
int64 id = 1;
repeated bytes name = 2;
string sex = 3;
int64 age = 4;
}
*/
int main()
{
//1.创建person对象,并且初始化
Person p;
p.set_id(1001);
p.add_name();//可以理解为创造一块新的内存
p.set_name(0, "路飞");
p.add_name();
p.set_name(1, "艾斯");
p.add_name();
p.set_name(2, "萨博");
p.set_sex("man");
p.set_age(17);
p.set_sex("man");
p.set_age(18);
//2.将person对象序列化 -- 字符串
string output;
p.SerializeToString(&output);
cout << "序列化后的数据是:" << output << endl;
//3.数据传输
//
//4.接收数据,解析(output) --解码
//4.1创建对象
Person p1;
//4.2解析
p1.ParseFromString(output);
//5.处理原始数据 -- 打印数据信息
cout << "id = " << p1.id() << endl;
cout << "name = " << p1.name(0) << p1.name(1) << p1.name(2) << endl;
cout << "sex = " << p1.sex() << endl;
cout << "age = " << p1.age() << endl;
system("pause");
return 0;
}
protobuf中的枚举
syntax = "proto3";
enum Color//枚举也是类型
{
Red = 0;//protobuf中第一个枚举值必须是0
Green = 6;
Blue = 9;
}
message Person
{
int64 id = 1;
repeated bytes name = 2;
string sex = 3;
int64 age = 4;
Color color = 5;
}
#include <iostream>
#include "Persion.pb.h"
using namespace std;
int main()
{
//1.创建person对象,并且初始化
Person p;
p.set_id(1001);
p.add_name();
p.set_name(0, "路飞");
p.add_name();
p.set_name(1, "艾斯");
p.add_name();
p.set_name(2, "萨博");
p.set_sex("man");
p.set_age(17);
p.set_sex("man");
p.set_age(18);
// 枚举
p.set_color(Blue);
//2.将person对象序列化 -- 字符串
string output;
p.SerializeToString(&output);
cout << "序列化后的数据是:" << output << endl;
//3.数据传输
//
//4.接收数据,解析(output) --解码
//4.1创建对象
Person p1;
//4.2解析
p1.ParseFromString(output);
cout << "id: " << p1.id() << ", name: "
<< p1.name(0) << ", "
<< p1.name(1) << ", "
<< p1.name(2)
<< ", sex: " << p1.sex() << ", age: " << p1.age()
<< ", color: " << p1.color()
<< endl;
system("pause");
return 0;
}
在proto文件中导入另外一个proto
syntax = "proto3";
//导入另外一个proto文件
import "Info.proto";
enum Color
{
Red = 0;//protobuf中第一个枚举值必须是0
Green = 6;
Blue = 9;
}
message Person
{
int32 id = 1;
repeated bytes name = 2;
string sex = 3;
int32 age = 4;
Color color = 5;
Info info = 6;
}
syntax = "proto3";
message Info
{
bytes address = 1; // 地址
int32 number = 2; // 门牌号
}
#include <iostream>
#include "Persion.pb.h"
using namespace std;
int main()
{
//1.创建person对象,并且初始化
Person p;
p.set_id(1001);
p.add_name();
p.set_name(0, "路飞");
p.add_name();
p.set_name(1, "艾斯");
p.add_name();
p.set_name(2, "萨博");
p.set_sex("man");
p.set_age(17);
p.set_sex("man");
p.set_age(18);
// 枚举
p.set_color(Blue);
Info * info = p.mutable_info();
info->set_address("hello 新世界");
info->set_number(101);
//2.将person对象序列化 -- 字符串
string output;
p.SerializeToString(&output);
cout << "序列化后的数据是:" << output << endl;
//3.数据传输
//
//4.接收数据,解析(output) --解码
//4.1创建对象
Person p1;
//4.2解析
p1.ParseFromString(output);
//将Info对象的值取出来
//当右边是一个 const itwz::Persion& 类型的返回值时,C++ 的编译器会将 const 引用视为一个有效的拷贝源。这意味着,即使返回的是 const 引用,C++ 仍会允许拷贝构造一个新的对象。
Info li = p1.info();//info()的返回值是const &
//const Info &li=p1.info();
cout << "id: " << p1.id() << ", name: "
<< p1.name(0) << ", "
<< p1.name(1) << ", "
<< p1.name(2)
<< ", sex: " << p1.sex() << ", age: " << p1.age()
<< ", color: " << p1.color()
<< ", address: "<<li.address()
<< ", number: "<<li.number()
<< endl;
system("pause");
return 0;
}
proto文件同名,在protobuf中添加命名空间
包–>命名空间
两个都是message Persion
syntax = "proto3";
//导入另外一个proto文件
import "Info.proto";
//添加命名空间
package itcast;//Persion类中的命名空间
enum Color
{
Red = 0;//protobuf中第一个枚举值必须是0
Green = 6;
Blue = 9;
}
message Persion
{
int32 id = 1;
repeated bytes name = 2;
string sex = 3;
int32 age = 4;
Color color = 5;
itwz.Persion info = 6;
}
syntax = "proto3";
//Persion类属于itwz这个命名空间
package itwz;
message Persion
{
bytes address = 1; // 地址
int32 number = 2; // 门牌号
}
#include <iostream>
#include "Persion.pb.h"
using namespace std;
using namespace itcast;
int main()
{
//1.创建person对象,并且初始化
Persion p;
p.set_id(1001);
p.add_name();
p.set_name(0, "路飞");
p.add_name();
p.set_name(1, "艾斯");
p.add_name();
p.set_name(2, "萨博");
p.set_sex("man");
p.set_age(17);
p.set_sex("man");
p.set_age(18);
// 枚举
p.set_color(Blue);
itwz::Persion* info = p.mutable_info();
info->set_address("hello 新世界");
info->set_number(101);
//2.将person对象序列化 -- 字符串
string output;
p.SerializeToString(&output);
cout << "序列化后的数据是:" << output << endl;
//3.数据传输
//
//4.接收数据,解析(output) --解码
//4.1创建对象
Persion p1;
//4.2解析
p1.ParseFromString(output);
//将Info对象的值取出来
itwz::Persion li = p1.info();
cout << "id: " << p1.id() << ", name: "
<< p1.name(0) << ", "
<< p1.name(1) << ", "
<< p1.name(2)
<< ", sex: " << p1.sex() << ", age: " << p1.age()
<< ", color: " << p1.color()
<< ", address: "<<li.address()
<< ", number: "<<li.number()
<< endl;
system("pause");
return 0;
}