Go+Python双语言混合开发 第三部分 Go开发学习 第5章 protobuf3和grpc进阶 学习笔记

Go+Python双语言混合开发 第三部分 Go开发学习 第5章 protobuf3和grpc进阶 学习笔记

第5章 protobuf3和grpc进阶

protobuf 官方文档参考

官方地址: https://developers.google.com/protocol-buffers/docs/proto3

5.1 定义一个消息类型

先来看一个非常简单的例子。假设你想定义一个“搜索请求”的消息格式,每一个请求含有一个查询字符串、你感兴趣的查询结果所在的页数,以及每一页多少条查询结果。可以采用如下的方式来定义消息类型的.proto文件了:

syntax = "proto3";
message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}
  • 文件的第一行指定了你正在使用proto3语法:如果你没有指定这个,编译器会使用proto2。这个指定语法行必须是文件的非空非注释的第一个行。
  • SearchRequest消息格式有3个字段,在消息中承载的数据分别对应于每一个字段。其中每个字段都有一个名字和一种类型。

指定字段类型

在上面的例子中,所有字段都是标量类型:两个整型(page_number和result_per_page),一个string类型(query)。当然,你也可以为字段指定其他的合成类型,包括枚举(enumerations)或其他消息类型。

分配标识号

正如你所见,在消息定义中,每个字段都有唯一的一个数字标识符。这些标识符是用来在消息的二进制格式中识别各个字段的,一旦开始使用就不能够再改变。注:[1,15]之内的标识号在编码的时候会占用一个字节。[16,2047]之内的标识号则占用2个字节。所以应该为那些频繁出现的消息元素保留 [1,15]之内的标识号。切记:要为将来有可能添加的、频繁出现的标识号预留一些标识号。

最小的标识号可以从1开始,最大到2^29 - 1, or 536,870,911。不可以使用其中的[19000-19999]( (从FieldDescriptor::kFirstReservedNumber 到 FieldDescriptor::kLastReservedNumber))的标识号, Protobuf协议实现中对这些进行了预留。如果非要在.proto文件中使用这些预留标识号,编译时就会报警。同样你也不能使用早期保留的标识号。

指定字段规则

所指定的消息字段修饰符必须是如下之一:

  • singular:一个格式良好的消息应该有0个或者1个这种字段(但是不能超过1个)。
  • repeated:在一个格式良好的消息中,这种字段可以重复任意多次(包括0次)。重复的值的顺序会被保留。
    在proto3中,repeated的标量域默认情况虾使用packed。
    你可以了解更多的pakced属性在Protocol Buffer 编码

添加更多消息类型

在一个.proto文件中可以定义多个消息类型。在定义多个相关的消息的时候,这一点特别有用——例如,如果想定义与SearchResponse消息类型对应的回复消息格式的话,你可以将它添加到相同的.proto文件中,如:

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

添加注释

向.proto文件添加注释,可以使用C/C++/java风格的双斜杠(//) 语法格式,如:

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.
}

保留标识符(Reserved)

如果你通过删除或者注释所有域,以后的用户可以重用标识号当你重新更新类型的时候。如果你使用旧版本加载相同的.proto文件这会导致严重的问题,包括数据损坏、隐私错误等等。现在有一种确保不会发生这种情况的方法就是指定保留标识符(and/or names, which can also cause issues for JSON serialization不明白什么意思),protocol buffer的编译器会警告未来尝试使用这些域标识符的用户。

message Foo {
  reserved 2, 15, 9 to 11;
  reserved "foo", "bar";
}

注:不要在同一行reserved声明中同时声明域名字和标识号

从.proto文件生成了什么?

当用protocol buffer编译器来运行.proto文件时,编译器将生成所选择语言的代码,这些代码可以操作在.proto文件中定义的消息类型,包括获取、设置字段值,将消息序列化到一个输出流中,以及从一个输入流中解析消息。

  • 对C++来说,编译器会为每个.proto文件生成一个.h文件和一个.cc文件,.proto文件中的每一个消息有一个对应的类。
  • 对Java来说,编译器为每一个消息类型生成了一个.java文件,以及一个特殊的Builder类(该类是用来创建消息类接口的)。
  • 对Python来说,有点不太一样——Python编译器为.proto文件中的每个消息类型生成一个含有静态描述符的模块,,该模块与一个元类(metaclass)在运行时(runtime)被用来创建所需的Python数据访问类。
  • 对go来说,编译器会位每个消息类型生成了一个.pd.go文件。
  • 对于Ruby来说,编译器会为每个消息类型生成了一个.rb文件。
  • javaNano来说,编译器输出类似域java但是没有Builder类
  • 对于Objective-C来说,编译器会为每个消息类型生成了一个pbobjc.h文件和pbobjcm文件,.proto文件中的每一个消息有一个对应的类。
  • 对于C#来说,编译器会为每个消息类型生成了一个.cs文件,.proto文件中的每一个消息有一个对应的类。

你可以从如下的文档链接中获取每种语言更多API。API Reference

5.2 标量数值类型

一个标量消息字段可以含有一个如下的类型——该表格展示了定义于.proto文件中的类型,以及与之对应的、在自动生成的访问类中定义的类型:

