Protocol Buffer基本使用
简介
用来序列化数据和定义服务
官方文档:https://developers.google.com/protocol-buffers/docs/proto3
从Release中找到所需语言(C++)的版本按照提示安装
源码阅读则下载仓库源码进行浏览,否则会导致代码提示不全、找不到头文件等问题
在cmake项目中使用时,通过find_package(Protobuf REQUIRED)来引入
Style 文件编写风格
https://developers.google.com/protocol-buffers/docs/style
编译
protoc --proto_path=待编译proto文件的依赖文件目录(没有则不写) --cpp_out=输出文件的保存目录 --go_out=输出文件的保存目录 待编译的proto文件
cpp编译
# 1. 手动编译
protoc --cpp_out=. test.proto
# 2. 使用cmake编译
foreach(example add_person list_people)
set(${example}_SRCS ${example}.cc)
set(${example}_PROTOS addressbook.proto)
set(executable_name ${example}_cpp)
add_executable(${executable_name} ${${example}_SRCS} ${${example}_PROTOS})
target_link_libraries(${executable_name} protobuf::libprotobuf)
protobuf_generate(TARGET ${executable_name})
endforeach()
go编译
# 首先安装支持go编译的插件,protoc-gen-go会由protoc来调用
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
# 然后编译
protoc --go_out=. test.proto
使用
Go需要引入protobuf官方包和通过proto文件生成的包
import "github.com/golang/protobuf/proto"
import "protoTest/test"
data := &test.Data{}
//序列化
str, err := proto.Marshal(data)
//反序列化
err := proto.Unmarshal(str, data)
C++需要包含通过proto文件生成的xx.pb.h头文件
类型
一般类型
各类型适用场景:https://developers.google.com/protocol-buffers/docs/proto3#scalar
float,double 浮点数
int32,int64:变长存储,对负值编码效率低,适用于有少量负值的情况,若对负值编码推荐sint32
sint32,sint64:对负值编码效率高
uint32,uint64:变长存储,只存储正值。
fix32,fix64:定长编码,只存储正值。若数值大小大于$2^{28}$,则效率比uint32高
sfix32,sfix64:定长编码,可正可负
string:包含utf-8编码或ASCII编码的字符,长度不超过$2^{32}$
bytes:包含任意字节,长度不超过$2^{32}$
bool类型默认值为false
数值类型默认值为0
枚举类型默认第一个字段的编号为0,并且必须为0
在枚举类型中,可以存在相同的编号,使其具有别名
message MyMessage {
enum EnumAllowingAlias {
option allow_alias = true; //设置allow_alias为true来允许存在相同编号
UNKNOWN = 0;
STARTED = 1;
RUNNING = 1; //STARTED的别名
}
}
Any类型
Any类型可包含任意类型数据pack后的内容
import "google/protobuf/any.proto";
message ErrorStatus {
string message = 1;
repeated google.protobuf.Any details = 2;
}
// Storing an arbitrary message type in Any.
NetworkErrorDetails details = ...;
ErrorStatus status;
status.add_details()->PackFrom(details);
// Reading an arbitrary message from Any.
ErrorStatus status = ...;
for (const Any& detail : status.details()) {
if (detail.Is<NetworkErrorDetails>()) {
NetworkErrorDetails network_error;
detail.UnpackTo(&network_error);
... processing network_error ...
}
}
oneof类型
类似c++的union,oneof中的类型可以是除了map和repeated之外的任何类型
message SampleMessage {
oneof test_oneof {
string name = 4;
SubMessage sub_message = 9;
}
}
map类型
其key可以为整数类型和string类型(不可以的为:浮点类型、bytes、enum),其value可以为除了map之外的任何类型
map不能被定义为重复(repeated)字段
map<string, Project> projects = 3;
字段与编号
编号
编号1-15使用1个字节来编码,编号16-2047使用2个字节来编码;所以编号1-15应该优先分配给最常使用的字段。可使用的最大编号为2^29 - 1,即536,870,911,除了19000-19999之内的
单一字段和重复字段
singular:默认的形式,有0或1个
repeated:字段可重复0到多次,相当于数组
字段名应使用小写加下划线来命名
保留字段
当删除一个字段时,最好在删除后将其添加为保留编号或保留名称
message Foo {
reserved 2, 15, 9 to 11, 40 to max;;
reserved "foo", "bar";
}
import 导入文件
默认情况下只能使用直接导入的 .proto 文件中的定义。但是,有时可能需要将 .proto 文件移动到新位置。此时可以在旧位置放置一个占位符 .proto 文件,以使用import public将所有导入转发到新位置,而不是直接移动 .proto 文件并在一次更改中更新所有的调用点
// new.proto
// All definitions are moved here
// old.proto
// This is the proto that all clients are importing.
import public "new.proto";
import "other.proto";
// client.proto
import "old.proto";
// You use definitions from old.proto and new.proto, but not other.proto
package 包名
定义的包名会使生成的代码在其namespace内,如下面代码生成的Open消息是在foo::bar命名空间下
在Go中,包名用来当作Go包的名称,除非在 .proto 文件中明确的提供了选项 go_package = "xxxxxxx"
package foo.bar;
message Open { ... }
message Foo {
...
foo.bar.Open open = 1;
...
}
rpc
// The greeter service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
应用
enum类型与string类型互转
-
enum->string
std::string str = AuthOnce_Name(AuthOnce::SECPOLICYTREE_DELETE)
-
string->enum
auto descriptor = AuthOnce_descriptor();
AuthOnce once_auth_type = (AuthOnce)descriptor->FindValueByName(auth.auth_type())->number()