1. 序列化
序列化 (Serialization)将对象的状态信息转换为可以存储或传输的形式的过程,与之相对应的过程称之为反序列化(Unserialization)。==序列化和反序列化主要用于解决在跨平台和跨语言的情况下, 模块之间的交互和调用,但其本质是为了解决数据传输问题==。
实现数据序列化:
- 要有原始数据
- 复合类型 -> 最常见的情况
- 基础数据类型
- 通过某些方式 -> 另外一种形式的数据
- 得到的数据干啥? -> 目的: 进行分发, 分发到不同的终端/平台, 保证不同的平台能正确解析
- 网络传输
- 磁盘拷贝
序列化目的不是为了加密, 为的是数据的跨平台传输
序列化的整体过程:
- 发送端
- 原始数据 -> 序列化 (编码) -> 特殊格式的字符串
- 发送这个字符串
- 接收端:
- 接收数据
- 特殊格式的字符串 -> 反序列化 (解码) -> 原始数据
- 对原始数据进行处理
1.1 网络通信中的问题分析
发送过程中遇到的一些问题?
-
平台不同
- 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字节
- 语言不同数据类型占用的内存有可能不同
-
字节对齐问题
1.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的事实标准协议。
更多资料可查看: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 序列化的类
2. protobuf
Protocol Buffer( 简称 Protobuf) 是Google公司内部的混合语言数据标准,它是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,很适合做数据存储或RPC 数据交换格式。
Protobuf是一个纯粹的展示层协议,可以和各种传输层协议一起使用,Protobuf的文档也非常完善。google 提供了多种语言的实现:java、c#、c++、go 和 python,每一种实现都包含了相应语言的编译器以及库文件。
Protobuf支持的数据类型相对较少,不支持常量类型。由于其设计的理念是纯粹的展现层协议,目前并没有一个专门支持Protobuf的RPC框架。
2.1 操作流程
- 准备数据
- 复合类型: 结构体/ 类
- 基础类型
- 创建一个新文件
xxx.proto- 将我们要序列化的数据 -> 写入到proto文件
- 有语法格式
- 通过一个命令
protoc将xxx.proto文件生成一个c++的类
- 对应一个头文件/ 源文件
- 操作命令-> 在window终端中:
protoc xxx.proto --cpp_out=./- 直接使用这个类
- 里边有对数据操作的api
- 读数据 api
- 方法名字
变量名()- 写数据 api
- 方法名字:
set_变量名(arg)
// 要序列化的数据
struct Person
{
int id;
string name;
string sex; // man woman
int age;
};
int id;
-
在.proto文件中定义消息格式
// protobuf的版本 syntax = "proto3"; // proto2 // 组织Person结构体 // 语法格式 message 关键字(相当于被创建出的类的名字) { // 成员变量 数据类型 变量名 = 变量的编号; // 编号从1开始, 不能重复 } // .proto文件 生成 c++ 类的命令 protoc proto文件名 --cpp_out=生成目录.proto类型 **C++**类型 备注 double double 64位浮点数 float float 32位浮点数 int32 int32 32位整数 int64 int64 64位整数 uint32 uint32 32位无符号整数 uint64 uint64 64位无符号整数 sint32 sint32 32位整数,处理负数效率比int32更高 sint64 sint64 64位整数,处理负数效率比int64更高 fixed32 uint32 总是4个字节。如果数值总是比总是比228大的话,这个类型会比uint32高效。 fixed64 uint64 总是8个字节。如果数值总是比总是比256大的话,这个类型会比uint64高效。 sfixed32 int32 总是4个字节 sfixed64 int64 总是8个字节 bool bool 布尔类型 string string 一个字符串必须是UTF-8编码或者7-bit ASCII编码的文本 bytes string 处理多字节的语言字符、如中文 enum enum 枚举 message object of class 自定义的消息类型 -
repeated限定修饰符
syntax = "proto3"; message Person { int32 id = 1; // 编号从1开始 bytes name = 2; string sex = 3; int32 age = 4; } // 要求name有多个 -> 数组 syntax = "proto3"; message Person { int32 id = 1; // 编号从1开始 // vector<string> name; repeated bytes name = 2; // name可以在程序中创建多个, 在程序中作为动态数组来使用 string sex = 3; int32 age = 4; } -
枚举
syntax = "proto3"; // 定义枚举 enum Color { Red = 0; // protbuf中第一个枚举值必须为0 Green = 6; Blue = 9; } message Person { int32 id = 1; // 编号从1开始 bytes name = 2; string sex = 3; int32 age = 4; Color color = 5; // 枚举变量 } -
proto文件的导入
// Person.proto syntax = "proto3"; // 导入另外一个proto文件 import "Info.proto"; enum Color { Red = 0; // protbuf中第一个枚举值必须为0 Green = 6; Blue = 9; } message Person { int32 id = 1; // 编号从1开始 repeated bytes name = 2; string sex = 3; int32 age = 4; Color color = 5; Info info = 6; // Info对象, 导入的proto文件中的类 }// Info.proto syntax = "proto3"; message Info { bytes address = 1; // 地址 int32 number = 2; // 门牌号 } -
包 -> 命名空间
// Person.proto syntax = "proto3"; // 导入另外一个proto文件 import "Info.proto"; // 添加命名空间 package cpplearn; // Person类属于cpplearn这个命名空间 enum Color { Red = 0; // protbuf中第一个枚举值必须为0 Green = 6; Blue = 9; } message Person { int32 id = 1; // 编号从1开始 repeated bytes name = 2; string sex = 3; int32 age = 4; Color color = 5; // 命名空间.类名 CPPlearn.Person info = 6; // Info对象, 导入的proto文件中的类 }// Info.proto syntax = "proto3"; // Person类属于CPPlearn这个命名空间 package CPPlearn; message Person { bytes address = 1; // 地址 int32 number = 2; // 门牌号 } -
使用protobuf编译器生成C++类
# protobuf编译器, 编译源码得到的 protoc.exe # 语法 # --cpp_out 生成的c++类的位置 protoc.exe xxx.proto --cpp_out=目录 -
使用C++ API来读写消息
- 读:
变量名() - 写:
set_变量名(arg1, arg2, ...)
- 读:
-
vs配置 -> 添加宏