目录

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中的类型可以是除了maprepeated之外的任何类型

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类型互转

  1. enum->string

    std::string str = AuthOnce_Name(AuthOnce::SECPOLICYTREE_DELETE)

  2. string->enum

    auto descriptor = AuthOnce_descriptor();

    AuthOnce once_auth_type = (AuthOnce)descriptor->FindValueByName(auth.auth_type())->number()