1.protobuf简介
Protobuf是Protocol Buffers的简称,它是Google公司开发的一种数据描述语言,是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。目前提供了 C++、Java、Python 三种语言的 API。
Protobuf刚开源时的定位类似于XML、JSON等数据描述语言,通过附带工具生成代码并实现将结构化数据序列化的功能。这里我们更关注的是Protobuf作为接口规范的描述语言,可以作为设计安全的跨语言PRC接口的基础 工具。
需要了解两点
- protobuf是类似与json一样的数据描述语言(数据格式)
- protobuf非常适合于RPC数据交换格式
接着我们来看一下protobuf的优势和劣势:
优势:
1:序列化后体积相比Json和XML很小,适合网络传输
2:支持跨平台多语言
3:消息格式升级和兼容性还不错
4:序列化反序列化速度很快,快于Json的处理速度
劣势:
1:应用不够广(相比xml和json)
2:二进制格式导致可读性差
3:缺乏自描述
2.protobuf的安装
-
下载protobuf
方法一:===> git clone https://github.com/protocolbuffers/protobuf.git 方法二:===> 或者将准备好的压缩包进行拖入 解压到$GOPATH/src/github.com/protocolbuffers/下面 Unzip protobuf.zip -
安装(ubuntu)
(1)安装依赖工具(联网) $ sudo apt-get install autoconf automake libtool curl make g++ unzip libffi-dev -y (2)进入protobuf文件 cd protobuf/ (3)进行安装检测 并生成自动安装脚本 ./autogen.sh ./configure (4)进行编译C代码 make (5)进行安装 sudo make install (6)刷新linux共享库关系 sudo ldconfig go get git clone go install -
测试protobuf编译工具
protoc -h如果正常输出 相关指令 没有报任何error,为安装成功
-
安装protobuf的go语言插件
由于protobuf并没直接支持go语言需要我们手动安装相关插件
(1)下载 方法一:===> go get -v -u github.com/golang/protobuf/proto 方法二:===>或者将 github.com-golang-protobuf.zip拖入 进行解压到 $GOPATH/src/github.com/golang (2)进入到文件夹内进行编译 $ cd $GOPATH/src/github.com/golang/protobuf/protoc-gen-go $ go build (3)将生成的 protoc-gen-go可执行文件,放在/bin目录下 $ sudo cp protoc-gen-go /bin/ (4)尝试补齐protoc-gen-go 如果可以补齐代表成功,如果执行不报错 代表工具成功
3.protobuf简单语法
首先让我们看一个非常简单的例子。
syntax = "proto3"; //指定版本信息,不指定会报错
package pb; //后期生成go文件的包名
//message为关键字,作用为定义一种消息类型
message Person{
// 名字
string name = 1;
// 年龄
int32 age = 2 ;
}
enum test{
int32 age = 0;
}
- protobuf消息的定义(或者称为描述)通常都写在一个以 .proto 结尾的文件中。
- 该文件的第一行指定正在使用
proto3语法:如果不这样做,协议缓冲区编译器将假定正在使用proto2。这也必须是文件的第一个非空的非注释行。- 第二行package指明当前是pb包(生成go文件之后和Go的包名保持一致)
- 最后message关键字定义一个Person消息体,类似于go语言中的结构体,是包含一系列类型数据的集合。许多标准的简单数据类型都可以作为字段类型,包括
bool,int32,float,double,和string。也可以使用其他message类型作为字段类型。- 在message中有一个字符串类型的value成员,该成员编码时用1代替名字。我们知道,在json中是通过成员的名字来绑定对应的数据,但是Protobuf编码却是通过成员的唯一编号来绑定对应的数据,因此Protobuf编码后数据的体积会比较小,能够快速传输,缺点是不利于阅读。
message的格式说明
消息由至少一个字段组合而成,类似于Go语言中的结构体,每个字段都有一定的格式:
- 唯一的编号标签:代表每个字段的一个唯一的编号标签,在同一个消息里不可以重复。这些编号标签用与在消息二进制格式中标识你的字段,并且消息一旦定义就不能更改。需要说明的是标签在1到15范围的采用一个字节进行编码,所以通常将标签1到15用于频繁发生的消息字段。编号标签大小的范围是1到2的29次。19000-19999是官方预留的值,不能使用。
- 注释格式:向.proto文件添加注释,可以使用C/C++/java/Go风格的双斜杠(//) 语法格式或者
/*.....*/
**message常见的数据类型与go中类型对比 **
| .proto类型 | Go类型 | 介绍 |
|---|---|---|
| double | float64 | 64位浮点数 |
| float | float32 | 32位浮点数 |
| int32 | int32 | 使用可变长度编码。编码负数效率低下——如果你的字段可能有负值,请改用sint32。 |
| int64 | int64 | 使用可变长度编码。编码负数效率低下——如果你的字段可能有负值,请改用sint64。 |
| uint32 | uint32 | 使用可变长度编码。 |
| uint64 | uint64 | 使用可变长度编码。 |
| sint32 | int32 | 使用可变长度编码。符号整型值。这些比常规int32s编码负数更有效。 |
| sint64 | int64 | 使用可变长度编码。符号整型值。这些比常规int64s编码负数更有效。 |
| fixed32 | uint32 | 总是四字节。如果值通常大于228,则比uint 32更有效 |
| fixed64 | uint64 | 总是八字节。如果值通常大于256,则比uint64更有效 |
| sfixed32 | int32 | 总是四字节。 |
| sfixed64 | int64 | 总是八字节。 |
| bool | bool | 布尔类型 |
| string | string | 字符串必须始终包含UTF - 8编码或7位ASCII文本 |
| bytes | []byte | 可以包含任意字节序列 |
3.protobuf高级用法
protobuf除了上面的简单类型还有一些复杂的用法,如下:
message嵌套
messsage除了能放简单数据类型外,还能存放另外的message类型,如下:
syntax = "proto3"; //指定版本信息,不指定会报错
package pb; //后期生成go文件的包名
//message为关键字,作用为定义一种消息类型
message Person{
// 名字
string name = 1;
// 年龄
int32 age = 2 ;
//定义一个message
message PhoneNumber {
string number = 1;
int64 type = 2;
}
PhoneNumber phone = 3;
}
repeated关键字
repeadted关键字类似与go中的切片,编译之后对应的也是go的切片,用法如下:
syntax = "proto3"; //指定版本信息,不指定会报错
package pb; //后期生成go文件的包名
//message为关键字,作用为定义一种消息类型
message Person{
// 名字
string name = 1;
// 年龄
int32 age = 2 ;
//定义一个message
message PhoneNumber {
string number = 1;
int64 type = 2;
}
repeated PhoneNumber phone = 3;
}
默认值
解析数据时,如果编码的消息不包含特定的单数元素,则解析对象对象中的相应字段将设置为该字段的默认值。不同类型的默认值不同,具体如下:
- 对于字符串,默认值为空字符串。
- 对于字节,默认值为空字节。
- 对于bools,默认值为false。
- 对于数字类型,默认值为零。
- 对于枚举,默认值是第一个定义的枚举值,该值必须为0。
- repeated字段默认值是空列表
- message字段的默认值为空对象
enum关键字
在定义消息类型时,可能会希望其中一个字段有一个预定义的值列表。比如说,电话号码字段有个类型,这个类型可以是,home,work,mobile。我们可以通过enum在消息定义中添加每个可能值的常量来非常简单的执行此操作。实例如下:
syntax = "proto3"; //指定版本信息,不指定会报错
package pb; //后期生成go文件的包名
//message为关键字,作用为定义一种消息类型
message Person{
// 名字
string name = 1;
// 年龄
int32 age = 2 ;
//定义一个message
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}
repeated PhoneNumber phone = 3;
}
//enum为关键字,作用为定义一种枚举类型
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
如上,enum的第一个常量映射为0,每个枚举定义必须包含一个映射到零的常量作为其第一个元素。这是因为:
- 必须有一个零值,以便我们可以使用0作为数字默认值。
- 零值必须是第一个元素,以便与proto2语义兼容,其中第一个枚举值始终是默认值。
enum还可以为不同的枚举常量指定相同的值来定义别名。如果想要使用这个功能必须讲allow_alias选项设置为true,负责编译器将报错。示例如下:
syntax = "proto3"; //指定版本信息,不指定会报错
package pb; //后期生成go文件的包名
//message为关键字,作用为定义一种消息类型
message Person{
// 名字
string name = 1;
// 年龄
int32 age = 2 ;
//定义一个message
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}
repeated PhoneNumber phone = 3;
}
//enum为关键字,作用为定义一种枚举类型
enum PhoneType {
//如果不设置将报错
option allow_alias = true;
MOBILE = 0;
HOME = 1;
WORK = 2;
Personal = 2;
}
oneof关键字
如果有一个包含许多字段的消息,并且最多只能同时设置其中的一个字段,则可以使用oneof功能,示例如下:
message Person{
// 名字
string name = 1;
// 年龄
int32 age = 2 ;
//定义一个message
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}
repeated PhoneNumber phone = 3;
oneof data{
string school = 5;
int32 score = 6;
}
}
定义RPC服务
如果需要将message与RPC一起使用,则可以在.proto文件中定义RPC服务接口,protobuf编译器将根据你选择的语言生成RPC接口代码。示例如下:
//定义RPC服务
service HelloService {
rpc Hello (Person)returns (Person);
}
大部分protobuf的语法就是这样,其他想学习的可以参考官方文档,语法写完之后,让我们编译一下,然后通过代码试用一下。
4.protobuf基本编译
protobuf编译是通过编译器protoc进行的,通过这个编译器,我们可以把.proto文件生成go,Java,Python,C++, Ruby, JavaNano, Objective-C,或者C# 代码,生成命令如下:
protoc --proto_path=IMPORT_PATH --go_out=DST_DIR path/to/file.proto
- --proto_path=IMPORT_PATH,IMPORT_PATH是 .proto 文件所在的路径,如果忽略则默认当前目录。如果有多个目录则可以多次调用--proto_path,它们将会顺序的被访问并执行导入。
- --go_out=DST_DIR, 指定了生成的go语言代码文件放入的文件夹
- 允许使用
protoc --go_out=./ *.proto的方式一次性编译多个 .proto 文件- go语言编译时,protobuf 编译器会把 .proto 文件编译成 .pd.go 文件
一般在使用的时候我们都是使用下面这种简单的命令:
protoc --go_out=./ *.proto
编译当前文件夹下的所有.proto文件,并把生成的go文件放置在当前文件夹下。
message被对应的编译成go语言中的结构体,同时还为String类型自动生成了一组方法,其中ProtoMessage方法表示这是一个实现了proto.Message接口的方法。此外Protobuf还为每个成员生成了一个Get方法,Get方法不仅可以处理空指针类型,而且可以和Proto2的方法保持一致.
然后我们给这个.proto文件中添加一个RPC服务,再次进行编译,发现生成的go文件没有发生变化。这是因为世界上的RPC实现有很多种,protoc编译器并不知道该如何为HelloService服务生成代码。不过在protoc-gen-go内部已经集成了一个叫grpc的插件,可以针对grpc生成代码:
protoc --go_out=plugins=grpc:. *.proto