.proto Type Notes Python Type Go Type
double float float64
float float float32
int32 使用变长编码,对于负值的效率很低,如果你的域有可能有负值,请使用sint64替代 int int32
uint32 使用变长编码 int uint32
uint64 使用变长编码 int uint64
sint32 使用变长编码,这些编码在负值时比int32高效的多 int int32
sint64 使用变长编码,有符号的整型值。编码时比通常的int64高效。 int int64
fixed32 总是4个字节,如果数值总是比总是比228大的话,这个类型会比uint32高效。 int uint32
fixed64 总是8个字节,如果数值总是比总是比256大的话,这个类型会比uint64高效。 int uint64
sfixed32 总是4个字节 int int32
sfixed64 总是8个字节 int int64
bool bool bool
string 一个字符串必须是UTF-8编码或者7-bit ASCII编码的文本。 str string
bytes 可能包含任意顺序的字节数据。 str []byte

你可以在文章Protocol Buffer 编码中,找到更多“序列化消息时各种类型如何编码”的信息。

  1. 在java中,无符号32位和64位整型被表示成他们的整型对应形似,最高位被储存在标志位中。
  2. 对于所有的情况,设定值会执行类型检查以确保此值是有效。
  3. 64位或者无符号32位整型在解码时被表示成为ilong,但是在设置时可以使用int型值设定,在所有的情况下,值必须符合其设置其类型的要求。
  4. python中string被表示成在解码时表示成unicode。但是一个ASCIIstring可以被表示成str类型。
  5. Integer在64位的机器上使用,string在32位机器上使用

5.3 默认值

当一个消息被解析的时候,如果被编码的信息不包含一个特定的singular元素,被解析的对象锁对应的域被设置位一个默认值,对于不同类型指定如下:

  • 对于strings,默认是一个空string
  • 对于bytes,默认是一个空的bytes
  • 对于bools,默认是false
  • 对于数值类型,默认是0
  • 对于枚举,默认是第一个定义的枚举值,必须为0;
  • 对于消息类型(message),域没有被设置,确切的消息是根据语言确定的,详见generated code guide
    对于可重复域的默认值是空(通常情况下是对应语言中空列表)。
    注:对于标量消息域,一旦消息被解析,就无法判断域释放被设置为默认值(例如,例如boolean值是否被设置为false)还是根本没有被设置。你应该在定义你的消息类型时非常注意。例如,比如你不应该定义boolean的默认值false作为任何行为的触发方式。也应该注意如果一个标量消息域被设置为标志位,这个值不应该被序列化传输。
    查看generated code guide选择你的语言的默认值的工作细节。

5.4 枚举

当需要定义一个消息类型的时候,可能想为一个字段指定某“预定义值序列”中的一个值。例如,假设要为每一个SearchRequest消息添加一个 corpus字段,而corpus的值可能是UNIVERSAL,WEB,IMAGES,LOCAL,NEWS,PRODUCTS或VIDEO中的一个。 其实可以很容易地实现这一点:通过向消息定义中添加一个枚举(enum)并且为每个可能的值定义一个常量就可以了。

在下面的例子中,在消息格式中添加了一个叫做Corpus的枚举类型——它含有所有可能的值 ——以及一个类型为Corpus的字段:

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
  enum Corpus {
    UNIVERSAL = 0;
    WEB = 1;
    IMAGES = 2;
    LOCAL = 3;
    NEWS = 4;
    PRODUCTS = 5;
    VIDEO = 6;
  }
  Corpus corpus = 4;
}

如你所见,Corpus枚举的第一个常量映射为0:每个枚举类型必须将其第一个类型映射为0,这是因为:

  • 必须有有一个0值,我们可以用这个0值作为默认值。
  • 这个零值必须为第一个元素,为了兼容proto2语义,枚举类的第一个值总是默认值。
    你可以通过将不同的枚举常量指定位相同的值。如果这样做你需要将allow_alias设定位true,否则编译器会在别名的地方产生一个错误信息。

Go+Python双语言混合开发 第三部分 Go开发学习 第5章 protobuf3和grpc进阶 学习笔记
Go+Python双语言混合开发 第三部分 Go开发学习 第5章 protobuf3和grpc进阶 学习笔记
Go+Python双语言混合开发 第三部分 Go开发学习 第5章 protobuf3和grpc进阶 学习笔记
Go+Python双语言混合开发 第三部分 Go开发学习 第5章 protobuf3和grpc进阶 学习笔记
Go+Python双语言混合开发 第三部分 Go开发学习 第5章 protobuf3和grpc进阶 学习笔记
Go+Python双语言混合开发 第三部分 Go开发学习 第5章 protobuf3和grpc进阶 学习笔记
Go+Python双语言混合开发 第三部分 Go开发学习 第5章 protobuf3和grpc进阶 学习笔记
Go+Python双语言混合开发
最后附上视频下载地址
官网地址:https://coding.imooc.com/class/469.html
百度网盘下载地址:
链接: https://pan.baidu.com/s/1z54qp2yO-2RYyJrZ_Ovgbg 密码: 6dl0
–来自百度网盘超级会员V3的分享

上一篇:5个步骤搞定protoc环境安装


下一篇:Go+Python双语言混合开发 第三部分 Go开发学习 第4章 grpc入门 学习笔记