Protobuf语言指南

Posted yangykaifa

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Protobuf语言指南相关的知识,希望对你有一定的参考价值。

本指南描写叙述了怎样使用protocolbuffer语言来构造你的protocol buffer数据,包括.proto文件语法以及怎样生成.proto文件的数据訪问类。

本文是一个參考指南——假设要查看怎样使用本文中描写叙述的多个特性的循序渐进的样例,请在http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/tutorials.html中查找须要的语言的教程。

定义一个消息类型

先来看一个非常easy的样例。

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

message SearchRequest {

  required string query = 1;

  optional int32 page_number = 2;

  optional int32 result_per_page = 3;

}

SearchRequest消息格式有3个字段,在消息中承载的数据分别相应于每一个字段。当中每一个字段都有一个名字和一种类型。

  • ? 指定字段类型

在上面的样例中。全部字段都是标量类型:两个整型(page_number和result_per_page),一个string类型(query)。当然,你也能够为字段指定其它的合成类型,包括枚举(enumerations)或其它消息类型。

  • ? 分配标识号

正如上述文件格式,在消息定义中,每一个字段都有唯一的一个标识符。这些标识符是用来在消息的二进制格式中识别各个字段的。一旦開始使用就不能够再改 变。注:[1,15]之内的标识号在编码的时候会占用一个字节。

[16,2047]之内的标识号则占用2个字节。所以应该为那些频繁出现的消息元素保留 [1,15]之内的标识号。切记:要为将来有可能加入的、频繁出现的标识号预留一些标识号。

最小的标识号能够从1開始,最大到229 - 1, or 536,870,911。不能够使用当中的[19000-19999]的标识号。 Protobuf协议实现中对这些进行了预留。假设非要在.proto文件里使用这些预留标识号,编译时就会报警。

  • ? 指定字段规则

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

required:一个格式良好的消息一定要含有1个这样的字段。表示该值是必须要设置的;

optional:消息格式中该字段能够有0个或1个值(不超过1个)。

repeated:在一个格式良好的消息中,这样的字段能够反复随意多次(包括0次)。反复的值的顺序会被保留。表示该值能够反复。相当于java中的List。

由于一些历史原因,基本数值类型的repeated的字段并没有被尽可能地高效编码。

在新的代码中,用户应该使用特殊选项[packed=true]来保证更高效的编码。

如:

repeated int32 samples = 4 [packed=true]; 

required是永久性的:在将一个字段标识为required的时候,应该特别小心。假设在某些情况下不想写入或者发送一个required的 字段,将原始该字段修饰符更改为optional可能会遇到问题——旧版本号的使用者会觉得不含该字段的消息是不完整的。从而可能会无目的的拒绝解析。

在这 种情况下。你应该考虑编写特别针对于应用程序的、自己定义的消息校验函数。Google的一些工程师得出了一个结论:使用required弊多于利。他们更 愿意使用optional和repeated而不是required。当然,这个观点并不具有普遍性。

  • ? 加入很多其它消息类型

在一个.proto文件里能够定义多个消息类型。在定义多个相关的消息的时候,这一点特别实用——比如,假设想定义与SearchResponse消息类型相应的回复消息格式的话,你能够将它加入到同样的.proto文件里,如:

message SearchRequest {

  required string query = 1;

  optional int32 page_number = 2;

  optional int32 result_per_page = 3;

}

message SearchResponse {

 …

}
  • ? 加入凝视

