zoukankan      html  css  js  c++  java
  • Protocol Buffer学习教程之语法手册(二)

     

    1.说明

    此向导介绍如何使用protocol buffer language创建一个自己的protocolbuffer文件,包括语法与如何通过“.proto”文件生成数据访问的类,此处只介绍proto2proto3的更多消息点这里

    这是一个参考指南,一步一步功能描述的示例,请访问以下链接,并选择你自己熟悉的开发语言

    2.定义消息类型

    首先我们来看一个简单的示例,定义一个searchrequest消息格式,每一个search request有一个query字符串,页码,每页结果数量。以下是定义的“.proto”文件:

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

    消息指定了三“段”(“名-值”对),每一段,有修饰符、类型、名称、编号组成,还会有一些可选项组成,如指定缺省值呀,后续章节中会介绍到。

    段类型

    在以上的示例中,所有的段都指定了类型。你也可以用复合类型,包括枚举与其他消息类型(protobuffer 定义的类型)

    分配标记

    从以上示例中可以看到,每一段都指定了唯一编号“= x”,它用于二进制格式中标记“段”,当你的消息类型投入使用后,它们的顺序不能改变。编码时编号在1~15区间内的编号占用一个字节,在16~2047区间用两个字节,所以,你可以保留1~15的编号给那些比较常用的元素使用,并为将来可能要增加的段预留一些此区间的编号。

    最小编号为1,最大编号为229- 1,或者536,870,911,但是19000 ~ 19999区间的编号是保留给Protocol Buffers使用的。

    修饰符

    required: 一个格式完好的消息必须最少有一个这种类型的段。被这种修饰符修饰的段,是必须赋值的,否则会被认为“未初始化”,如果未赋值,在debug版本序列化时会抛出断言错误,release版本能顺利通过,但是反序列化(解析)时,必然会失败的。除此之外,requiredoptional修饰类型就没有什么区别了。

    optional: 一个格式完好的消息有N(N0)个这种类型的段。对于此字段的赋值,不是必须的。如果没有赋值,它将使用默认值,对于缺省数据类型,你可以指定默认值,如伪代码中的phone number,如果没有指定默认值,将使用系统默认值,数字类型为0string为空,bool型为false。对于嵌套类型,默认值为“默认实例”或“原型”。

    repeated: 字段会出现N(0)次,重复的值将按顺序保存在“protocol buffer”中,你只要把它当成一个动态数组即可。

    由于历史原因,repeated修饰的段的数据类型如果是数字类型的话,不能高效编码,为提高效果可以使用一个选项[packed=true]来获得更高的效率,示例如:

    repeated int32samples = 4 [packed=true];

    关于packed参见这里

    Required 是永久的,使用此种修饰符时,要特别小心,当你不想给此种类型的字段赋值的话,你需要把它改成Optional类型,它可能会出现一些问题----接受方可能会认为此消息是非完事的,而拒绝解析。有些google开发者认为required利大于弊,所以他们更喜欢使用optionalrepeated。当然,这种观点不一定是普遍的。

    定义多个消息

    可以在一个“.proto”文件定义多个消息,特别是对那些有相互关联的消息,比较适用。如你需要给以上示例的请求消息加一个响应消息

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

    关于注释

    .proto”文件注释,使用的是C/C++语法“//”,如下:

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

    保留段

    如果你要对以前定义的消息中的段删除,或者注释。将来使用者可能会更新他们的消息,并重新使用这些段,或者他们又使用此消息的旧版本,这将导致数据损坏,隐性错误等问题,有一个办法可以避免这些问题。把这个删除的段指定为reserved类型,可以通过它的标志指定,也可以通过名称(JSON版本会有问题)指定,指定后使用都如果再使用这些段,将会收到错误提醒。使用reserved时,同一行,不能混合使用标志与名称。

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

    .proto生成的内容

    通过protocol buffer 编译器对.proto文件进行编译后,能生成你选择的语言的代码。你可以通过此代码对你在.proto文件中描述的数据进行提取、给段赋值、把你打包后的数据序列化成流、把接收到的流反序列化成类实例等操作。

    C++:对应每一个.proto文件生成.h.cpp文件。每个消息将生成一个类。可以通过此链接,找到对应语言的API

    3.数据类型

    以下列表中是.proto文件中数据类型与相应的语言之间的数据类型的对应关系。

    .proto Type

    Notes

    C++ Type

    Java Type

    Python Type[2]

    Go Type

    double

    double

    double

    float

    *float64

    float

    float

    float

    float

    *float32

    int32

    Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead.

    int32

    int

    int

    *int32

    int64

    Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead.

    int64

    long

    int/long[3]

    *int64

    uint32

    Uses variable-length encoding.

    uint32

    int[1]

    int/long[3]

    *uint32

    uint64

    Uses variable-length encoding.

    uint64

    long[1]

    int/long[3]

    *uint64

    sint32

    Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s.

    int32

    int

    int

    *int32

    sint64

    Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s.

    int64

    long

    int/long[3]

    *int64

    fixed32

    Always four bytes. More efficient than uint32 if values are often greater than 228.

    uint32

    int[1]

    int

    *uint32

    fixed64

    Always eight bytes. More efficient than uint64 if values are often greater than 256.

    uint64

    long[1]

    int/long[3]

    *uint64

    sfixed32

    Always four bytes.

    int32

    int

    int

    *int32

    sfixed64

    Always eight bytes.

    int64

    long

    int/long[3]

    *int64

    bool

    bool

    boolean

    bool

    *bool

    string

    A string must always contain UTF-8 encoded or 7-bit ASCII text.

    string

    String

    str/unicode[4]

    *string

    bytes

    May contain any arbitrary sequence of bytes.

    string

    ByteString

    str

    []byte

    关于以上数据类型的编码方式的详情,点击这里

    [1]Java, unsigned 32-bit and64-bit被解释成有符合整形,最高位被描述成符号位。

    [2]对段进行赋值时,会执行类型检查。

    [3]64-bit orunsigned 32-bit 整形被在解析时都被解析成long型,可以为int型,如果在设置的时候设置成int型的话。总之,值必须与设置的时候一致。参见[2]

    [4]Pythonstrings将解析为宽字符,同时可以是ASCII,当被指定为ASCII的话(主观指定)

    4.可选字段与缺省值

    消息中的元素可以指定为optional类型,指此段可以不被赋值,在解析时,没有被赋值的段将被赋缺省值。

    缺省值可以在字段描述时指定,如给字段指定一个为10的缺省值

    optional int32 result_per_page = 3 [default = 10];

    没有指定缺省值的optional类型,解析时将被赋类型相关的缺省值。string为空,foolsfalse,numberic0enmums为枚举中的第一个值。

    5.枚举类型

    枚举类型,大家都懂了,不多说。下面是为消息加一个Corpus枚举类型,以下是示例,定义的时候的数据类型应该是Corpus,而不是整形哦。

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

    枚举常量的名称应该是唯一的,如果想要在不同的枚举中用相同的名称,则要指定一个选项allow_alias option true, 不然编译将会出错。定义如下:

    enum EnumAllowingAlias {
      option allow_alias = true;
      UNKNOWN = 0;
      STARTED = 1;
      RUNNING = 1;
    }
    enum EnumNotAllowingAlias {
      UNKNOWN = 0;
      STARTED = 1;
      // RUNNING = 1;  // Uncommenting this line will cause a compile error inside Google and a warning message outside.
    }

    个人建议采用,常量名称加枚举名称为前缀,尽量不要重名,省去一些麻烦。

    6.自定义数据类型

    大家都应该能看明白,就是消息类型中定义自定义类型,不过,他们应该在同一个”.proto”文件下,不然要引入,关于引用见下一章节。

    message SearchResponse {
      repeated Result result = 1;
    }
     
    message Result {
      required string url = 1;
      optional string title = 2;
      repeated string snippets = 3;
    }

    引用自定义类型

    不在同一个消息文件的消息,可以通过import相互引用,只要在引用的文件里,加上以下语句:

    import "myproject/other_protos.proto";

    有时候,你想把一个文件移到一个新的目录下,但是,如果你移动此文件的话,你需要修改所有与此文件相关的文件的引用路径,这可麻烦了,那怎么办呢,你可以把原路径下的文件保留,然后在原路径下的文件加上import public语法,指引所有引用此文件的文件,必须引用新文件,这样所有与旧文件相关的文件,将会自动引用新目录下的文件,示例如下:

    // new.proto
    // All definitions are moved here
    // old.proto
    // This is the proto that all clients are importing.
    import public "new.proto";//新文件的路径
    import "other.proto";
    // client.proto
    import "old.proto";
    // You use definitions from old.proto and new.proto, but not other.proto

    编译器将要在你编译指定的路径----通过-I=proto_path指定的路径下搜索引用文件,如果没有指定此参数,将在编译器所在目录下搜索。一般情况下,你需要通过-I=proto_path指定路径。如下:

    proto –I=proto文件路径 –cpp_out=proto文件目录 proto文件路径

    proto3消息类型

    可以引用proto3版本的消息类型到proto2版本,反之亦然,但是,proto2的枚举类型不适用于proto3版本的语法。

    6.嵌套类型

    消息是可以嵌套的,当一个消息需要使用另外一个消息里面的消息时,你可以加上其“父”消息域即可,示例如一:

    message SearchResponse {
      message Result {
        required string url = 1;
        optional string title = 2;
        repeated string snippets = 3;
      }
      repeated Result result = 1;
    }
    message SomeOtherMessage {
      optional SearchResponse.Result result = 1;
    }

    同时你可以进行多层嵌套,如下:

    message Outer {                  // Level 0
      message MiddleAA {  // Level 1
        message Inner {   // Level 2
          required int64 ival = 1;
          optional bool  booly = 2;
        }
      }
      message MiddleBB {  // Level 1
        message Inner {   // Level 2
          required int32 ival = 1;
          optional bool  booly = 2;
        }
      }
    }

    注意此功能已经被弃用,在定义一个新的消息时,不建议再使用它,应该使用嵌套消息来替代它。组是消息嵌套中的另一种方法,如在SearchResponse嵌套一个Result消息:

    message SearchResponse {
      repeated group Result = 1 {
        required string url = 2;
        optional string title = 3;
        repeated string snippets = 4;
      }
    }

    7.更新消息

    如果一个消息因为需求需要进行修改时,只要遵循以下规则,它就可以不影响原来的代码的基础上进行升级修改。

    不要修改已经存在的段的编号。

    在旧消息中,新增段时,最好使用optionalrepeated修饰符,这样那些旧的消息格式文件也能新版本的消息,只要required元素都被赋值了的话。应该给新增的元素加上缺省值,这样新定义的消息格式将能与旧的消息格式进行一定的交互,同理,新的消息数据能被旧的消息数据解析。

    对于旧消息来说,新增的段是不可识别的,但是新增的段并不会被丢弃,并能被序列化,如果被新的消息解析的话,它将正确解析。

    required段是可以被删除的,只要它的编号不会再被使用。如果你想对它重命名,请加上"OBSOLETE_"前缀,或者直接对这个编号设置成reserved,这样就能避免你的“.proto”文件的使用者使用它。

    一个非required字段可以转化为一个扩展(extension),反之亦然(扩展可以转化为一个非required字段),只要编号与类型不变。

    int32,uint32,int64,uint64,and bool这些类型是可以相互转换的,同时还能保证向前向后兼容。解析的时候,如果不符的话,它将像C++里的强制转换一样(64位整数,被强制转换成32)

    sint32 sint64 是相互兼容的,但是与其他类型的整形不兼容。

    stringbytes是相互兼容的,只要bytes是有效的UTF-8编码。

    嵌套消息与bytes是兼容的,只要bytes包含该消息已经编码过的版本。

    fixed32sfixed32兼容,同时ixed64sfixed64兼容。

    optionalrepeated兼容,如果一个序列化的数据串,被使用者预判为optional类型的话,如果它是缺省数据类型的话,将解析最后一个值(repeated类型可能会有很多值),如果是消息类型(自定义消息)的话,将被全部解析。

    修改一个字段的缺省值一般不会有问题的,接收者收到一个某个段没有赋值的消息时,接收者是按自己的消息版本的缺省值给它赋值,而不是发送者的版本的缺省值。

    枚举类型与int32,uint32,int64,and uint64类型是兼容的,当然如果溢出的话,它可能被截断,但是,需要注意的是,客户端(使用者,接收者)对他们会区别对待,当反序列化时,不能识别的枚举常量将被丢弃,并会得到“has…”之类的提示,并返回枚举中第一个常量给它赋值,或者是缺省值,如果有指定缺省值的话。

    8.扩展

    在消息中声明一个号段预留给第三方来定义,其他使用者可以在你指定的这个号段里定义他们自己的消息文件,同时不需要重新编译原始文件,如例:

    message Foo {
      // ...
      extensions 100 to 199;
    }

    也就是说[100, 199]之间的号段被保留为扩展所用,如例:

    extend Foo {
      optional int32 bar = 126;
    }

    也就是说消息中多了一个bar的段,当对它进行编码时,在栈格式上与你重新定义一个这样的bar字段是无异的,访问此扩展段的访问标准段的方式很类似,编译器给生成了扩展段的交互方法,给扩展段赋值(C++),如例:

    Foo foo;
    foo.SetExtension(bar,15);

    类似的,Foo类还下定义了以下接口:

    HasExtension(),ClearExtension(),GetExtension(),MutableExtension(), and AddExtension()

    关于扩展段的更多信息,请参考你选择的对应语言的代码生成手册,扩展段可以是任何数据类型的段,除oneofsmap外。

    嵌套扩展

    可以声明一个扩展,在其他消息类型里面:

    message Baz {
      extend Foo {
        optional int32 bar = 126;
      }
      ...
    }

    C++中,访问扩展示例如下:

    Foo foo;
    foo.SetExtension(Baz::bar, 15);

    换句话说,这唯一能说明的是,foo扩展定义在bar消息里面

    这一般是引起混淆的根源:声明一个扩展块,并嵌套在一个消息里,同时此消息与扩展并没有任何关系。以上示例并没有表明barzFoo的子类型,唯一只表示了bar被声明在Baz消息内,它只是一个简单的静态成员而已。

    一般常用的方法是,把扩展定义在扩展消息里面,如定义一个Baz类型的Foo扩展,如例:

    message Baz {
      extend Foo {
        optional Baz foo_ext = 127;
      }
      ...
    }

    当然,这里并没有需求,说要把一个扩展定义在某个消息类型里面,所以,你可以这样定义,如例:

    message Baz {
      ...
    }
    // This can even be in a different file.
    extend Foo {
      optional Baz foo_baz_ext = 127;
    }

    事实上这种语法可以比较完美的避免困惑,而上面相互嵌套的语法通常会让人产生他们之间有子类化的误解,特别对那些对扩展不是很熟悉的用户。

    选择扩展编号

    确保两个用户不会在同一个消息中使用相同编号来扩展消息,不然会因为数据类型不一样可能引发数据损坏。可以约定扩展的编号范围来解决这个问题(译者注:但是我也没有看懂怎么解决这个问题),如例:

    message Foo {
      extensions 1000 to max;
    }

    max229 - 1,536,870,911[19000,19999]号段是保留给ProtocolBuffers实现使用的,此号段不能用。

    9.Oneof(Union)

    Oneof其实就是C/C++中的Union共用体,当你某个消息里,有很多optional属性的段,同时他们当中同时最多只有一个需要赋值的时候,它们可以共用内存,此功能叫Oneof。可以给所有的段赋值,但是你给其中一个段赋值时,其他段的值自动被清空,你可以通过case()WhichOneof()方法来检查哪个段被赋值,取决于你使用的语言。

    Oneof用法

    以下是语法,用oneof关键字后面跟着oneof类型名,如例:

    message SampleMessage{
    oneof test_oneof {
          string name =4;
          SubMessage sub_message =9;
      }
    }

    oneof类型(test_oneof)里可以加任何数据类型的段,当然不能加任何修饰符。在你生成的代码时,oneof段有相同的getterssetters方法,同时有特定的方法用于判断哪个段被赋了值,更多关于oneof的详细资料参见这里

    Oneof功能

    oneof赋值,将清空所有其他段的值,所以当你给它赋几次值后,最后一次的值将保留

    SampleMessage message;
    message.set_name("name");
    CHECK(message.has_name());
    message.mutable_sub_message();   // Will clear name field.
    CHECK(!message.has_name());

    If the parser encounters multiple members of the same oneof onthe wire, only the last member seen is used in the parsed message.

    oneof不支持扩展

    oneof不支持repeated修饰

    反射APIsoneof有效

    如果你使用的是C++语言,请注意内存引起的冲突,如下例中的冲突是因为内存已经删除引起的。

    SampleMessage message;SubMessage* sub_message = message.mutable_sub_message();
    message.set_name("name");      // Will delete sub_message
    sub_message->set_...            // Crashes here

    ·        Again in C++, if you Swap() two messages with oneofs, each message will end up with theother’s oneof case: in the example below, msg1 will have a sub_message and msg2 will have a name.

    SampleMessage msg1;
    msg1.set_name("name");SampleMessage msg2;
    msg2.mutable_sub_message();
    msg1.swap(&msg2);
    CHECK(msg1.has_sub_message());
    CHECK(msg2.has_name());

    向后兼容问题

    增加或者删除一个oneof段需要小心,当检查到返回的值为None/NOT_SET时,它可以是oneof没有被赋值或者使用了不同版本赋值了,这是没有办法分辨的。

    编号重用问题

    当消息已经序列化或者反序列化后,在oneof中移入或者移出一些optinal段,可以丢失一些信息(某些段将被清空)

    当消息已经序列化或者反序列化后,删除或者重新恢复某些段,它可能会清除当前设置的某些段。

    ·        Split or merge oneof: This hassimilar issues to moving regular optional fields.

    10.Maps

    MapC++中的映射,以下是定义映射类型的语法:

    map<key_type, value_type> map_field = N;

    key_type可以是整数字符串类型,value_type可以为任何类型,如定义prOjects的映射表,键为string,如例:

    map<string,Project> projects =3;

    生成的APIproto2版本全支持,更详细的消息参见连接

    Maps功能

    不支持扩展

    不能被repeated,optional, or required修饰

    Wire format ordering and map iteration ordering of map values isundefined, so you cannot rely on your map items being in a particular order.(对于值与键的排序并没有定义,所以不能把你的迭代顺序依赖于此)

    When generating text format for a .proto, maps are sorted bykey. Numeric keys are sorted numerically.(通过.proto文件生成文件格式时,是按键的以数字排序)

    When parsing from the wire or when merging, if there areduplicate map keys the last key seen is used. When parsing a map from textformat, parsing will fail if there are duplicate keys.(当反序列化或者融合Map时,如果有重新的key将以最后一个为准,如果通过文件格式反序列化,如果有重复的键,将会失败)

    向后兼容

    Map也可以通过以下方法来实现,所以protobuf并不保证以后都支持map的语法:

    message MapFieldEntry{
      key_type key = 1;
      value_type value = 2;
    }
    
    repeated MapFieldEntry map_field = N;

    11.Packages(命名空间)

    package为了防止命名冲突的关键字,功能与namespace类似。如例:

    package foo.bar;
    message Open { ... }

    定义消息时,可以通过package名来指定域,如例:

    message Foo {
      ...
      required foo.bar.Open open = 1;
      ...
    }

    package的效果,依赖于你选择的语言:

    对于C++,产生的类会被包装在C++的命名空间中,如上例中的Open会被封装在 foo::bar空间中;

    对于Java,包声明符会变为java的一个包,除非在.proto文件中提供了一个明确有java_package

    对于 Python,这个包声明符是被忽略的,因为Python模块是按照其在文件系统中的位置进行组织的。

    包及名称解析

    Protocol buffer语言中类型名称的解析与C++是一致的:首先从最内部开始查找,依次向外进行,每个包会被看作是其父类包的内部类。当然对于(foo.bar.Baz)这样以“.”分隔的意味着是从最外围开始的。ProtocolBuffer编译器会解析.proto文件中定义的所有类型名。对于不同语言的代码生成器会知道如何来指向每个具体的类型,即使它们使用了不同的规则。

    12.定义服务接口

    如果你想要把你的消息用于远程调用系统,那么你可以在proto文件中定义服务接口,然后protobuf编译器可以生成服务接口代码及存根(未知其意),例如,你想要定义一个服务接口即能接受SearchRequest请求,同时能返回SearchResponse结果,你可以如此定义你的proto文件:

    service SearchService {
      rpc Search (SearchRequest) returns (SearchResponse);
    }

    proto编译器将生成一个SearchService的抽象接口与一个相应的存根实现,存根将把所有请求都指向RpcChannel,它是一个抽象接口,同时你需要对它具体实现,如序列化消息并把通过http发给另一个服务端,换句话说,它只为基于proto远程调用提供一个数据类型安排的接口,并没有提供具体的实现,在C++中,你可以如下实现:

    using google::protobuf;
    
    protobuf::RpcChannel* channel;
    protobuf::RpcController* controller;SearchService* service;SearchRequest request;SearchResponse response;voidDoSearch(){
      // You provide classes MyRpcChannel and MyRpcController, which implement
      // the abstract interfaces protobuf::RpcChannel and protobuf::RpcController.
      channel = newMyRpcChannel("somehost.example.com:1234");
      controller = newMyRpcController;
    
      // The protocol compiler generates the SearchService class based on the
      // definition given above.
      service = newSearchService::Stub(channel);
    
      // Set up the request.
      request.set_query("protocol buffers");
    
      // Execute the RPC.
      service->Search(controller, request, response, protobuf::NewCallback(&Done));
    }
    
    voidDone(){
      delete service;
      delete channel;
      delete controller;
    }

    所有service类都必须实现Service接口,它提供了一种用来调用具体方法的方式,即在编译期不需要知道方法名及它的输入、输出类型。在服务器端,通过服务注册它可以被用来实现一个RPC Server

    using google::protobuf;classExampleSearchService:publicSearchService{
     public:
      voidSearch(protobuf::RpcController* controller,
                  constSearchRequest* request,
                  SearchResponse* response,
                  protobuf::Closure*done){
        if(request->query()=="google"){
          response->add_result()->set_url("http://www.google.com");
        } elseif(request->query()=="protocol buffers"){
          response->add_result()->set_url("http://protobuf.googlecode.com");
        }
        done->Run();
      }
    };
    
    int main(){
      // You provide class MyRpcServer.  It does not have to implement any
      // particular interface; this is just an example.
      MyRpcServer server;
    
      protobuf::Service* service =newExampleSearchService;
      server.ExportOnPort(1234, service);
      server.Run();
    
      delete service;
      return0;
    }

    如果你不想把它加入到你现有的远程调用服务系统的话,你可以使用gRPC:google开发的一种跨语言跨平台的开源系统,gRPCprotocolbuffer运行的非常出色,并能通过一个专门的protocolbuffer 编译器插件,结合proto文件直接生成RPC相关的代码,但是在版本proto2proto3,在客户端与服务端之间有一些兼容性问题,所以我建议你用proto3定义gRPC服务,关于proto3的更多多语法请参考Proto3,实现gRPC,那你需要高于3.0.0()版本的protocol buffers编译器与库。

    另外,还有一些第三方团队在给RPC提供实现,下面是我们已知的链接列表:third-party add-ons wiki page

    13.选项(Options)/后续章节待翻译

    proto文件中,某些个别的声明会有很多选项,这些声明并不是用于完全改变声明的含义,而是用于影响其在特定的上下文中处理方式,完整的选项列定义在文件google/protocuf/descriptor.proto中。

    某些选项是文件级的,级别比较高,不同于具体消息、枚举或者服务定义的规范,而有些选项是消息级别的,意思是它们用于消息定义,而有些选项是段级别的,是用于定义具体的段的。选项也能用于枚举类型,枚举值,服务类型各服务方法,但是,当目前为止关没有任何一个有效的选项,能同时满足所有的类型。

    下面介绍一些常用的选项:

    java_package(文件级别):这个选项表明生成java类所在的包。如果在.proto文件中没有明确的声明java_package,就采用默认的包名。当然了,默认方式产生的 java包名并不是最好的方式,按照应用名称倒序方式进行排序的。如果不需要产生java代码,则该选项将不起任何作用。

    option java_package = "com.example.foo";

    ·        java_outer_classname (fileoption): The class name for the outermost Java class (and hence the file name)you want to generate. If no explicit java_outer_classname is specified in the .proto file, the class name will be constructed by converting the .proto file name to camel-case (so foo_bar.proto becomesFooBar.java). If not generating Java code, this option has no effect.

    option java_outer_classname = "Ponycopter";

    ·        optimize_for (fileoption): Can be set to SPEEDCODE_SIZE, or LITE_RUNTIME. This affects the C++and Java code generators (and possibly third-party generators) in the followingways:

    ·        SPEED (default):The protocol buffer compiler will generate code for serializing, parsing, andperforming other common operations on your message types. This code isextremely highly optimized.

    ·        CODE_SIZE: The protocol buffer compiler will generate minimal classes andwill rely on shared, reflection-based code to implement serialialization,parsing, and various other operations. The generated code will thus be muchsmaller than with SPEED, but operations will beslower. Classes will still implement exactly the same public API as they do in SPEED mode. This mode is most useful in apps that contain a very largenumber .proto files and do not need all of them to be blindingly fast.

    ·        LITE_RUNTIME: The protocol buffer compiler will generate classes that dependonly on the "lite" runtime library (libprotobuf-lite instead of libprotobuf). The lite runtime ismuch smaller than the full library (around an order of magnitude smaller) butomits certain features like descriptors and reflection. This is particularlyuseful for apps running on constrained platforms like mobile phones. Thecompiler will still generate fast implementations of all methods as it does in SPEED mode. Generated classes will only implement the MessageLite interface in each language, which provides only a subset of themethods of the full Message interface.

    option optimize_for = CODE_SIZE;

    ·        cc_generic_servicesjava_generic_servicespy_generic_services (file options): Whether or not the protocol buffer compilershould generate abstract service code based on services definitions in C++, Java, and Python, respectively. For legacy reasons,these default to true. However, as of version2.3.0 (January 2010), it is considered preferrable for RPC implementations toprovide code generator plugins to generate code more specific to each system, rather than relyon the "abstract" services.

    ·         // This file relies on plugins to generate service code.
    ·         option cc_generic_services = false;
    ·         option java_generic_services = false;
    option py_generic_services = false;

    ·        cc_enable_arenas (fileoption): Enables arena allocation for C++ generated code.

    ·        message_set_wire_format (message option): If set to true, the message uses adifferent binary format intended to be compatible with an old format usedinside Google called MessageSet. Users outside Googlewill probably never need to use this option. The message must be declaredexactly as follows:

    ·         message Foo {
    ·           option message_set_wire_format = true;
    ·           extensions 4 to max;
    ·         }

    ·        packed (fieldoption): If set to true on a repeated field of a basic numeric type, a more compact encoding is used. There is no downside to using this option. However,note that prior to version 2.3.0, parsers that received packed data when notexpected would ignore it. Therefore, it was not possible to change an existingfield to packed format without breaking wire compatibility. In 2.3.0 and later,this change is safe, as parsers for packable fields will always accept bothformats, but be careful if you have to deal with old programs using oldprotobuf versions.

    repeated int32 samples = 4 [packed=true];

    ·        deprecated (fieldoption): If set to true, indicates that thefield is deprecated and should not be used by new code. In most languages thishas no actual effect. In Java, this becomes a @Deprecated annotation. In the future, other language-specific codegenerators may generate deprecation annotations on the field's accessors, whichwill in turn cause a warning to be emitted when compiling code which attemptsto use the field. If the field is not used by anyone and you want to preventnew users from using it, consider replacing the field declaration with a reserved statement.

    optional int32 old_field = 6 [deprecated=true];

    Custom Options

    Protocol Buffers even allow you to define and use your ownoptions. Note that this is an advancedfeature which most people don'tneed. Since options are defined by the messages defined in google/protobuf/descriptor.proto(like FileOptions or FieldOptions), defining your own options is simply a matter of extending those messages. For example:

    import "google/protobuf/descriptor.proto";
     
    extend google.protobuf.MessageOptions {
      optional string my_option = 51234;
    }
     
    message MyMessage {
      option (my_option) = "Hello world!";
    }

    Here we have defined a new message-level option by extending MessageOptions. When we then use the option, the option name must be enclosedin parentheses to indicate that it is an extension. We can now read the valueofmy_option in C++ like so:

    string value = MyMessage::descriptor()->options().GetExtension(my_option);

    Here, MyMessage::descriptor()->options() returns the MessageOptions protocol message for MyMessage. Reading custom options from it is justlike reading any other extension.

    Similarly, in Java we would write:

    String value = MyProtoFile.MyMessage.getDescriptor().getOptions()
      .getExtension(MyProtoFile.myOption);

    In Python it would be:

    value = my_proto_file_pb2.MyMessage.DESCRIPTOR.GetOptions()
      .Extensions[my_proto_file_pb2.my_option]

    Custom options can be defined for every kind of construct in theProtocol Buffers language. Here is an example that uses every kind of option:

    import "google/protobuf/descriptor.proto";
     
    extend google.protobuf.FileOptions {
      optional string my_file_option = 50000;
    }
    extend google.protobuf.MessageOptions {
      optional int32 my_message_option = 50001;
    }
    extend google.protobuf.FieldOptions {
      optional float my_field_option = 50002;
    }
    extend google.protobuf.EnumOptions {
      optional bool my_enum_option = 50003;
    }
    extend google.protobuf.EnumValueOptions {
      optional uint32 my_enum_value_option = 50004;
    }
    extend google.protobuf.ServiceOptions {
      optional MyEnum my_service_option = 50005;
    }
    extend google.protobuf.MethodOptions {
      optional MyMessage my_method_option = 50006;
    }
     
    option (my_file_option) = "Hello world!";
     
    message MyMessage {
      option (my_message_option) = 1234;
     
      optional int32 foo = 1 [(my_field_option) = 4.5];
      optional string bar = 2;
    }
     
    enum MyEnum {
      option (my_enum_option) = true;
     
      FOO = 1 [(my_enum_value_option) = 321];
      BAR = 2;
    }
     
    message RequestType {}
    message ResponseType {}
     
    service MyService {
      option (my_service_option) = FOO;
     
      rpc MyMethod(RequestType) returns(ResponseType) {
        // Note:  my_method_option has type MyMessage.  We can set each field
        //   within it using a separate "option" line.
        option (my_method_option).foo = 567;
        option (my_method_option).bar = "Some string";
      }
    }

    Note that if you want to use a custom option in a package otherthan the one in which it was defined, you must prefix the option name with thepackage name, just as you would for type names. For example:

    // foo.proto
    import "google/protobuf/descriptor.proto";
    package foo;
    extend google.protobuf.MessageOptions {
      optional string my_option = 51234;
    }
    // bar.proto
    import "foo.proto";
    package bar;
    message MyMessage {
      option (foo.my_option) = "Hello world!";
    }

    One last thing: Since custom options are extensions, they mustbe assigned field numbers like any other field or extension. In the examplesabove, we have used field numbers in the range 50000-99999. This range isreserved for internal use within individual organizations, so you can usenumbers in this range freely for in-house applications. If you intend to usecustom options in public applications, however, then it is important that youmake sure that your field numbers are globally unique. To obtain globallyunique field numbers, please send a request to protobuf-global-extension-registry@google.com. Simplyprovide your project name (e.g. Object-C plugin) and your project website (ifavailable). Usually you only need one extension number. You can declaremultiple options with only one extension number by putting them in asub-message:

    message FooOptions {
      optional int32 opt1 = 1;
      optional string opt2 = 2;
    }
     
    extend google.protobuf.FieldOptions {
      optional FooOptions foo_options = 1234;
    }
     
    // usage:
    message Bar {
      optional int32 a = 1 [(foo_options).opt1 = 123, (foo_options).opt2 = "baz"];
      // alternative aggregate syntax (uses TextFormat):
      optional int32 b = 2 [(foo_options) = { opt1: 123 opt2: "baz" }];
    }

    Also, note that each option type (file-level, message-level,field-level, etc.) has its own number space, so e.g. you could declareextensions of FieldOptions and MessageOptions with the same number.

  • 相关阅读:
    CocoaPods初试用(1)
    iOS界面调试利器DCIntrospect
    iOS开发中sqlite3基本应用
    禁用iOS7系统自带的pop手势
    iOS7开发~Xcode5制作framework
    完美兼容iOS7自定义的AlertView
    iOS float类型比较大小
    iOS开发应用更新时,须注意事项
    Tesseract 文字识别 运用于ios7 xcode5 任意工程
    oc学习之路----内存管理
  • 原文地址:https://www.cnblogs.com/MingoJiang/p/8682015.html
Copyright © 2011-2022 走看看