Protocol Buffer route guide

文章主要内容为介绍使用golang protocol buffer的安装与使用,参考资料有ProtocolBuffer,GRPC route guide

安装

可以去protoclbuffer官网直接下载安装,也可以使用homebrew这类工具直接安装。

1
brew install protoc

此时安装好的是通用件,之后我们再来安装golang的编译插件protoc-gen-go

1
brew install protoc-gen-go

或者是使用golang自带的工具进行安装

1
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest

基础使用

基本控制

1
2
3
4
syntax = "proto3"; // 指定版本协议,此处使用的是3的协议
package tutorial; // 指定包名,golang中似乎不起作用
import "google/protobuf/timestamp.proto"; //导入相关文件,支持分布式导入
option go_package = "github.com/protocolbuffers/protobuf/examples/go/tutorialpb"; // 创建目录

使用语法

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
message Person { // 定义消息类型,会被特定的语言转化为特定类型,例如cpp是转译成class,golang是struct
string name = 1; //独特的消息id(tag),在传输时使用,次字段在同一个级别中不可重复
int32 id = 2; // Unique ID number for this person.
string email = 3;

enum PhoneType { //枚举类型,golang中是int32
MOBILE = 0;
HOME = 1;
WORK = 2;
}

message PhoneNumber {
string number = 1; // 字符串类型,不同语言翻译不同,golang中就是string
PhoneType type = 2;
}

repeated PhoneNumber phones = 4;//使用repeated标记为可重复字段,golang中表示为数组

google.protobuf.Timestamp last_updated = 5; // 其他包导入字段
}

// Our address book file is just one of these.
message AddressBook {
repeated Person people = 1;
}

在以上例子中,我们注意到Person类型中包含了枚举类型PhoneType以及PhoneNumber类型,不难发现protoc是支持内置类型的。并且内置类型的每个字段(field)的tag都是唯一的,因为传输过程以字段作为标识而不是名字。

并且为例节省字节数在1-15字段中为常用字段,他们在传输时只需要占用1byte的空间,16及以后的字段会占用2byte以上的空间。因此我们要尽量使用小的字段。

默认值

在integer类型中,字段的默认值为0,string的默认值为空字符串,bool类型的默认值为false。

repeated

如果字段被标记为repeated,那么protocol buffer会将其解析为对应类型的动态数组。

Complied

编译对应语言的桩代码,需要对应的插件(golang插件默认安装在GOBIN文件夹中),例如编译成go语言的代码,会默认生成filename.pb.go的桩文件。

编译

1
protoc -I=$SRC_DIR --go_out=$DST_DIR $SRC_DIR/addressbook.proto

例如要编译helloworld文件夹中的hello world.proto,我们可以使用以下命令

1
protoc --proto_path=helloworld --go_out=helloworld helloworld/helloworld.proto

其中 –proto_path指定源文件路径,**–go_out为要输出的桩代码类型,如果要输出cpp类型的桩代码则使用–cpp_out**

后面跟的是生产文件的输出路径,最后跟的是要编译的文件。因为要编译的文件可能会有依赖文件,因此最开始得指明源文件路径,用于解决依赖。

由于需要编译的文件可能会有相当多的依赖,因此在编译时会以最后指定的输出文件为有限选择

字段对照表

Trivial type

1
2
optional int32 foo = 1;
required int32 foo = 1;
1
int32 foo = 1;

Message

1
message Bar {}
1
2
3
type Baz struct {
Foo *Bar
}

Repeated

1
2
3
message Baz {
repeated Bar foo = 1; //可重复
}
1
2
3
type Baz struct {
Foo []*Bar //对应动态数组
}

初始化方式

1
2
3
4
5
6
baz := &Baz{
Foo: []*Bar{
{}, // First element.
{}, // Second element.
},
}

MapFields

1
2
3
4
5
message Bar {}

message Baz {
map<string, Bar> foo = 1;
}
1
2
3
type Baz struct {
Foo map[string]*Bar
}

OneofFields

1
2
3
4
5
6
message Profile {
oneof avatar {
string image_url = 1;
bytes image_data = 2;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
type Profile struct {
// Types that are valid to be assigned to Avatar:
// *Profile_ImageUrl
// *Profile_ImageData
Avatar isProfile_Avatar `protobuf_oneof:"avatar"`
}

type Profile_ImageUrl struct {
ImageUrl string
}
type Profile_ImageData struct {
ImageData []byte
}

Enumerations

1
2
3
4
5
6
7
8
9
enum Corpus {
UNIVERSAL = 0;
WEB = 1;
IMAGES = 2;
LOCAL = 3;
NEWS = 4;
PRODUCTS = 5;
VIDEO = 6;
}
1
2
3
4
5
enum Foo { // type Foo int32
DEFAULT_BAR = 0;
BAR_BELLS = 1;
BAR_B_CUE = 2;
}

FQA

对于不同种类的消息类型,应该如何使用?

"google.golang.org/protobuf/proto"消息是由协议缓冲区编译器的当前版本生成的所有消息实现的接口类型。对任意消息(如proto.Marshal或proto.Clone)进行操作的函数接受或返回此类型。

"google.golang.org/protobuf/reflect/protoreflect"Message是描述消息的反射视图的接口类型。对proto.Message调用ProtoReflect方法以获取Protorefect.Message。

"google.golang.org/protobuf/reflect/protoreflect"ProtoMessage是”google.golang.org/protobuf/proto”.Message`.的别名,两种消息类型可以互换。

"github.com/golang/protobuf/proto".消息是由传统GO协议缓冲区API定义的接口类型。所有生成的消息类型都实现此接口,但该接口不描述这些消息的预期行为。新代码应避免使用此类型。

什么是protocbuffer的命名空间类型冲突?

protocbuffer生成的桩代码都会链接到GO二进制的所有协议缓冲区声明都插入到全局注册表中。每个Protobuf声明(例如枚举、枚举值或消息)都有一个绝对名称,它是包名与.proto源文件中声明的相对名称(例如my.Proto.Package.MyMessage.NestedMessage)的串联。Protobuf语言假定所有声明都是全局唯一的。如果链接到GO二进制文件的两个Protobuf声明具有相同的名称,则会导致名称空间冲突,并且注册表不可能按名称正确解析该声明。根据所使用的GO协议错误的版本,这要么会在初始时失败,或者静默地丢弃冲突,并在以后的运行时可能会导致潜在的错误。

如何修复命名空间冲突?

最好方法是人为的更换类型的命名。当然也可以使用以下方法。

当单个.proto文件生成到两个或多个GO包中并链接到相同的GO二进制文件中时,它会在生成的GO包中的每个Protobuf声明上发生冲突。这通常发生在.proto文件被提供并从中生成GO包,或者生成的GO包本身被提供时。用户应避免提供服务,而应依赖于该.proto文件的集中式GO包。

后续

FAQ写不下了,更多请看原文Protocol Buffers