向.proto文件加入凝视,能够使用C/C++/java风格的双斜杠(//) 语法格式。如:

message SearchRequest {

  required string query = 1;

  optional int32 page_number = 2;// 终于返回的页数

  optional int32 result_per_page = 3;// 每页返回的结果数

}
  • ? 从.proto文件生成了什么?

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

2 对C++来说。编译器会为每一个.proto文件生成一个.h文件和一个.cc文件。.proto文件里的每一个消息有一个相应的类。

2 对Java来说,编译器为每一个消息类型生成了一个.java文件,以及一个特殊的Builder类(该类是用来创建消息类接口的)。

2
对Python来说。有点不太一样——Python编译器为.proto文件里的每一个消息类型生成一个含有静态描写叙述符的模块。,该模块与一个元类(metaclass)在执行时(runtime)被用来创建所需的Python数据訪问类。

你能够从例如以下的文档链接中获取每种语言很多其它API。http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/reference/overview.html

标量数值类型

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

技术分享
技术分享

可能包括随意顺序的字节数据。

你能够在文章http://code.google.com/apis/protocolbuffers/docs/encoding.html 中,找到很多其它“序列化消息时各种类型怎样编码”的信息。

Optional的字段和默认值

如上所述,消息描写叙述中的一个元素能够被标记为“可选的”(optional)。一个格式良好的消息能够包括0个或一个optional的元素。当解 析消息时,假设它不包括optional的元素值。那么解析出来的对象中的相应字段就被置为默认值。默认值能够在消息描写叙述文件里指定。比如。要为 SearchRequest消息的result_per_page字段指定默认值10,在定义消息格式时例如以下所看到的:

optional int32 result_per_page = 3 [default = 10];

假设没有为optional的元素指定默认值,就会使用与特定类型相关的默认值:对string来说,默认值是空字符串。对bool来说,默认值是false。对数值类型来说。默认值是0。对枚举来说,默认值是枚举类型定义中的第一个值。

枚举

当须要定义一个消息类型的时候,可能想为一个字段指定某“提前定义值序列”中的一个值。比如,假设要为每一个SearchRequest消息加入一个 corpus字段,而corpus的值可能是UNIVERSAL,WEB,IMAGES。LOCAL。NEWS,PRODUCTS或VIDEO中的一个。 其实能够非常easy地实现这一点:通过向消息定义中加入一个枚举(enum)就能够了。一个enum类型的字段仅仅能用指定的常量集中的一个值作为其值(假设尝 试指定不同的值,解析器就会把它当作一个未知的字段来对待)。

在以下的样例中。在消息格式中加入了一个叫做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];

}

枚举常量必须在32位整型值的范围内。由于enum值是使用可变编码方式的,对负数不够高效。因此不推荐在enum中使用负数。如上例所看到的。能够在 一个消息定义的内部或外部定义枚举——这些枚举能够在.proto文件里的不论什么消息定义里重用。当然也能够在一个消息中声明一个枚举类型,而在还有一个不同 的消息中使用它——採用MessageType.EnumType的语法格式。

当对一个使用了枚举的.proto文件执行protocol buffer编译器的时候,生成的代码中将有一个相应的enum(对Java或C++来说),或者一个特殊的EnumDescriptor类(对 Python来说)。它被用来在执行时生成的类中创建一系列的整型值符号常量(symbolic constants)。

关于怎样在你的应用程序的消息中使用枚举的很多其它信息,请查看所选择的语言http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/reference/overview.html

使用其它消息类型

你能够将其它消息类型用作字段类型。比如,假设在每一个SearchResponse消息中包括Result消息,此时能够在同样的.proto文件里定义一个Result消息类型,然后在SearchResponse消息中指定一个Result类型的字段,如:

message SearchResponse {

  repeated Result result = 1;

}

message Result {

  required string url = 1;

  optional string title = 2;

  repeated string snippets = 3;

}
  • ? 导入定义

在上面的样例中。Result消息类型与SearchResponse是定义在同一文件里的。

假设想要使用的消息类型已经在其它.proto文件里已经定义过了呢?

你能够通过导入(importing)其它.proto文件里的定义来使用它们。要导入其它.proto文件的定义,你须要在你的文件里加入一个导入声明。如:

import “myproject/other_protos.proto”;
protocol编译器就会在一系列文件夹中查找须要被导入的文件。这些文件夹通过protocol编译器的命令行參数-I/–import_path指定。假设不提供參数,编译器就在其调用文件夹下查找。

嵌套类型

你能够在其它消息类型中定义、使用消息类型,在以下的样例中。Result消息就定义在SearchResponse消息内,如:

message SearchResponse {

  message Result {

    required string url = 1;

    optional string title = 2;

    repeated string snippets = 3;

  }

  repeated Result result = 1;

}

假设你想在它的父消息类型的外部重用这个消息类型,你须要以Parent.Type的形式使用它,如:

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;

  }

}

一个“组”仅仅是简单地将一个嵌套消息类型和一个字段捆绑到一个单独的声明中。在代码中。能够把它看成是含有一个Result类型、名叫result的字段的消息(后面的名字被转换成了小写。所以它不会与前面的冲突)。

