Protobuf
# 初步认识
Protocol Buffers 是一种语言中立、平台中立、可扩展的结构化数据序列化机制。类似于 JSON,但更小更快,并且能生成原生语言绑定。只需一次性定义好想要的数据结构,然后就可以使用专门生成的源代码,轻松地从各种数据流中读写你的结构化数据,并支持多种语言。
- 跨语言、跨平台:即 ProtoBuf 支持 Java、C++、Python?等多种语言,支持多个平台
- 高效:即比 XML 更小、更快、更为简单
- 扩展性、兼容性好:更新数据结构,而不影响和破坏原有的旧程序
# Linux 安装
wget https://github.com/protocolbuffers/protobuf/releases/download/v21.11/protobuf-cpp-3.21.11.zip:表示下载 21.11 版本的支持 C++语言的 protobuf- unzip 解压压缩包
- 安装
# 执行configure
./configure --prefix=/usr/local/protobuf
# 安装
make
sudo make install
1
2
3
4
5
6
2
3
4
5
6
- 修改配置
sudo vim /etc/profile
# 添加内容如下:
# (动态库搜索路径) 程序加载运行期间查找动态链接库时指定除了系统默认路径之外的其他路径
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/protobuf/lib/
# (静态库搜索路径) 程序编译期间查找动态链接库时指定查找共享库的路径
export LIBRARY_PATH=$LIBRARY_PATH:/usr/local/protobuf/lib/
# 执行程序搜索路径
export PATH=$PATH:/usr/local/protobuf/bin/
# c程序头⽂件搜索路径
export C_INCLUDE_PATH=$C_INCLUDE_PATH:/usr/local/protobuf/include/
# c++程序头⽂件搜索路径
export CPLUS_INCLUDE_PATH=$CPLUS_INCLUDE_PATH:/usr/local/protobuf/include/
# pkg-config 路径
export PKG_CONFIG_PATH=/usr/local/protobuf/lib/pkgconfig/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
最后source /etc/profile
protoc --version检查版本
# 快速上手
- 指定版本: Protocol Buffers 语言版本 3,简称 proto3,是 .proto 文件最新的语法版本。proto3 简化了 Protocol Buffers 语言,易于使用。
// 指定proto版本
syntax = "proto3";
1
2
2
- package: package 是一个可选的声明符,能表示 .proto 文件的命名空间,是为了避免我们定义的消息出现冲突。
// 包名
package contacts;
1
2
2
- message:
message 就是要定义的结构化对象,可以给这个结构化对象中定义其对应的属性内容。字段定义格式为:字段类型 字段名 = 字段唯一编号
- 字段名称命名规范:全小写字母,多个字母之间⽤
_连接 - 字段类型分为:标量数据类型和特殊类型(包括枚举、其他消息类型等)
- 字段唯一编号:用来标识字段,一旦开始使用就不能够再改变
- 字段名称命名规范:全小写字母,多个字母之间⽤
// 首行:指定语法指定行
syntax = "proto3";
// package:命名空间
package cen;
message People {
// 字段的定义:字段类型 字段名 = 字段编号
// string name = 1
string name = 1;
int32 age = 2;
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 类型对比
| Protobuf 类型 | C++ 类型 | 说明 |
|---|---|---|
double | double | 双精度浮点数 |
float | float | 单精度浮点数 |
int32 | int32 | 可变长编码,对负数效率低 |
int64 | int64 | 可变长编码,对负数效率低 |
uint32 | uint32 | 可变长编码 |
uint64 | uint64 | 可变长编码 |
bool | bool | 布尔值 |
bytes | std::string | 任意字节序列 |
# 编译
编译命令行格式为:protoc [--proto_path=IMPORT_PATH] --cpp_out=DST_DIR path/to/file.proto
- --proto_path 指定被编译的 .proto 文件所在目录,可简写成 -I IMPORT_PATH。如不指定该参数,则在当前目录进行搜索。当 import 其他 .proto 文件时,同时也不在当前目录下,这时就要用 -I 来指定搜索目录
- --cpp_out= 指编译后的文件为 C++ 文件
- OUT_DIR 编译后生成文件的目标路径
- path/to/file.proto 要编译的 .proto 文件路径
[root@VM-4-9-opencloudos lesson08-protobuf]# protoc --cpp_out=. contacts.proto
[root@VM-4-9-opencloudos lesson08-protobuf]# ls
contacts.pb.cc contacts.pb.h contacts.proto
1
2
3
2
3
其中,提供了包括序列化、反序列化等一系列接口
# 序列化与反序列化
// 序列化:
// 将类对象中的数据序列化为字符串, c++ 风格的字符串, 参数是一个传出参数
bool SerializeToString(std::string* output) const;
// 将类对象中的数据序列化为字符串, c 风格的字符串, 参数 data 是一个传出参数
bool SerializeToArray(void* data, int size) const;
// 反序列化:
bool ParseFromString(const std::string& data) ;
bool ParseFromArray(const void* data, int size);
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 使用示例
#include <test.pb.h>
#include <iostream>
#include <string>
int main() {
// 序列化:
cen::People people;
std::string people_str;
people.set_name("zhangsan");
people.set_age(22);
if (!people.SerializeToString(&people_str)) {
std::cout << "serialize error!" << std::endl;
return 1;
}
std::cout << "序列化" << std::endl;
std::cout << people_str << std::endl;
// 反序列化:
cen::People people1;
if (!people1.ParseFromString(people_str)) {
std::cout << "serialize error!" << std::endl;
return 1;
}
std::cout << "反序列化" << std::endl;
std::cout << "name:" << people1.name() << " " << "age:" << people1.age() << std::endl;
return 0;
}
/*
输出:
序列化
zhangsan
反序列化
name:zhangsan age:22
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 字段规则
字段可以用以下规则来修饰:
- singular:消息中可以包含该字段零次或一次(不超过一次)。proto3 语法中,字段默认使用该规则
- repeated:消息中可以包含该字段任意多次(包括零次),其中重复值的顺序会被保留。理解为定义了一个数组
# enum
例如:
电话类型:移动电话和固定电话:
enum phone_type {
MP = 0;
TEL = 1;
}
1
2
3
4
2
3
4
# Any
理解为泛型类型
// <google/protobuf/any.pb.h>
bool PackFrom(const ::PROTOBUF_NAMESPACE_ID::Message& message) {
GOOGLE_DCHECK_NE(&message, this);
return _impl_._any_metadata_.PackFrom(GetArena(), message);
}
bool PackFrom(const ::PROTOBUF_NAMESPACE_ID::Message& message,
::PROTOBUF_NAMESPACE_ID::ConstStringParam type_url_prefix) {
GOOGLE_DCHECK_NE(&message, this);
return _impl_._any_metadata_.PackFrom(GetArena(), message, type_url_prefix);
}
bool UnpackTo(::PROTOBUF_NAMESPACE_ID::Message* message) const {
return _impl_._any_metadata_.UnpackTo(message);
}
static bool GetAnyFieldDescriptors(
const ::PROTOBUF_NAMESPACE_ID::Message& message,
const ::PROTOBUF_NAMESPACE_ID::FieldDescriptor** type_url_field,
const ::PROTOBUF_NAMESPACE_ID::FieldDescriptor** value_field);
template <typename T, class = typename std::enable_if<!std::is_convertible<T, const ::PROTOBUF_NAMESPACE_ID::Message&>::value>::type>
bool PackFrom(const T& message) {
return _impl_._any_metadata_.PackFrom<T>(GetArena(), message);
}
template <typename T, class = typename std::enable_if<!std::is_convertible<T, const ::PROTOBUF_NAMESPACE_ID::Message&>::value>::type>
bool PackFrom(const T& message,
::PROTOBUF_NAMESPACE_ID::ConstStringParam type_url_prefix) {
return _impl_._any_metadata_.PackFrom<T>(GetArena(), message, type_url_prefix);}
template <typename T, class = typename std::enable_if<!std::is_convertible<T, const ::PROTOBUF_NAMESPACE_ID::Message&>::value>::type>
bool UnpackTo(T* message) const {
return _impl_._any_metadata_.UnpackTo<T>(message);
}
template<typename T> bool Is() const {
return _impl_._any_metadata_.Is<T>();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
- 使用 PackFrom() 可以将任意消息类型转为 Any 类型
- 使用 UnpackTo() 可以将 Any 类型转回之前设置的任意消息类型
- 使用 Is() 可以用来判断存放的消息类型是否为 typename T