Protobuf 教程
字段格式
限定修饰符① | 数据类型② | 字段名称③ | = | 字段编码值④ | [字段默认值⑤]
①.限定修饰符
包含 required\optional\repeated
required
表示是一个必须字段,必须相对于发送方,在发送消息之前必须设置该字段的值,对于接收方,必须能够识别该字段的意思。发送之前没有设置required字段或者无法识别required字段都会引发编解码异常,导致消息被丢弃。
Optional
表示是一个可选字段,可选对于发送方,在发送消息时,可以有选择性的设置或者不设置该字段的值。对于接收方,如果能够识别可选字段就进行相应的处理,如果无法识别,则忽略该字段,消息中的其它字段正常处理。---因为optional字段的特性,很多接口在升级版本中都把后来添加的字段都统一的设置为optional字段,这样老的版本无需升级程序也可以正常的与新的软件进行通信,只不过新的字段无法识别而已,因为并不是每个节点都需要新的功能,因此可以做到按需升级和平滑过渡。
Repeated
表示该字段可以包含0~N个元素。其特性和optional一样,但是每一次可以包含多个值。可以看作是在传递一个数组的值。
②. 数据类型
Protobuf定义了一套基本数据类型。几乎都可以映射到Go\C++\Java等语言的基础数据类型.
- bool 布尔类型 1字节 bool
- double 64位浮点数 N double
- float 32为浮点数 N float
- int32 32位整数、 N int
- uin32 无符号32位整数 N unsigned int
- int64 64位整数 N __int64
- uint64 64为无符号整 N unsigned __int64
- sint32 32位整数,处理负数效率更高 N int32
- sing64 64位整数 处理负数效率更高 N __int64
- fixed32 32位无符号整数 4 unsigned int32
- fixed64 64位无符号整数 8 unsigned __int64
- sfixed32 32位整数、能以更高的效率处理负数 4 unsigned int32
- sfixed64 64为整数 8 unsigned __int64
- string 只能处理 ASCII字符 N std::string
- bytes 用于处理多字节的语言字符、如中文 N std::string
- enum 可以包含一个用户自定义的枚举类型uint32 N(uint32) enum
- message 可以包含一个用户自定义的消息类型 N object of class
N 表示打包的字节并不是固定。而是根据数据的大小或者长度。 例如int32,如果数值比较小,在0~127时,使用一个字节打包。 关于枚举的打包方式和uint32相同。 关于message,类似于C语言中的结构包含另外一个结构作为数据成员一样。 关于 fixed32 和int32的区别。fixed32的打包效率比int32的效率高,但是使用的空间一般比int32多。因此一个属于时间效率高,一个属于空间效率高。根据项目的实际情况,一般选择fixed32,如果遇到对传输数据量要求比较苛刻的环境,可以选择int32.
③.字段名称
字段名称的命名与Go、C++、Java等语言的变量命名方式几乎是相同的。
- protobuf建议字段的命名采用以下划线分割的驼峰式。例如 first_name 而不是firstName.
④.字段编码值
有了该值,通信双方才能互相识别对方的字段。当然相同的编码值,其限定修饰符和数据类型必须相同。 编码值的取值范围为 1
2^32(4294967296)。 其中 115的编码时间和空间效率都是最高的,编码值越大,其编码的时间和空间效率就越低(相对于1-15),当然一般情况下相邻的2个值编码效率的是相同的,除非2个值恰好实在4字节,12字节,20字节等的临界区。比如15和16. 1900~2000编码值为Google protobuf 系统内部保留值,建议不要在自己的项目中使用。 protobuf 还建议把经常要传递的值把其字段编码设置为1-15之间的值。 消息中的字段的编码值无需连续,只要是合法的,并且不能在同一个消息中有字段包含相同的编码值。
建议:项目投入运营以后涉及到版本升级时的新增消息字段全部使用optional或者repeated,尽量不实用required。如果使用了required,需要全网统一升级,如果使用optional或者repeated可以平滑升级。
⑤.默认值。
当在传递数据时,对于required数据类型,如果用户没有设置值,则使用默认值传递到对端。当接受数据是,对于optional字段,如果没有接收到optional字段,则设置为默认值。
import
protobuf 接口文件可以像C语言的h文件一个,分离为多个,在需要的时候通过 import导入需要对文件。其行为和C语言的#include或者java的import的行为大致相同。
package
避免名称冲突,可以给每个文件指定一个package名称,对于java解析为java中的包。对于C++则解析为名称空间。
message
支持嵌套消息,消息可以包含另一个消息作为其字段。也可以在消息内定义一个新的消息。 类似于java中的嵌套class或者golang中的嵌套结构体。
enum
枚举的定义和Java 相同,但是有一些限制。
- 枚举值必须大于等于0的整数。
- 使用分号(;)分隔枚举变量而不是Java 语言中的逗号(,)
- 每一个枚举值定义都会与一个常量映射,而这些常量的第一个常量必须为0。
服务定义
如果想要将消息类型用在RPC(远程方法调用)系统中,可以在.proto文件中定义一个RPC服务接口,protocol buffer编译器将会根据所选择的不同语言生成服务接口代码及存根。如,想要定义一个RPC服务并具有一个方法,该方法能够接收 SearchRequest并返回一个SearchResponse,此时可以在.proto文件中进行如下定义:
service SearchService {
rpc Search (SearchRequest) returns (SearchResponse);
}
额外选项
在定义.proto文件时能够标注一系列的options。Options并不改变整个文件声明的含义,但却能够影响特定环境下处理方式。完整的可用选项可以在google/protobuf/descriptor.proto找到。
一些选项是文件级别的,意味着它可以作用于最外范围,不包含在任何消息内部、enum或服务定义中。一些选项是消息级别的,意味着它可以用在消息定义的内部。当然有些选项可以作用在域、enum类型、enum值、服务类型及服务方法中。到目前为止,并没有一种有效的选项能作用于所有的类型。
option go_package = "api";
- 这个选项表明生成java\go类所在的包
option go_outer_classname= "HelloService";
- 该选项表明想要生成Java\go类的名称,如果在.proto文件中没有明确的go_outer_classname定义,生成的class名称将会根据.proto文件的名称采用驼峰式的命名方式进行生成。如(hello_service.proto生成的java\go类名为HelloService.java),如果不生成java\go代码,则该选项不起任何作用。
示例
// 指定使用proto3协议; 否则使用proto2
syntax = "proto3";
// 定义消息的命名空间
package pb;
// 导入Any类型
import "google/protobuf/any.proto";
// java_xx表示生成java代码需要的几个属性
// java_package: 指定生成java的包名
option java_package = "com.sy.common.pojo";
// java_outer_classname: 生成java的类型,注意不能与message中定义的名称重名
option java_outer_classname = "MyResp";
// 用message定义一个消息(message可以理解为定义一个结构体的意思), 名为result
message Result {
// 定义一个int类型的变量,变量名为code,
// 赋值为1表示的是这个变量的唯一编号,序列化的时候会用这个编号替代变量名
// 注意:编号1~15,编码时占1字节。16~2047编码时占两个字节。编号19000~19999为保留编号,不能用。
int32 code = 1;
// 定义一个string类型的变量,名为msg, 唯一编号为2
string msg = 2;
// 定义一个Any类型(Any表示泛型,也可以理解为java的Object类型)的变量,名为data,唯一编号为3
// 定义成Any类型的好处时,赋值的时候可以给data赋任意类型的值
google.protobuf.Any data = 3;
}
// 定义一个Student类型的消息
message Student {
int32 id = 1;
string name = 2;
// repeated Book表示 List<Book>的意思
// 定义一个List类型的字段,名为book,编号为3
repeated Book book = 3;
// map<type1, type2>
// 定义一个map列席的字段,名为attr,编号为4
map<string, string> attr = 4;
// 定义一个子类型Book, 其中包含id,name两个字段
message Book {
int32 id = 1;
string name = 2;
}
}