因此。除了传输数据格式不同之外,这个样例与上面的SearchResponse样例是全然等价的。

更新一个消息类型

假设一个已有的消息格式已无法满足新的需求——如,要在消息中加入一个额外的字段——可是同一时候旧版本号写的代码仍然可用。

不用操心!

更新消息而不破坏已有代码是非常easy的。在更新时仅仅要记住以下的规则就可以。

  1. 不要更改不论什么已有的字段的数值标识。

  2. 所加入的不论什么字段都必须是optional或repeated的。这就意味着不论什么使用“旧”的消息格式的代码序列化的消息能够被新的代码所解析。由于它们 不会丢掉不论什么required的元素。

    应该为这些元素设置合理的默认值,这样新的代码就能够正确地与老代码生成的消息交互了。相似地,新的代码创建的消息 也能被老的代码解析:老的二进制程序在解析的时候仅仅是简单地将新字段忽略。

    然而,未知的字段是没有被抛弃的。

    此后,假设消息被序列化。未知的字段会随之中的一个 起被序列化——所以,假设消息传到了新代码那里,则新的字段仍然可用。注意:对Python来说,对未知字段的保留策略是无效的。

  3. 非required的字段能够移除——仅仅要它们的标识号在新的消息类型中不再使用(更好的做法可能是重命名那个字段,比如在字段前加入“OBSOLETE_”前缀,那样的话,使用的.proto文件的用户将来就不会无意中又一次使用了那些不该使用的标识号)。

  4. 一个非required的字段能够转换为一个扩展,反之亦然——仅仅要它的类型和标识号保持不变。

  5. int32, uint32, int64, uint64,和bool是全部兼容的,这意味着能够将这些类型中的一个转换为另外一个。而不会破坏向前、 向后的兼容性。假设解析出来的数字与相应的类型不相符。那么结果就像在C++中对它进行了强制类型转换一样(比如。假设把一个64位数字当作int32来 读取,那么它就会被截断为32位的数字)。

  6. sint32和sint64是互相兼容的,可是它们与其它整数类型不兼容。

  7. string和bytes是兼容的——仅仅要bytes是有效的UTF-8编码。

  8. 嵌套消息与bytes是兼容的——仅仅要bytes包括该消息的一个编码过的版本号。

  9. fixed32与sfixed32是兼容的,fixed64与sfixed64是兼容的。

扩展

通过扩展。能够将一个范围内的字段标识号声明为可被第三方扩展所用。

然后,其它人就能够在他们自己的.proto文件里为该消息类型声明新的字段,而不必去编辑原始文件了。看个详细样例:

message Foo {

  // …

  extensions 100 to 199;

}

这个样例表明:在消息Foo中,范围[100,199]之内的字段标识号被保留为扩展用。如今。其它人就能够在他们自己的.proto文件里加入新字段到Foo里了,可是加入的字段标识号要在指定的范围内——比如:

extend Foo {

  optional int32 bar = 126;

}

这个样例表明:消息Foo如今有一个名为bar的optional int32字段。

当用户的Foo消息被编码的时候。数据的传输格式与用户在Foo里定义新字段的效果是全然一样的。

然而,要在程序代码中訪问扩展字段的方法与訪问普通的字段稍有不同——生成的数据訪问代码为扩展准备了特殊的訪问函数来訪问它。比如,以下是怎样在C++中设置bar的值:

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

相似地,Foo类也定义了模板函数 HasExtension(),ClearExtension()。GetExtension(),MutableExtension()。以及 AddExtension()。这些函数的语义都与相应的普通字段的訪问函数相符。要查看很多其它使用扩展的信息,请參考相应语言的代码生成指南。注:扩展可 以是不论什么字段类型,包括消息类型。

嵌套的扩展

能够在还有一个类型的范围内声明扩展。如:

message Baz {

  extend Foo {

    optional int32 bar = 126;

  }

  …

}

在此例中。訪问此扩展的C++代码例如以下:

Foo foo;

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

一个通常的设计模式就是:在扩展的字段类型的范围内定义该扩展——比如,以下是一个Foo的扩展(该扩展是Baz类型的),当中。扩展被定义为了Baz的一部分:

