是一种由google开发的高效、跨语言、跨平台的序列化框架。它的核心功能是定义结构化数并将其序列化(序列化是指将数据对象转换为以字节流以便传输或存储:所谓序列化就通俗来说就是把内存的一段数据转换为二进制并存储或者通过网络传输,而读取磁盘或另一端接收到后可以在内存中重建这段数据,即protobuf就是编解码,可以把程序中的一些对象用pb序列化,然后存储到本地文件,过一会在读取文件,然后恢复出那些对象),同时支持多种多种语言。它被广泛应用于需要高性能数据传输和存储的场景。
优点:
缺点:
ubuntu:sudo apt install protobuf-compiler
centos:sudo yum install protobuf-compiler
windows:https://github.com/protocolbuffers/protobuf/releases
下载.zip文件后解压并配置环境变量即可
.proto 是 Protobuf 的核心数据结构描述文件,类似于定义类的头文件。
文件中使用 message 关键字定义数据结构(类似 C++ 的 class)。
示例:persion.proto
// person.proto
syntax = "proto3";
package = test;
message Person {
string name = 1;
int32 age = 2;
}
用 protoc 编译器将 .proto 文件编译为对应语言的源文件, .pb.h 和 .pb.cc(C++ 中使用)。
protoc --cpp_out=生成文件的目标路径 person.proto
#include "person.pb.h"
using namespace test;
Person person;
person.set_name("Tom");
person.set_age(20);
std::string name = person.name();
int age = person.age();
std::string output;
person.SerializeToString(&output);
Person new_person;
new_person.ParseFromString(output);
syntax = “proto3”;
syntax = “proto2”;
option = optimize_for = SPEED
默认的优化场景:优化生成代码以提高性能即运行速度,者通常会生成较大的代码文件,因为它内联了更多的逻辑
option = optimize_for = CODESIZE
优化生成的代码以最小化文件大小
option = optimize_for = LITE_RUNTIME
生成的代码会依赖一个较小的protocol buffers运行库,而不是完整的标准库lite_runtime,因此降低链接库的大小,但这个lite_runtime库去掉了许多高级功能
package 命名空间;
package tengxun::myprotobuf;
在程序中使用时,先include生成的对应pb.h文件,再使用设置的命名空间
#include “......pb.h”
using namespace tengxun::myptotobuf;
一个message是一组字段(field)的集合,每个字段都有以下元素:
message Test {
string name = 1;
}
①字段类型:比如字符串、整数等
②字段名
③字段编号:唯一标识字段的数字
④字段的定义规则
singular(单值字段):表示消息中可以存在零个或一个该字段。这个规则在proto3中是默认的字段规则。如果字段没有被设置,它将使用该字段类型的默认值。上述示例中的name就是默认的singular
optional(可选字段):表示该字段是可选的,他可以在消息中存在,也可以缺失(这里的缺失是指在程序中可以不显示地为optional字段赋值,protobuf会自动为该字段填充一个默认值)。如果缺 失,字段将使用该类型的默认值。
repeated(重复字段):表示该字段可以重复零次或多次,即他是一个列表。消息中该字段的值可以出现任意次数,并且系统会保留重复值的顺序,对于这种类型的字段,protobuf会根据字段类型生 成一个列表(或数组)
message Persion {
repeated string phone_number = 1;
}
Persion persion;
persion.add_phone_numbers(“123”);
persion.add_phone_numbers(“456”);
for (int i = 0; i < perison.phone_numbers_size(); ++i) {
std::cout << persion.phone_numbers(i) << std::endl;
}
map(键值对字段):表示一个键值对字段,键和值的类型可以是基本类型,每个map字段都包含一个唯一的键与对应的值,键是唯一的,并且会自动去重。
message Persion{
map email_address = 4;
}
Persion persion;
persion.mutable_email_address()->insert({“a”, “b”});
persion.mutable_email_address()->insert({“c”,”d”});
enum MyTest {
FIRST_VALUE = 0;
SECOND_VALUE = 1;
}
①枚举类型名使用大驼峰命名,值名使用全大写,字母间以_分割
②每个枚举定义必须包括一个映射到0的常量作为第一个元素
③不能使用负数作为枚举值,编码效率低
④每个枚举值以分号(;)而不是逗号
⑤在枚举中可以为不同的枚举常量分配相同的值来定义别名。为此需要将allow_alias选项设置为 true
enum EnumAllowAlias {
option allow_alias = true;
UNKNOWN = 0;
STARTING = 1;
RUNNING = 1;
}
message Request {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
message SearchResponse {
repeated Request requests = 1; 表示一个结果列表
}
message SearchResponse {
message Request {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
repeated Request requests = 1;
}
如果嵌套类型需要在其他消息中使用,需要通过父类型引用该嵌套类型。假如Request是SearchResponse内部定义的嵌套消息类型
message SomeOtherMessage {
SearchResponse.Request request = 1;
}
示例
error.proto
message NetworkErrorDetails {
string error_code = 1;
string error_message = 2;
}
message ErrorStatus {
string message = 1;
repeated google.protobuf.Any details = 2;
}
test.cpp
#include
#include "error.pb.h" // 包含 NetworkErrorDetails 和 ErrorStatus 的定义
#include // Any 类型需要包含的头文件
int main() {
// 创建 NetworkErrorDetails 对象并设置字段
NetworkErrorDetails details;
details.set_error_code("404"); // 设置错误码,例如 HTTP 404
details.set_error_message("not found"); // 设置错误信息内容
// 创建 ErrorStatus 对象用于封装多个错误详情
ErrorStatus status;
status.set_message("error occur"); // 设置顶层错误描述信息
// 将 NetworkErrorDetails 打包进 Any 对象,并添加到 ErrorStatus 中的 repeated Any 列表中
google::protobuf::Any* any_details = status.add_details(); // 获取 repeated Any 的新增元素指针
any_details->PackFrom(details); // 使用 PackFrom 将实际类型打包到 Any 对象中
// 遍历 ErrorStatus 中所有的 details(Any 类型),解包并识别具体类型
for (const google::protobuf::Any& item : status.details()) {
if (item.Is()) {
// 如果能识别为 NetworkErrorDetails 类型,则解包
NetworkErrorDetails network_error;
item.UnpackTo(&network_error); // 将 Any 类型解包为原始类型 NetworkErrorDetails
// 输出解包后的错误信息
std::cout << "Error Code: " << network_error.error_code() << std::endl;
std::cout << "Error Message: " << network_error.error_message() << std::endl;
}
}
return 0;
}
.proto
message SampleMessage {
oneof test_oneof {
string name = 1;
int32 age = 2;
bool is_active = 3;
}
}
.cpp
SampleMessage message;
message.set_name(“name”);
if (message.has_name()) {
std::cout << message.name();
}
message MyMesage {
string name = 1;
}
protobuf自动为每个消息类型生成相应的构造函数和方法,用于构建和初始化消息。
MyMessage msg;
①设置字段
set_* msg.set_name(“john”);
②获取字段值:
get_* std::string name = msg.get_name();
*() std::string name = msg.name();
在c++中protobuf为每个字段生成了对应的访问器getter函数,即”字段名()”,字段是私有的,需要通过提供的这些访问器来访问这些字段
③检查字段是否设置:
has_* bool is_set = msg.has_name();
④清除字段:
clear_* msg.clear_name();
⑤访问消息变量的可变引用(指针),允许修改该字段的值
.mutable_*
.proto
message B {
string description = 1;
}
message Msg {
string name = 1;
B b = 2;
}
.cpp
Msg msg;
B* b = msg.mutable_b();
b - > set_description(“name”);
msg.set_name(“name”);
std::cout << msg.b().description();
.proto
message MyMessage {
string name = 1;
int32 age = 2;
repeated int32 numbers = 3;
}
.cpp
MyMessage msg;
①获取repeated字段的元素数量:msg.numbers_size();
②访问单个元素:msg.numbers(0);
③添加元素:msg.add_numbers(10);
④修改元素:msg.mutable_numbers(0) = 15;
⑤清除所有元素:msg.clear_numbers();
.proto
message Product {
string name = 1;
map attrs = 2;
}
.cpp
Product msg;
①设置键值对:msg.mutable_attrs()->insert({“key”, “value”});
②获取键值对:msg.attrs().at(“key”);
③检查键是否存在:msg.attrs().contains(“key”);
④清空所有键值对:msg.clear_attrs();
①序列化为字节流:SerializeToString、SerializeToArray将消息对象序列化为字符串或者字节数组
②从字节流反序列化:ParseFromString、ParseFromArray方法将字节流反序列化为消息对象
// .proto
message MyMessage {
string name = 1;
int32 age = 2;
}
// .cpp
MyMessage msg;
msg.set_name("Alice");
msg.set_age(25);
// 序列化为 string
std::string output;
msg.SerializeToString(&output);
// 序列化为数组
int size = msg.ByteSizeLong();
char* buffer = new char[size];
msg.SerializeToArray(buffer, size);
// 从 string 反序列化
MyMessage from_str;
from_str.ParseFromString(output);
// 从 array 反序列化
MyMessage from_arr;
from_arr.ParseFromArray(buffer, size);
delete[] buffer;