本文介绍了如何使用ProtocolBuffers语言构造protocolbuffer数据,包括.proto文件语法以及如何从.proto文件生成数据访问类。它涵盖了ProtocolBuffers语言的proto2版本。本文只是一个参考指南,以后会有Java语言教程。如何使用本指南?工作中遇到,可以通过查询关键词找到自己需要的知识点。定义消息类型首先,我们从一个简单的示例开始,假设我们要构建一个搜索请求消息格式。每条搜索消息包括三个参数:查询字符串指定的页码和结果数下面是这个.proto消息对应的消息格式SearchRequest{requiredstringquery=1;可选的int32page_number=2;optionalint32result_per_page=3;}SearchRequest消息定义指定了三个字段(也称为名称或键值对),每个字段都有一个名称和一个类型。指定字段类型在上面的例子中,可以看到有两种类型:两种整数(页码和每页的结果数),和字符串(查询条件)。当然,我们也可以将字段定义为复合类型,包括枚举和其他消息类型。分配字段编号在上面的例子中,可以看到每个字段都有一个唯一的编号,用于在二进制消息中标识我们的字段,相当于字段的别名。1到15范围内的字段用一个字节表示。16~2047范围内的字段用两个字节表示。因此,为了提高性能,我们尽量将出现频率很高的字段保持在1~15的范围内。最小的数是1,最大的数是2^29-1,即536,870,911。请注意,数字19000到19999不能使用,因为它们是为ProtocolBuffers实现保留的-如果在.proto中使用这些保留数字之一,ProtocolBuffers编译器会报错。同一条消息中的数字不能重复,这一点也需要注意。指定字段规则required:简单理解为必填字段,个数为1。optional:可选,个数不超过1。repeated:修改的字段可以重复任意次数,包括0次。重复值的顺序也被记录下来。由于历史原因,标量数字类型(例如int32、int64、枚举)的重复字段没有像它们应该的那样有效地编码。新代码应该使用特殊选项[packed=true]来提高编码效率。例如:repeatedint32samples=4[packed=true];重复的ProtoEnum结果=5[packed=true];packed是压缩字段,后续文章会解释。required也意味着永远,所以我们在设置字段的时候,需要尽可能的考虑字段的范围。添加更多消息类型我们可以在一个.proto文件中设置多种消息类型。messageSearchRequest{要求的字符串查询=1;可选的int32page_number=2;可选int32result_per_page=3;}messageSearchResponse{...}虽然可以在单个.proto文件中定义多种消息类型(例如message、enum和service),但在定义大量具有不同依赖项的消息时也会导致依赖项膨胀单个文件。官方建议在每个.proto文件中包含尽可能少的消息类型,但这个值并没有给出具体范围,需要根据实际情况评估。Reservedfields当我们更新消息结构,删除某个字段或者注释掉某个字段时,以后的用户可以使用我们删除的字段对应的编号,这是没有问题的。但是一旦后期加载.proto修改前的旧版本,就会因为数字冲突出现问题,导致数据混乱,所以可以引入保留字段。消息Foo{保留2、15、9到11;reserved"foo","bar";}保留字段编号的范围包括:2,15,9,10,11,也可以用9到max来保留后面的所有编号。注意字段名和字段号不能在同一个保留语句中混用。.proto文件自动生成了什么?对于java,编译器生成一个.java文件,其中包含每个消息类型的类,以及用于创建消息类实例的特殊Builder类。类型比较表。原型类型描述java类型doubledoublefloatfloatint32使用可变长度编码。编码负数效率低下-如果您的字段可以有负值,请改用sint32。intint64使用可变长度编码。编码负数效率低下-如果您的字段可以有负值,请改用sint64。longuint32使用可变长度编码。intuint64使用可变长度编码。longsint32使用可变长度编码。一个带符号的int值。这些编码负数比常规int32更有效。intsint64使用可变长度编码。一个带符号的int值。这些编码负数比常规int64更有效。longfixed32总是4个字节。如果值通常大于2^28,则比uint32更有效。intfixed64总是8个字节。如果值通常大于2^56,则比uint64更有效。longsfixed32始终为4个字节。intsfixed64总是8个字节。longboolbooleanstring必须始终包含UTF-8编码的文本。Stringbytes可以包含任意字节序列。ByteString可选字段和默认值当一个字段设置为可选时,我们的消息可能包含也可能不包含该字段。我们可以为不包含的可选字段设置默认值。可选int32result_per_page=3[默认=10];如果未指定默认值,则为系统默认值。string为空字符串,bool为false,integer为0,枚举为枚举类型的第一个值。因此,在设置枚举类型时,要特别注意。枚举我们可以在消息内部定义一个枚举,并为它设置一个数字,比如我们设置一个语料库枚举。messageSearchRequest{要求的字符串查询=1;可选的int32page_number=2;可选int32result_per_page=3[默认=10];枚举语料库{UNIVERSAL=0;网络=1;图像=2;本地=3;新闻=4;=5;视频=6;}可选语料库语料库=4[默认=通用];}在同一个枚举中。如果我们想让多个枚举值对应一个数字,我们可以使用别名。在下面的例子中,上面的例子不会有问题,但是下面的例子会有错误提示。注意:如果枚举名称不同,但枚举值相同,也会报错,但如果在不同的消息中,则不会报错。enumEnumAllowingAlias{optionallow_alias=true;未知=0;开始=1;RUNNING=1;}enumEnumNotAllowingAlias{未知=0;开始=1;//运行=1;//取消注释这一行会导致谷歌内部出现编译错误,外部会出现警告信息。}使用其他消息类型您可以使用其他消息类型作为字段类型。例如,假设您想在每个SearchResponse消息中包含一个Result消息——为此,您可以在同一个.proto中定义一个Result消息类型,然后在SearchResponse中指定一个Result类型的字段:messageSearchResponse{repeated结果result=1;}messageResult{要求的字符串url=1;可选字符串标题=2;repeatedstringsnippets=3;}import定义当我们需要在另一个.proto文件中使用消息时,我们可以导入其他.proto文件定义来使用它们。要导入另一个.proto的定义,请在文件顶部添加导入语句:import"myproject/other_protos.proto";默认情况下,我们只能使用直接导入的.proto文件中的定义。但是,有时我们可能需要将.proto文件移动到新位置。我们可以在旧位置放置一个占位符.proto文件,而不是直接移动.proto文件并在一次更改中更新所有调用站点,以使用导入通用文件的概念将所有导入转发到新位置。//new.proto//所有定义都移到这里//old.proto//这是所有客户端正在导入的原型。importpublic"new.proto";import"other.proto";//client.protoimport"old.proto";//你使用old.proto和new.proto的定义,但不使用other.proto导入包含importpublic语句的proto的任何代码都可以传递依赖于importpublic依赖项。嵌套类型我们可以在其他消息类型中定义和使用消息类型,如下例所示-这里的结果消息在SearchResponse消息中定义:messageSearchResponse{messageResult{requiredstringurl=1;可选字符串标题=2;重复的字符串片段=3;}repeatedResultresult=1;}如果需要在其他消息中引用result,需要指定result外围类(父类)的编号。messageSomeOtherMessage{optionalSearchResponse.Resultresult=1;}您可以根据需要嵌套消息。在下面的示例中,请注意两个名为Inner的嵌套类型是完全独立的,因为它们在不同的消息中定义:messageOuter{//Level0messageMiddleAA{//Level1messageInner{//Level2optionalint64ival=1;可选布尔booly=2;}}messageMiddleBB{//Level1messageInner{//Level2optionalstringname=1;可选布尔标志=2;}}}updatemessageif现有的消息类型不再满足我们所有的需求——例如,我们希望消息格式有一个额外的字段——但是我们想使用之前用旧格式创建的代码,不用担心!在不破坏任何现有代码的情况下更新消息类型非常简单。请记住以下规则:不要更改任何现有字段的字段编号。您添加的任何新字段都应该是可选的或重复的。这意味着任何由使用“旧”消息格式的代码序列化的消息都可以被新生成的代码解析,因为它们不会丢失任何必需的元素。您应该为这些元素设置合理的默认值,以便新代码可以正确地与旧代码生成的消息进行交互。同样,新代码创建的消息可以被旧代码解析:旧二进制文件在解析时忽略新字段。但是,未知字段不会被丢弃,如果消息稍后被序列化,未知字段也会随之序列化——所以如果消息被传递给新代码,新字段仍然可用。如果更新的消息类型中不再使用某些字段编号,则可以删除非必填字段。您可能想要重命名该字段,也许添加前缀“OBSOLETE_”,或者保留字段编号,以便您的.proto的未来用户不会意外地重复使用该编号。只要类型和编号保持不变,非必填字段就可以转换为扩展(扩展将在后面讨论),反之亦然。int32、uint32、int64、uint64和bool都是兼容的——这意味着您可以将字段从这些类型中的一种更改为另一种,而不会破坏向前或向后兼容性。sint32和sint64彼此兼容,但与其他整数类型不兼容。只要字节采用有效的UTF-8编码格式,字符串和字节就可以兼容。fixed32与sfixed32兼容,fixed64与sfixed64兼容。可选与字符串、字节和消息字段的重复兼容。给定一个重复字段的序列化数据作为输入,如果它是原始类型字段,则期望此字段是可选的客户端将采用最后一个输入值,或者如果它是消息类型字段元素,则合并所有输入。请注意,这对于数字类型(包括布尔值和枚举)通常是不安全的。numeric类型的重复字段可以序列化为packed(packedlater)格式,当需要可选字段时,将无法正确解析。更改默认值通常没问题,但请记住,默认值永远不会通过网络发送。因此,如果某个程序收到一条消息,指出某个特定字段未设置,则该程序将看到在该程序的协议版本中定义的默认值。它不会看到发件人代码中定义的默认值。尽量不要修改枚举值,否则会出现一些奇怪的问题。在map