message Baz {

  extend Foo {

    optional Baz foo_ext = 127;

  }

  …

}

然而,并没有强制要求一个消息类型的扩展一定要定义在那个消息中。

也能够这样做:

message Baz {

  …

}
extend Foo {

  optional Baz foo_baz_ext = 127;

}

其实,这样的语法格式更能防止引起混淆。正如上面所提到的。嵌套的语法通常被错误地觉得有子类化的关系——尤其是对那些还不熟悉扩展的用户来说。

  • ? 选择可扩展的标符号

在同一个消息类型中一定要确保两个用户不会扩展新增同样的标识号,否则可能会导致数据的不一致。

能够通过为新项目定义一个可扩展标识号规则来防止该情况的发生。

假设标识号须要非常大的数量时,能够将该可扩展标符号的范围扩大至max。当中max是229 - 1, 或536,870,911。

例如以下所看到的:

message Foo {

  extensions 1000 to max;

}

通常情况下在选择标符号时,标识号产生的规则中应该避开[19000-19999]之间的数字,由于这些已经被Protocol Buffers实现中预留了。

包(Package)

当然能够为.proto文件新增一个可选的package声明符。用来防止不同的消息类型有命名冲突。如:

package foo.bar;

message Open { ... }

在其它的消息格式定义中能够使用包名+消息名的方式来定义域的类型。如:

message Foo {

  ...

  required foo.bar.Open open = 1;

  ...

}

包的声明符会依据使用语言的不同影响生成的代码。

对于C++,产生的类会被包装在C++的命名空间中,如上例中的Open会被封装在 foo::bar空间中。对于Java,包声明符会变为java的一个包,除非在.proto文件里提供了一个明白有java_package;对于 Python,这个包声明符是被忽略的,由于Python模块是依照其在文件系统中的位置进行组织的。

  • ? 包及名称的解析

Protocol buffer语言中类型名称的解析与C++是一致的:首先从最内部開始查找。依次向外进行,每一个包会被看作是其父类包的内部类。

当然对于 (foo.bar.Baz)这样以“.”分隔的意味着是从最外围開始的。ProtocolBuffer编译器会解析.proto文件里定义的全部类型名。

对于不同语言的代码生成器会知道怎样来指向每一个详细的类型,即使它们使用了不同的规则。

定义服务(Service)

假设想要将消息类型用在RPC(远程方法调用)系统中。能够在.proto文件里定义一个RPC服务接口,protocol buffer编译器将会依据所选择的不同语言生成服务接口代码及存根。

如,想要定义一个RPC服务并具有一个方法,该方法能够接收 SearchRequest并返回一个SearchResponse,此时能够在.proto文件里进行例如以下定义:

service SearchService {

  rpc Search (SearchRequest) returns (SearchResponse);

}

protocol编译器将产生一个抽象接口SearchService以及一个相应的存根实现。

存根将全部的调用指向RpcChannel,它是一 个抽象接口,必须在RPC系统中对该接口进行实现。如。能够实现RpcChannel以完毕序列化消息并通过HTTP方式来发送到一个服务器。换句话说, 产生的存根提供了一个类型安全的接口用来完毕基于protocolbuffer的RPC调用,而不是将你限定在一个特定的RPC的实现中。

C++中的代码 例如以下所看到的:

using google::protobuf;

protobuf::RpcChannel* channel;
protobuf::RpcController* controller;
SearchService* service;
SearchRequest request;
SearchResponse response;

void DoSearch() {
  // You provide classes MyRpcChannel and MyRpcController, which implement
  // the abstract interfaces protobuf::RpcChannel and protobuf::RpcController.
  channel = new MyRpcChannel("somehost.example.com:1234");
  controller = new MyRpcController;


// The protocol compiler generates the SearchService class based on the
  // definition given above.


service = new SearchService::Stub(channel);
  // Set up the request.
  request.set_query("protocol buffers");

  // Execute the RPC.
  service->Search(controller, request, response, protobuf::NewCallback(&Done));
}

void Done() {
  delete service;
  delete channel;
  delete controller;
}

全部service类都必须实现Service接口,它提供了一种用来调用详细方法的方式。即在编译期不须要知道方法名及它的输入、输出类型。在服务器端。通过服务注冊它能够被用来实现一个RPC Server。

