Protobuf协议

2019-03-17

什么是protocol buffers

Protocol buffers是Google的与语言无关、平台无关、可扩展的序列化结构数据,比xml更小、更简单、更快。
用户定义数据的结构,通过特殊生成的代码,然后就可以使用多种语言(包括C++、C#、Dart、Go、Java、Python)来读写结构化数据了。

使用protocol buffers

这里以Go为例来进行演示,其他语言请查询官方文档,本例使用proto3版本。

具体包括以下三步:

  • 定义message格式的.proto文件
  • 使用protocol buffer编译器
  • 使用Go protocol buffer API来读写message

定义protocol格式

.proto文件的定义比较简单:为每个想序列化的数据结构新增一个message,并给与每一个field一个名字和类型。为了防止命名冲突.proto文件以包声明开始。如下所示:

1
2
3
4
syntax = "proto3";
package tutorial;

import "google/protobuf/timestamp.proto";

其后定义message:

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 {
string name = 1;
int32 id = 2; // Unique ID number for this person.
string email = 3;

enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}

message PhoneNumber {
string number = 1;
PhoneType type = 2;
}

repeated PhoneNumber phones = 4;

google.protobuf.Timestamp last_updated = 5;
}

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

一个message包含一系列值类型。很多简单的标准数据类型在.proto文件中是可以使用的,其中包括boolint32floatdoublestring。当然还有其他的类型,可以参考

在上面的例子中,Person包含PhoneNumberAddressBook包含Person,我们可以嵌套定义message类型,如PhoneNumber就是嵌套在Person中定义,可以定义枚举类型,如PhoneType

每个元素的”=1”,”=2”标签是二进制编码后的唯一标识,1-15标签需要的编码数比15以后的数据要少一个byte,所以一种常用的优化方式是用1-15来标记常用的元素和重复的元素(数组),16以后的tags来标记使用频率少的元素。重复field中的每个元素都需要重新编码,比如PhoneNumber内tags重现从1开始,所以重复field也是一种常规的优化方法。

如果一个field值没有定义,一般会有默认值:numeric类型的默认值为0,strings类型的默认值是空字符串,bools类型是false,嵌套的messages默认值为”default instance”或者”prototype”,是没有元素的。

如果一个field是repeated的,这个field可以重复若干次。它在protocal buffer中的顺序将保留。可以将其理解为动态大小的数组。

编译protocol buffers

在上面的步骤中,我们得到了AddressBook.proto文件,现在我们来完成.proto文件的编译:

下载编译器

编译器地址

在linux中的安装如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 下载
wget https://github.com/protocolbuffers/protobuf/releases/download/v3.6.1/protobuf-all-3.6.1.tar.gz

# 解压
tar xvf protobuf-all-3.6.1.tar.gz
cd protobuf-3.6.1/

# 生成configure
./autogen.sh

# 编译安装
./configure
make
make check
make install

注意:./autogen.sh时可能报错,请确保安装了automake,对于未安装的请执行apt-get install automake或者yum install automake,遇到libtoolize报错,请安装apt-get install libtool*或者yum install libtool*。遇到报错:

1
protoc: error while loading shared libraries: libprotoc.so.17: cannot open shared object file: No such file or directory

将/usr/local/lib加到环境变量中:

1
export LD_LIBRARY_PATH=/usr/local/lib

安装结束后可以使用protoc --version查看protoc版本。

windows的protoc更为简单:

下载windows版的protoc:protoc-3.6.1-win32.zip,解压后将解压目录加到环境变量中,即可。可以运行protoc --version查看protoc版本。

安装Go protocol buffers插件

运行:

1
go get -u github.com/golang/protobuf/protoc-gen-go

运行结束后可以在$GOPATH/bin中看到protoc-gen-go文件。

编译.protoc文件

在当前AddressBook.proto文件目录中,运行:

1
protoc --go_out=. AddressBook.proto

运行结束后,会在本地生成AddressBook.pb.go文件。

1
2
> 详细命令的protoc:
> protoc -I=$SRC_DIR --go_out=$DST_DIR $SRC_DIR/addressbook.proto

Protocol Buffer API

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
package main

import (
"fmt"
"github.com/golang/protobuf/proto"

pb "leitty/rpc/protocol/protobuf"
)

func main() {
//初始化一个Person
p := pb.Person{
Id: 1,
Name: "Mike",
Email: "mike@test.com",
Phones: []*pb.Person_PhoneNumber{
{Number: "123", Type: pb.Person_HOME},
},
}
// 查看p中的Email
fmt.Println(p.GetEmail())

//使用Marshal函数对p进行编码
data, err := proto.Marshal(&p)
if err != nil {
panic(err)
}

//解码data
var target pb.Person
err = proto.Unmarshal(data,&target)
if err != nil {
panic(err)
}
fmt.Println(target.GetName())
}

参考

https://developers.google.com/protocol-buffers/

https://developers.google.com/protocol-buffers/docs/tutorials