一个故事
//这里故意用的汉语拼音,防止英文单词多重含义
enum XingBie
{
FEMALE,
MALE
};
struct School
{
std::string m_strName; //学校名字
std::string m_Address; //学校地址
};
struct Student
{
std::string m_strName; //姓名
unsigned int m_uAge; //年龄
XingBie m_eXingbie; //性别
std::vector<School> m_vSchools; //学习过的学校
};
复制
整形
: 就采用四个字节字符串
: 方法有多种,假设选择了最后一种。可以在协议中以
\0
结尾表示结束,也可以在字符串以固定长度来表示,比如255
在字符串的表述前面加一个长度,这样也可以用来表示任意长度的,任意字符的字节流
数组
: 比如上述的Student
就读过多所School
,那么可以在数组前面加个数量,然后依次输入School
信息
序列化
: 内存里面的对象是连续内存的,但是对象管理啊的数据不一定,序列化就是将这些内存的数据表示到连续的内存中。作为客户端,将序列化的内容发送到服务端。反序列化
: 一般来说接受到数据的服务器再将数据反序列化
为内存里对象的结构状态,便于我们去操作。
而这些序列化
的方法就由上述定义的协议来进行代码编写,反序列化
则是一个解析数据的过程,也需要进行代码编写。
代码后续要增加新的类型,得重新在协议中定义
后续传输的数据进行变更,
对象的成员和方法
,序列化
与反序列化
代码都得跟着去修改,并且可能存在服务器与客户端不一致的兼容性问题。
Protobuf for C++
编写一个
Student.proto
文件,去定义你的一个Message
然后根据
Student.proto
, 用protoc
生成相应的语言代码, 比如C++
,Golang
,Python
,C#
,Java
等等。这样也便于在分布式环境中,多个不同语言的服务之间通过Protobuf
去通信。其实除了分布式的网络访问方式,有时候也可以在同一个进程里跨语言调用,比如C#/Python/Golang调用C++的代码,使用了Protobuf也就不用过于关心不同语言之间数据类型兼容的问题,调用的时候只需要传入一个序列化的数据地址和数据大小。
Student.proto
协议采用的是
proto3
package ProtoSample
那么就转换为C++的namespace ProtoSample
所有的字段均是
singular
, 也就是proto2
中的optional
, 并且注意proto3
中也没有required
字段了。
syntax = "proto3";
package ProtoSample;
message School {
string name = 1;
string address = 2;
}
enum XingBie {
FEMALE = 0;
MALE = 1;
}
message Student {
string name = 1;
int32 age = 2;
XingBie xingbie = 3;
repeated School schools = 4;
}
复制
编译Student.proto
先编译Protobuf的源码,参照官方文档即可。以Windows为例(Linux类似),编译后产生
protoc.exe
和libprotobuf.lib
protoc.exe
用于编译Student.proto
,将产生两个源码文件Student.pb.h
和Student.pb.cc
: 这个文件主要就是传输的数据结构的定义,包括设置/获取接口,序列化与反序列化等。最后就是自己的项目编译,把上面产生的
Student.pb.h
,Student.pb.cc
和libprotobuf.lib
都引用在项目中。
简单说下编译Student.proto
到C++的源码文件的命令:protoc -I=. --cpp_out=. Student.proto
protobuf的代码使用
CreateStudent
中直接构造一个对象SerializeToString
序列化ParseFromString
反序列化在有些系统构成中,可能还需要用到
json
,也可以直接使用MessageToJsonString
将对象序列化为一个json
#include <iostream>
#include <string>
#include <vector>
#include "Student.pb.h"
#include "google/protobuf/util/json_util.h"
// 构造一个学生对象
void CreateStudent(ProtoSample::Student& student)
{
student.set_name(u8"一个程序员的修炼之路");
student.set_age(18);
student.set_xingbie(ProtoSample::XingBie::MALE);
auto pSchool = student.add_schools();
pSchool->set_name(u8"小学");
pSchool->set_address(u8"老地方");
pSchool = student.add_schools();
pSchool->set_name(u8"初中");
pSchool->set_address(u8"新地方");
}
//打印一个学生信息
void PrintStudent(const ProtoSample::Student& student)
{
std::cout << "Student Name: " << student.name() << std::endl
<< "Student Age: " << student.age() << std::endl
<< "Student Xingbie: " << student.xingbie() << std::endl;
for (auto& school : student.schools())
{
std::cout << "School Name: " << school.name() << std::endl
<< "School Address: " << school.address() << std::endl;
}
}
int main()
{
setlocale(LC_CTYPE, "zh-cn.utf-8");
// 1. 构造一个学生信息
ProtoSample::Student student;
CreateStudent(student);
// 2. 序列化学生信息
std::string strStudentInBytes;
student.SerializeToString(&strStudentInBytes);
// 3. 反序列化学生
ProtoSample::Student studentNew;
studentNew.ParseFromString(strStudentInBytes);
PrintStudent(studentNew);
// 4. 序列化到Json格式
std::string strJson;
google::protobuf::util::MessageToJsonString(studentNew, &strJson);
std::cout << strJson << std::endl;
return 0;
}
复制
proto 2 和 proto 3
Protobuf VS Json
使用复杂度
Protobuf
需要定义一个Schema
文件.proto
,并且需要编译,引入源码文件和库。JSON
直接文本形式表述,很多语言内置支持。
数据表达能力
数据格式
JSON
采用文本, 一般来说体积比二进制大,传输的带宽和效率也会相对较低。Protbuf
二进制
效率
语言支持
参考
《Protocol Buffers》: https://developers.google.com/protocol-buffers/docs/overview
《Difference Between Protobuf vs JSON》: https://www.educba.com/protobuf-vs-json/
《区分 Protobuf 中缺失值和默认值》 : https://zhuanlan.zhihu.com/p/46603988