using google::protobuf;

class ExampleSearchService : public SearchService {
 public:
  void Search(protobuf::RpcController* controller,
              const SearchRequest* request,
              SearchResponse* response,
              protobuf::Closure* done) {
    if (request->query() == "google") {
      response->add_result()->set_url("http://www.google.com");
    } else if (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 = new ExampleSearchService;
  server.ExportOnPort(1234, service);
  server.Run();

  delete service;
  return 0;
}

选项(Options)

在定义.proto文件时能够标注一系列的options。

Options并不改变整个文件声明的含义。但却能够影响特定环境下处理方式。完整的可用选项能够在google/protobuf/descriptor.proto找到。

一些选项是文件级别的,意味着它能够作用于最外范围,不包括在不论什么消息内部、enum或服务定义中。一些选项是消息级别的,意味着它能够用在消息定 义的内部。当然有些选项能够作用在域、enum类型、enum值、服务类型及服务方法中。

到眼下为止。并没有一种有效的选项能作用于全部的类型。

例如以下就是一些经常使用的选择:

  1. java_package (file option): 这个选项表明生成java类所在的包。

    假设在.proto文件里没有明白的声明java_package,就採用默认的包名。当然了。默认方式产生的 java包名并非最好的方式。依照顾用名称倒序方式进行排序的。假设不须要产生java代码。则该选项将不起不论什么作用。如:

option java_package = "com.example.foo";
  1. java_outer_classname (file option): 该选项表明想要生成Java类的名称。

    假设在.proto文件里没有明白的java_outer_classname定义,生成的class名称将会依据.proto文件的名称採用驼峰式的命名方式进行生成。如(foo_bar.proto生成的java类名为FooBar.java),假设不生成java代码,则该选项不起不论什么作用。如:

option java_outer_classname = "Ponycopter";
  1. optimize_for (fileoption): 能够被设置为 SPEED, CODE_SIZE,or LITE_RUNTIME。这些值将通过例如以下的方式影响C++及java代码的生成:

· SPEED (default): protocol buffer编译器将通过在消息类型上执行序列化、语法分析及其它通用的操作。这样的代码是最优的。

· CODE_SIZE: protocol buffer编译器将会产生最少量的类,通过共享或基于反射的代码来实现序列化、语法分析及各种其它操作。採用该方式产生的代码将比SPEED要少得多。 可是操作要相对慢些。当然实现的类及其对外的API与SPEED模式都是一样的。这样的方式经经常使用在一些包括大量的.proto文件并且并不盲目追求速度的 应用中。

· LITE_RUNTIME: protocol buffer编译器依赖于执行时核心类库来生成代码(即採用libprotobuf-lite 替代libprotobuf)。

这样的核心类库由于忽略了一 些描写叙述符及反射。要比全类库小得多。这样的模式经常在移动手机平台应用多一些。编译器採用该模式产生的方法实现与SPEED模式不相上下。产生的类通过实现 MessageLite接口,但它仅仅是Messager接口的一个子集。

option optimize_for = CODE_SIZE;

2 cc_generic_services, java_generic_services, py_generic_services (file options): 在C++、java、python中protocol buffer编译器是否应该基于服务定义产生抽象服务代码。由于历史遗留问题。该值默认是true。可是自2.3.0版本号以来,它被觉得通过提供代码生成 器插件来对RPC实现更可取,而不是依赖于“抽象”服务。

// This file relies on plugins to generate service code.

option cc_generic_services = false;

option java_generic_services = false;

option py_generic_services = false;

2 message_set_wire_format (message option):假设该值被设置为true,该消息将使用一种不同的二进制格式来与Google内部的MessageSet的老格式相兼容。对于Google外部的用户来说,该选项将不会被用到。例如以下所看到的:

message Foo {

  option message_set_wire_format = true;

  extensions 4 to max;

}

2 packed (field option): 假设该选项在一个整型基本类型上被设置为真,则採用更紧凑的编码方式。当然使用该值并不会对数值造成不论什么损失。

在2.3.0版本号之前,解析器将会忽略那些 非期望的包装值。

因此,它不可能在不破坏现有框架的兼容性上而改变压缩格式。在2.3.0之后,这样的改变将是安全的,解析器能够接受上述两种格式。可是在 处理protobuf老版本号程序时。还是要多留意一下。

repeated int32 samples = 4 [packed=true];

2 deprecated (field option): 假设该选项被设置为true。表明该字段已经被弃用了,在新代码中不建议使用。在多数语言中。这并没有实际的含义。在java中。它将会变成一个 @Deprecated凝视。或许在将来。其它基于语言声明的代码在生成时也会如此使用,当使用该字段时。编译器将自己主动报警。

如:

optional int32 old_field = 6 [deprecated=true];
  • ? 自己定义选项

ProtocolBuffers同意自己定义并使用选项。

该功能应该属于一个高级特性,对于大部分人是用不到的。由于options是定在 google/protobuf/descriptor.proto中的,因此你能够在该文件里进行扩展,定义自己的选项。如:

import "google/protobuf/descriptor.proto";



extend google.protobuf.MessageOptions {

  optional string my_option = 51234;

}



message MyMessage {

  option (my_option) = "Hello world!";

}

在上述代码中。通过对MessageOptions进行扩展定义了一个新的消息级别的选项。当使用该选项时,选项的名称须要使用()包裹起来。以表明它是一个扩展。

在C++代码中能够看出my_option是以例如以下方式被读取的。

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

在Java代码中的读取方式例如以下:

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

正如上面的读取方式,定制选项对于Python并不支持。定制选项在protocol buffer语言中可用于不论什么结构。以下就是一些详细的样例:

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";

  }

}

