Protobuf 3 语法规则

目录

proto3语法

定义一个 Message

定义多个 message 类型

定义变量类型

分配Tag

指定变量规则

注释

保留变量不被使用

默认值

定义枚举 Enumerations


Protocol Buffers 是 google 的一种数据交换的格式,它独立于语言,独立于平台。提供了多种语言的实现:java、c#、c++、go 和 python,每一种实现都包含了相应语言的编译器以及库文件。由于它是一种二进制的格式,比使用 xml 进行数据交换快许多。

本文主要介绍proto3的使用语法。

proto3语法

定义一个 Message

首先我们来定义一个 Search 请求,在这个请求里面,我们需要给服务端发送三个信息:

  • query:查询条件
  • page_number:你想要哪一页数据
  • result_per_page:每一页有多少条数据

于是我们可以这样定义:

// 指定使用proto3,如果不指定的话,编译器会使用proto2去编译
syntax = "proto3";

message SearchRequests {
    // 定义SearchRequests的成员变量,需要指定:变量类型、变量名、变量Tag
    string query = 1;
    int32 page_number = 2;
    int32 result_per_page = 3;
}

定义多个 message 类型

一个 proto 文件可以定义多个 message ,比如我们可以在刚才那个 proto 文件中把服务端返回的消息结构也一起定义:

message SearchRequest {
    string query = 1;
    int32 page_number = 2;
    int32 result_per_page = 3;
}

message SearchResponse {
    repeated string result = 1;
}

message 可以嵌套定义,比如 message 可以定义在另一个 message 内部

message SearchResponse {
    message Result {
        string url = 1;
        string title = 2;
        repeated string snippets = 3;
    }
    repeated Result results = 1;
}

定义在 message 内部的 message 可以这样使用:

message SomeOtherMessage {
    SearchResponse.Result result = 1;
}

定义变量类型

在刚才的例子之中,我们使用了2个标准值类型: string 和 int32,除了这些标准类型之外,变量的类型还可以是复杂类型,比如自定义的枚举和自定义的 message

这里我们把标准类型列举一下protobuf内置的标准类型以及跟各平台对应的关系:

.proto说明C++JavaPythonGoRubyC#PHP
doubledoubledoublefloatfloat64Floatdoublefloat
floatfloatfloatfloatfloat32Floatfloatfloat
int32使用变长编码,对负数编码效率低,如果你的变量可能是负数,可以使用sint32int32intintint32Fixnum or Bignum (as required)intinteger
int64使用变长编码,对负数编码效率低,如果你的变量可能是负数,可以使用sint64int64longint/longint64Bignumlonginteger/string
uint32使用变长编码uint32intint/longuint32Fixnum or Bignum (as required)uintinteger
uint64使用变长编码uint64longint/longuint64Bignumulonginteger/string
sint32使用变长编码,带符号的int类型,对负数编码比int32高效int32intintint32Fixnum or Bignum (as required)intinteger
sint64使用变长编码,带符号的int类型,对负数编码比int64高效int64longint/longint64Bignumlonginteger/string
fixed324字节编码, 如果变量经常大于 的话,会比uint64高效uint64longint/longuint64Bignumulonginteger/string
sfixed324字节编码int32intintint32Fixnum or Bignum (as required)intinteger
sfixed648字节编码int64longint/longint64Bignumlonginteger/string
boolboolbooleanboolboolTrueClass/FalseClassboolboolean
string必须包含utf-8编码或者7-bit ASCII textstringStringstr/unicodestringString (UTF-8)stringstring
bytes任意的字节序列stringByteStringstr[]byteString (ASCII-8BIT)ByteStringstring

分配Tag

每一个变量在message内都需要自定义一个唯一的数字Tag,protobuf会根据Tag从数据中查找变量对应的位置,具体原理跟protobuf的二进制数据格式有关。Tag一旦指定,以后更新协议的时候也不能修改,否则无法对旧版本兼容。

Tag的取值范围最小是1,最大是-1,但 19000~19999 是 protobuf 预留的,用户不能使用。

虽然 Tag 的定义范围比较大,但不同 Tag 也会对 protobuf 编码带来一些影响:

  • 1 ~ 15:单字节编码
  • 16 ~ 2047:双字节编码

使用频率高的变量最好设置为1 ~ 15,这样可以减少编码后的数据大小,但由于Tag一旦指定不能修改,所以为了以后扩展,也记得为未来保留一些 1 ~ 15 的 Tag

指定变量规则

在 proto3 中,可以给变量指定以下两个规则:

  • singular:0或者1个,但不能多于1个
  • repeated:任意数量(包括0)

当构建 message 的时候,build 数据的时候,会检测设置的数据跟规则是否匹配

在proto2中,规则为:

  • required:必须有一个
  • optional:0或者1个
  • repeated:任意数量(包括0)

注释

//表示注释开头,如

message SearchRequest {
    string query = 1;
    int32 page_number = 2; // Which page number do we want
    int32 result_per_page = 3; // Number of results to return per page
}

保留变量不被使用

上面我们说到,一旦 Tag 指定后就不能变更,这就会带来一个问题,假如在版本1的协议中,我们有个变量:

int32 number = 1;

在版本2中,我们决定废弃对它的使用,那我们应该如何修改协议呢?注释掉它?删除掉它?如果把它删除了,后来者很可能在定义新变量的时候,使新的变量 Tag = 1 ,这样会导致协议不兼容。那有没有办法规避这个问题呢?我们可以用 reserved 关键字,当一个变量不再使用的时候,我们可以把它的变量名或 Tag 用 reserved 标注,这样,当这个 Tag 或者变量名字被重新使用的时候,编译器会报错

message Foo {
    // 注意,同一个 reserved 语句不能同时包含变量名和 Tag 
    reserved 2, 15, 9 to 11;
    reserved "foo", "bar";
}

默认值

当解析 message 时,如果被编码的 message 里没有包含某些变量,那么根据类型不同,他们会有不同的默认值:

  • string:默认是空的字符串
  • byte:默认是空的bytes
  • bool:默认为false
  • numeric:默认为0
  • enums:定义在第一位的枚举值,也就是0
  • messages:根据生成的不同语言有不同的表现,参考generated code guide

注意,收到数据后反序列化后,对于标准值类型的数据,比如bool,如果它的值是 false,那么我们无法判断这个值是对方设置的,还是对方压根就没给这个变量设置值。

定义枚举 Enumerations

在 protobuf 中,我们也可以定义枚举,并且使用该枚举类型,比如:

message SearchRequest {
    string query = 1;
    int32 page_number = 2; // Which page number do we want
    int32 result_per_page = 3; // Number of results to return per page
    enum Corpus {
        UNIVERSAL = 0;
        WEB = 1;
        IMAGES = 2;
        LOCAL = 3;
    }
    Corpus corpus = 4;
}

枚举定义在一个消息内部或消息外部都是可以的,如果枚举是 定义在 message 内部,而其他 message 又想使用,那么可以通过 MessageType.EnumType 的方式引用。定义枚举的时候,我们要保证第一个枚举值必须是0,枚举值不能重复,除非使用 option allow_alias = true 选项来开启别名。如:

enum EnumAllowingAlias {
    option allow_alias = true;
    UNKNOWN = 0;
    STARTED = 1;
    RUNNING = 1;
}

枚举值的范围是32-bit integer,但因为枚举值使用变长编码,所以不推荐使用负数作为枚举值,因为这会带来效率问题。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程大玩家

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值