Protobuf 教程

1,170 阅读8分钟

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.

④.字段编码值

有了该值,通信双方才能互相识别对方的字段。当然相同的编码值,其限定修饰符和数据类型必须相同。 编码值的取值范围为 12^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;
    }
}