注:假设要在该选项定义之外使用一个自己定义的选项,必须要由包名 + 选项名来定义该选项。如:

// 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!";

}

最后一件事情须要注意:由于自己定义选项是可扩展的,它必须象其它的域或扩展一样来定义标识号。正如上述演示样例,[50000-99999]已经被占 用,该范围内的值已经被内部所使用,当然了你能够在内部应用中随意使用。

假设你想在一些公共应用中进行自己定义选项,你必须确保它是全局唯一的。能够通过[email protected]来获取全局唯一标识号。

生成訪问类

能够通过定义好的.proto文件来生成Java、Python、C++代码。须要基于.proto文件执行protocol buffer编译器protoc。执行的命令例如以下所看到的:

protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR path/to/file.proto

· IMPORT_PATH声明了一个.proto文件所在的详细文件夹。假设忽略该值。则使用当前文件夹。假设有多个文件夹则能够 对–proto_path 写多次。它们将会顺序的被訪问并执行导入。-I=IMPORT_PATH是它的简化形式。

· 当然也能够提供一个或多个输出路径:

o –cpp_out 在目标文件夹DST_DIR中产生C++代码,能够在 http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/reference /cpp-generated.html中查看很多其它。

o –java_out 在目标文件夹DST_DIR中产生Java代码。能够在 http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/reference /java-generated.html中查看很多其它。

o –python_out 在目标文件夹 DST_DIR 中产生Python代码。能够在http://code.google.com/intl/zh-CN/apis/protocolbuffers /docs/reference/python-generated.html中查看很多其它。

 作为一种额外的使得,假设DST_DIR 是以.zip或.jar结尾的,编译器将输出结果打包成一个zip格式的归档文件。.jar将会输出一个 Java JAR声明必须的manifest文件。注:假设该输出归档文件已经存在,它将会被重写,编译器并没有做到足够的智能来为已经存在的归档文件加入新的文 件。

· 你必须提供一个或多个.proto文件作为输入。多个.proto文件能够一次全部声明。

尽管这些文件是相对于当前文件夹来命名的,每一个文件必须在一个IMPORT_PATH中。仅仅有如此编译器才干够决定它的标准名称。

from:http://www.open-open.com/home/space.php?uid=37924&do=blog&id=5873

=========================================================

ProtoBuf开发人员指南:http://gashero.yeax.com/?p=108

官方:http://code.google.com/p/protobuf/

语言指南
http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/proto.html

风格
http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/style.html






以上是关于Protobuf语言指南的主要内容,如果未能解决你的问题,请参考以下文章

Protobuf语言指南——.proto文件语法详解

Protobuf 协议语言指南

Protobuf3语言指南

gRPCProtoBuf 语言快速学习指南

在Go中使用Protobuf

等价于 protobuf 中的 #ifdef 语法