protobuf是什么
简单来讲,ProtoBuf(全称为ProtocolBuffer)是让结构数据序列化的⽅法,其具有以下特点:
• 语⾔⽆关、平台⽆关:即ProtoBuf⽀持Java、C++、Python等多种语⾔,⽀持多个平台。
• ⾼效:即⽐XML更⼩、更快、更为简单。
• 扩展性、兼容性好:你可以更新数据结构,⽽不影响和破坏原有的旧程序。
使用特点
ProtoBuf是需要 依赖通过编译⽣成的头⽂件和源⽂件来使⽤的。
也就是说,写一份.proto后缀的文件,再用相应的编译规则去编译,可以生成不同语言的代码。
位置:
/usr/local/protobuf/include/google/protobuf/
通讯录1.0
1.创建一份.proto文件
1.命名规范:
- 创建.proto⽂件时,⽂件命名应该使⽤全⼩写字⺟命名,多个字⺟之间⽤ _ 连接。例如:lower_snake_case.proto 。
- 书写.proto⽂件代码时,应使⽤2个空格的缩进。
我们为通讯录1.0新建⽂件: contacts.proto
指定proto3语法:
syntax = "proto3" proto3语法是最新的语法版本,更简化,易于使用,支持更多语言的生成。
package声明符:
package能表⽰.proto⽂件的 命名空间,在项⽬中要有唯⼀性。它的作⽤是为了避免我们定义的消息出现冲突。
2.定义消息(message):
消息(message):要定义的结构化对象,我们可以给这个结构化对象中定义其对应的属性内容。
定义消息字段:
在message中我们可以定义其属性字段,字段定义格式为: 字段类型 + 字段名=字段唯⼀编号;
- 字段名称命名规范:全⼩写字⺟,多个字⺟之间⽤_连接。
- 字段类型分为:标量数据类型和特殊类型(包括枚举、其他消息类型等)。
- 字段唯⼀编号:⽤来标识字段,⼀旦开始使⽤就不能够再改变。
.proto Type | Notes | c++ Type |
---|---|---|
double | double | |
float | float | |
int32 | 使⽤变⻓编码[1]。负数的编码效率较低⸺若字段可能为负值,应使⽤sint32代替 | int32 |
int64 | 使⽤变⻓编码[1]。负数的编码效率较低⸺若字段可 能为负值,应使⽤sint64代替 | int64 |
uint32 | 使用变长编码[1] | uint32 |
uint64 | 使用变长编码[1] | uint64 |
sint32 | 使⽤变⻓编码[1]。符号整型。负值的编码效率⾼于 常规的int32类型 | sint32 |
sint64 | 使⽤变⻓编码[1]。符号整型。负值的编码效率⾼于 常规的int64类型 | sint64 |
fixed32 | 定⻓4字节。若值常⼤于2^28则会⽐uint32更⾼ 效 | uint32 |
fixed64 | 定⻓8字节。若值常⼤于2^56则会⽐uint64更⾼效 | uint64 |
sfixed32 | 定⻓4字节 | int32 |
sfixed64 | 定⻓8字节 | int64 |
bool | bool | |
string | 包含UTF-8和ASCII编码的字符串,⻓度不能超过2^32 | string |
bytes | 可包含任意的字节序列但⻓度不能超过2^32 | string |
[1]变⻓编码:经过protobuf编码后,原本4字节或8字节的数可能会被变为其他字节数
在这⾥还要特别讲解⼀下字段唯⼀编号的范围:
1~536,870,911(2^29-1),其中19000?~19999不可⽤。
19000~19999不可⽤是因为:在Protobuf协议的实现中,对这些数进⾏了预留。
值得⼀提的是,范围为1~15的字段编号需要⼀个字节进⾏编码,16~2047内的数字需要两个字节进⾏编码。编码后的字节不仅只包含了编号,还包含了字段类型。所以 1~15要⽤来标记出现⾮常频繁的字段,要为将来有可能添加的、频繁出现的字段预留⼀些出来。
3.编译命令
编译命令⾏格式为:
protoc [--proto_path=IMPORT_PATH] --cpp_out=DST_DIR path/to/file.proto
protoc 是 Protocol Buffer 提供的命令⾏编译⼯具。
2.编译contacts.proto⽂件后会⽣成什么
- 对于每个message,都会⽣成⼀个对应的消息类。
- 在消息类中,编译器为每个字段提供了获取和设置⽅法,以及⼀下其他能够操作字段的⽅法。
- 编辑器会针对于每个 .proto⽂件⽣成 .h 和 .cc⽂件,分别⽤来存放类的声明与类的实现
3.常用的序列化,反序列化方法
class MessageLite {
public:
//序列化:
bool SerializeToOstream(ostream* output) const; // 将序列化后数据写⼊⽂件
流
bool SerializeToArray(void *data, int size) const;
bool SerializeToString(string* output) const;
//反序列化:
bool ParseFromIstream(istream* input); // 从流中读取数据,再进⾏反序列化
动作
bool ParseFromArray(const void* data, int size);
bool ParseFromString(const string& data);
};
注意:protobuf序列化出来的是二进制,放到String里打印会有误差,破解成本增⼤,所以ProtoBuf编码是相对安全的。
4.字段类型的规则
消息的字段可以⽤下⾯⼏种规则来修饰:
- singular:消息中可以包含该字段零次或⼀次(不超过⼀次)。proto3语法中,字段默认使⽤该规则。
- repeated:消息中可以包含该字段任意多次(包括零次),其中重复值的顺序会被保留。可以理解为定义了⼀个数组。
下面是contacts.proto
//首行写使用语法syntax = "proto3";package contacts;message PeopleInfo{string name = 1; //编号int32 age = 2;}
下面是main.cc
#include <iostream>
#include "contacts.pb.h"
#include <string>int main()
{std::string str;contacts::PeopleInfo person;person.set_name("张三");person.set_age(20);bool ret = person.SerializeToString(&str);if (ret == false)std::cout << "序列化失败" << std::endl;std::cout << "序列化成功,str: " << str << std::endl;{contacts::PeopleInfo person;bool ret = person.ParseFromString(str);if (ret == false)std::cout << "反序列化失败" << std::endl;std::cout << "反序列化成功" << std::endl;std::cout << "name: " << person.name() << std::endl;std::cout << "age: " << person.age() << std::endl;}return 0;
}