Protobuf 语言指南(proto3)

Protobuf 语言指南(proto3)

Protocol Buffer是Google的语言中立的,平台中立的,可扩展机制的,用于序列化结构化数据 - 对比XML,但更小,更快,更简单。您能够定义数据的结构化,而后可使用特殊生成的源代码轻松地在各类数据流中使用各类语言编写和读取结构化数据。php

定义消息类型

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

1 syntax = "proto3";
2 
3 message SearchRequest {
4   string query = 1;
5   int32 page_number = 2;
6   int32 result_per_page = 3;
7 }
  • 该文件的第一行指定您正在使用proto3语法:若是您不这样作,protobuf 编译器将假定您正在使用proto2。这必须是文件的第一个非空的非注释行。java

  • 所述SearchRequest消息定义了三个字段(名称/值对),对应着我须要的消息内容。每一个字段都有一个名称和类型。python

指定字段类型

在上面的示例中,全部字段都是标量类型:两个整数(page_numberresult_per_page)和一个字符串(query)。可是,您还能够为字段指定合成类型,包括枚举和其余消息类型。ios

分配标识号

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

最小的标识号能够从1开始,最大到2^29 - 1, or 536,870,911。不可使用其中的[19000-19999]的标识号, Protobuf协议实现中对这些进行了预留。若是非要在.proto文件中使用这些预留标识号,编译时就会报错。github

指定字段规则

消息字段能够是如下之一:golang

  • 单数:格式良好的消息能够包含该字段中的零个或一个(但不超过一个)。objective-c

  • repeated:此字段能够在格式良好的消息中重复任意次数(包括零)。将保留重复值的顺序。spring

在proto3中,repeated数字类型的字段默认使用packed编码。

packed您能够在协议缓冲区编码中找到有关编码的更多信息。

添加更多消息类型

能够在单个.proto文件中定义多种消息类型。当你要定义多个相关消息时,这就颇有用 了。 例如,若是要定义与SearchResponse消息类型对应的回复消息格式,那么我在同一个.proto文件中:

1   message SearchRequest {
2     string query = 1;
3     int32 page_number = 2;
4     int32 result_per_page = 3;
5   }
6 7   message SearchResponse {
8    ...
9   }

 

添加注释

要为.proto文件添加注释,请使用C / C ++ - 样式///* ... */语法。

1  / * SearchRequest表示搜索查询,带有分页选项
2    *代表响应中包含哪些结果。* /
3 4   message SearchRequest {
5     string query = 1;
6     int32 page_number = 2; //咱们想要哪一个页码?
7     int32 result_per_page = 3; //每页返回的结果数。
8   }

 

保留字段

当你在某次更新消息中屏蔽或者删除了一个字段的话,将来的使用着可能在他们的更新中重用这个标签数字来标记他们本身的字段。而后当他们加载旧的消息的时候就会出现不少问题,包括数据冲突,隐藏的bug等等。指定这个字段的标签数字(或者名字,名字可能在序列化为JSON的时候可能冲突)标记为reserved来保证他们不会再次被使用。若是之后的人试用的话protobuf编译器会提示出错。

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

 

注意一个reserved字段不能既有标签数字又有名字。

.proto文件最终生成什么

当你使用protoc 来编译一个.proto文件的时候,编译器将利用你在文件中定义的类型生成你打算使用的语言的代码文件。生成的代码包括getting setting 接口和序列化,反序列化接口。

  • 对于C ++,编译器会从每一个.proto文件生成一个.h和一个.cc文件,并为您文件中描述的每种消息类型提供一个类。

  • 对于Java,编译器生成一个.java文件,其中包含每种消息类型的类,以及Builder用于建立消息类实例的特殊类。

  • Python有点不一样 - Python编译器生成一个模块,其中包含每一个消息类型的静态描述符,而后,用一个元类在运行时建立必要的Python数据访问类。

  • 对于Go,编译器会为.pb.go文件中的每种消息类型生成一个类型的文件。

  • 对于Ruby,编译器生成一个.rb包含消息类型的Ruby模块的文件。

  • 对于Objective-C,编译器从每一个.proto文件生成一个pbobjc.h和一个pbobjc.m文件,其中包含文件中描述的每种消息类型的类。

  • 对于C#,编译器会从每一个.proto文件生成一个.cs文件,其中包含文件中描述的每种消息类型的类。

您能够按照所选语言的教程(即将推出的proto3版本)了解有关为每种语言使用API的更多信息。有关更多API详细信息,请参阅相关API参考(proto3版本即将推出)。

标量值类型

标量消息字段能够具备如下类型之一 - 该表显示.proto文件中指定的类型,以及自动生成的类中的相应类型:

.proto type notes C ++ type Java type Python type [2] Type Ruby type C# type PHP type
double   double double float float64 float double float
float   float float float FLOAT32 float float float
INT32 使用可变长度编码。编码负数的效率低 - 若是您的字段可能有负值,请改用sint32。 INT32 INT INT INT32 Fixnum or Bignum (as needed) INT Integer
Int64 使用可变长度编码。编码负数的效率低 - 若是您的字段可能有负值,请改用sint64。 Int64 long int / long [3] Int64 TWINS long Integer/string[5]
UINT32 使用可变长度编码。 UINT32 int [1] int / long [3] UINT32 Fixnum or Bignum (as needed) UINT Integer
UINT64 使用可变长度编码。 UINT64 Long [1] int / long [3] UINT64 TWINS ULONG Integer/string[5]
SINT32 使用可变长度编码。签名的int值。这些比常规int32更有效地编码负数。 INT32 INT INT INT32 Fixnum or Bignum (as needed) INT Integer
sint64 使用可变长度编码。签名的int值。这些比常规int64更有效地编码负数。 Int64 long int / long [3] Int64 TWINS long Integer/string[5]
fixed32 老是四个字节。若是值一般大于2 28,则比uint32更有效。 UINT32 int [1] int / long [3] UINT32 Fixnum or Bignum (as needed) UINT Integer
fixed64 老是八个字节。若是值一般大于2 56,则比uint64更有效。 UINT64 Long [1] int / long [3] UINT64 TWINS ULONG Integer/string[5]
sfixed32 老是四个字节。 INT32 INT INT INT32 Fixnum or Bignum (as needed) INT Integer
sfixed64 老是八个字节。 Int64 long int / long [3] Int64 TWINS long Integer/string[5]
Boolean   Boolean Boolean Boolean Boolean TrueClass / FalseClass Boolean Boolean
string 字符串必须始终包含UTF-8编码或7位ASCII文本。 string string str / unicode[4] string String (UTF-8) string string
byte 能够包含任意字节序列。 string Byte string Strait []byte String (ASCII-8BIT) Byte string string

protobuf 编码序列化消息时,您能够找到有关如何编码这些类型的更多信息。

[1]在Java中,无符号的32位和64位整数使用它们的带符号对应表示,最高位只是存储在符号位中。

[2]在全部状况下,将值设置为字段将执行类型检查以确保其有效。

[3] 64位或无符号32位整数在解码时始终表示为long,但若是在设置字段时给出int,则能够为int。在全部状况下,该值必须适合设置时表示的类型。见[2]。

[4] Python字符串在解码时表示为unicode,但若是给出了ASCII字符串,则能够是str(这可能会发生变化)。

[5] Integer用于64位计算机,字符串用于32位计算机。

默认值

解析消息时,若是编码消息不包含特定的单数元素,则解析对象中的相应字段将设置为该字段的默认值。这些默认值是特定于类型的:

  • 对于字符串,默认值为空字符串。

  • 对于字节,默认值为空字节。

  • 对于bools,默认值为false。

  • 对于数字类型,默认值为零。

  • 对于枚举,默认值是第一个定义的枚举值,该必须为0。

  • 对于消息字段,未设置该字段。它的确切值取决于语言。有关详细信息, 请参阅生成的代码指

重复字段的默认值为空(一般是相应语言的空列表)。

请注意,对于标量消息字段,一旦解析了消息,就没法肯定字段是否显式设置为默认值(例如,是否设置了布尔值false)或者根本没有设置:您应该记住这一点在定义消息类型时。例如,false若是您不但愿默认状况下也发生这种行为,那么在设置为时,没有一个布尔值能够启用某些行为。还要注意的是,若是一个标消息字段设置为默认值,该值将不会在电线上连载。

有关默认值如何在生成的代码中工做的更多详细信息,请参阅所选语言的生成代码指南

枚举

当你定义一个消息的时候,你可能但愿它其中的某个字段必定是预先定义好的一组值中的一个。你如说我要在SearchRequest中添加corpus字段。它只能是 UNIVERSAL, WEB , IMAGES , LOCAL, NEWS ,PRODUCTS, 或者 VIDEO 。你能够很简单的在你的消息中定义一个枚举而且定义corpus字段为枚举类型,若是这个字段给出了一个再也不枚举中的值,那么解析器就会把它看成一个未知的字段。

在下面的示例中,咱们添加了一个带有全部可能值的枚举方法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做为数字默认值

  • 零值必须是第一个元素,以便与proto2语义兼容,其中第一个枚举值始终是默认值。

只须要将相同的值赋值给不一样的枚举项名字,你就在枚举中你能够定义别名 。固然你得先将allow_alias选项设置为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.
  }

 

枚举器常量必须在32位整数范围内。因为enum值在线上使用varint编码,所以负值效率低,所以不建议使用。您能够enum在消息定义中定义s,如上例所示,enum也能够在外部定义 - 这些能够在.proto文件的任何消息定义中重用。您还可使用enum语法将一个消息中声明的类型用做另外一个消息中的字段类型。 *MessageType*.*EnumType*

若是你想要使用在消息内定义的枚举的话,使用语法 MessageType.EnumType 在你编译带有枚举的.proto文件的时候,若是生成的是C++或者Java代码, 那么生成的代码中会有对应的枚举。

在反序列化期间,将在消息中保留没法识别的枚举值,可是当反序列化消息时,如何表示这种值取决于语言。在支持具备超出指定符号范围的值的开放枚举类型的语言中,例如C ++和Go,未知的枚举值仅做为其基础整数表示存储。在具备封闭枚举类型(如Java)的语言中,枚举中的大小写用于表示没法识别的值,而且可使用特殊访问器访问基础整数。在任何一种状况下,若是消息被序列化,则仍然会使用消息序列化没法识别的值。

有关如何enum在应用程序中使用消息的详细信息,请参阅所选语言的生成代码指南

保留值

若是经过彻底删除枚举条目或将其注释掉来更新枚举类型,则将来用户能够在对类型进行本身的更新时重用该数值。若是之后加载相同的旧版本,这可能会致使严重问题.proto,包括数据损坏,隐私错误等。确保不会发生这种状况的一种方法是指定已删除条目的数值(和/或名称,这也可能致使JSON序列化问题)reserved。若是未来的任何用户尝试使用这些标识符,协议缓冲编译器将会抱怨。您可使用max关键字指定保留的数值范围达到最大可能值。

enum Foo {
    reserved 2, 15, 9 to 11, 40 to max;
    reserved "FOO", "BAR";
  }

 

请注意,您不能在同一reserved语句中混合字段名称和数值。

使用其余消息类型

您可使用其余消息类型做为字段类型。例如,假设你想包括Result每一个消息的SearchResponse消息-要作到这一点,你能够定义一个Result在同一个消息类型.proto,而后指定类型的字段ResultSearchResponse

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

 

导入定义

在上面的示例中,Result消息类型在同一文件中定义SearchResponse,若是要用做字段类型的消息类型已在另外一个.proto文件中定义,该怎么办?

您能够.proto经过导入来使用其余文件中的定义。要导入其余.proto的定义,请在文件顶部添加import语句:

import“myproject / other_protos.proto”;

 

默认状况下,您只能使用直接导入.proto文件中的定义。可是,有时您可能须要将.proto文件移动到新位置。.proto如今,您能够.proto在旧位置放置一个虚拟文件,以使用该import public概念将全部导入转发到新位置,而不是直接移动文件并在一次更改中更新全部调用站点。import public任何导入包含该import public语句的proto的人均可以传递依赖关系。例如:

 // new.proto
  // All definitions are moved here
  // old.proto
  //This is the proto that all clients are importing.
  import publicnew.proto”;
  import“other.proto”;
  // client.proto
  import "old.proto";
  //您使用old.proto和new.proto中的定义,但不使用other.proto

 

协议编译器使用-I/ --proto_pathflag 在协议编译器命令行中指定的一组目录中搜索导入的文件 。若是没有给出标志,它将查找调用编译器的目录。一般,您应该将--proto_path标志设置为项目的根目录,并对全部导入使用彻底限定名称。

使用proto2消息类型

能够导入proto2消息类型并在proto3消息中使用它们,反之亦然。可是,proto2枚举不能直接用于proto3语法(若是导入的proto2消息使用它们就能够了)。

嵌套类型

您能够在其余消息类型中定义和使用消息类型,以下例所示 - 此处Result消息在消息中定义SearchResponse

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

 

若是要在其父消息类型以外重用此消息类型,请将其称为: *Parent*.*Type*

 message SomeOtherMessage {
    SearchResponse.Result result = 1;
  }

 

您能够根据须要深刻嵌套消息:

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

 

更新消息类型

若是现有的消息类型再也不知足您的全部需求 - 例如,您但愿消息格式具备额外的字段 - 但您仍然但愿使用使用旧格式建立的代码,请不要担忧!在不破坏任何现有代码的状况下更新消息类型很是简单。请记住如下规则:

  • 请勿更改任何现有字段的字段编号。

  • 若是添加新字段,则使用“旧”消息格式按代码序列化的任何消息仍可由新生成的代码进行解析。您应该记住这些元素的默认值,以便新代码能够正确地与旧代码生成的消息进行交互。一样,您的新代码建立的消息能够由旧代码解析:旧的二进制文件在解析时只是忽略新字段。有关详细信息,请参阅“ 未知字段”部分

  • 只要在更新的消息类型中再也不使用字段编号,就能够删除字段。您可能但愿重命名该字段,可能添加前缀“OBSOLETE_”,或者保留字段编号,以便您的将来用户.proto不会意外地重复使用该编号。

  • int32uint32int64uint64,和bool都是兼容的-这意味着你能够改变这些类型到另外一个的一个场不破坏forwards-或向后兼容。若是从导线中解析出一个不符合相应类型的数字,您将得到与在C ++中将该数字转换为该类型相同的效果(例如,若是将64位数字做为int32读取,它将被截断为32位)。

  • sint32而且sint64彼此兼容但与其余整数类型兼容。

  • string``bytes只要字节是有效的UTF-8 ,它们是兼容的。

  • bytes若是字节包含消息的编码版本,则嵌入消息是兼容的。

  • fixed32与兼容sfixed32,并fixed64sfixed64

  • enum与兼容int32uint32int64,和uint64电线格式条款(注意,若是他们不适合的值将被截断)。但请注意,在反序列化消息时,客户端代码可能会以不一样方式对待它们:例如,enum将在消息中保留未识别的proto3 类型,但在反序列化消息时如何表示这种类型取决于语言。Int字段老是保留它们的价值。

  • 将单个值更改成 成员oneof是安全且二进制兼容的。oneof若是您肯定没有代码一次设置多个字段,则将多个字段移动到新字段多是安全的。将任何字段移动到现有字段oneof并不安全。

未知字段

未知字段是格式良好的协议缓冲区序列化数据,表示解析器没法识别的字段。例如,当旧二进制文件解析具备新字段的新二进制文件发送的数据时,这些新字段将成为旧二进制文件中的未知字段。

最初,proto3消息在解析期间老是丢弃未知字段,但在3.5版本中,咱们从新引入了保存未知字段以匹配proto2行为。在版本3.5及更高版本中,未知字段在解析期间保留并包含在序列化输出中。

任何

Any消息类型,可使用邮件做为嵌入式类型,而没必要本身.proto定义。一个Any含有任意的序列化消息bytes,以充当一个全局惟一标识符和解析到该消息的类型的URL一块儿。要使用该Any类型,您须要导入google/protobuf/any.proto

 import "google/protobuf/any.proto";
  ​
  message ErrorStatus {
    string message = 1;
    repeated google.protobuf.Any details = 2;
  }

 

给定消息类型的默认类型URL是。 type.googleapis.com/*packagename*.*messagename*

不一样的语言实现将支持运行时库佣工类型安全的方式打包和解包的任何值-例如,在Java中,任何类型都会有特殊pack()unpack()存取,而在C ++中有PackFrom()UnpackTo()方法:

  // Storing an arbitrary message type in Any.
  NetworkErrorDetails details = ...;
  ErrorStatus status;
  status.add_details()->PackFrom(details);
  ​
  // Reading an arbitrary message from Any.
  ErrorStatus status = ...;
  for (const Any& detail : status.details()) {
    if (detail.Is<NetworkErrorDetails>()) {
      NetworkErrorDetails network_error;
      detail.UnpackTo(&network_error);
      ... processing network_error ...
    }
  }

 

目前,正在开发用于处理Any类型的运行时库

若是您已熟悉proto2语法,则Any类型将替换扩展

Oneof

若是您有一个包含许多字段的消息,而且最多只能同时设置一个字段,则可使用oneof功能强制执行此行为并节省内存。

除了一个共享内存中的全部字段以外,其中一个字段相似于常规字段,而且最多能够同时设置一个字段。设置oneof的任何成员会自动清除全部其余成员。您可使用特殊case()WhichOneof()方法检查oneof中的哪一个值(若是有),具体取决于您选择的语言。

使用Oneof

要在您中定义oneof,请.proto使用oneof关键字后跟您的oneof名称,在这种状况下test_oneof

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

 

而后,将oneof字段添加到oneof定义中。您能够添加任何类型的字段,但不能使用repeated字段。

在生成的代码中,oneof字段与常规字段具备相同的getter和setter。您还可使用特殊方法检查oneof中的值(若是有)。您能够在相关API参考中找到有关所选语言的oneof API的更多信息。

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());

     

  • 若是解析器在线路上遇到同一个oneof的多个成员,则在解析的消息中仅使用看到的最后一个成员。

  • 一个不可能repeated

  • Reflection API适用于其中一个字段。

  • 若是您使用的是C ++,请确保您的代码不会致使内存崩溃。如下示例代码将崩溃,sub_message

    已经过调用该set_name()方法删除了该代码。

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

     

  • 一样在C ++中,若是你有Swap()两个消息与oneofs,每一个消息最终将与另外一个消息结果:在下面的例子中,msg1将有一个sub_messagemsg2并将有一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的被设置为一个字段。没有办法区分,由于没有办法知道线上的未知字段是不是其中一个成员。

标签重用问题

  • 将字段移入或移出oneof:在序列化和解析消息后,您可能会丢失一些信息(某些字段将被清除)。可是,您能够安全地将单个字段移动到新的 oneof中,而且若是已知只有一个字段被设置,则能够移动多个字段。

  • 删除oneof字段并将其添加回:在序列化和解析消息后,这可能会清除当前设置的oneof字段。

  • 拆分或合并oneof:这与移动常规字段有相似的问题。

Maps

若是要在数据定义中建立关联映射,protobuf提供了一种方便的快捷方式语法:

map < key_type ,value_type > map_field = N ;

 

这里key_type能够是任意整形或者字符串。而value_tpye 能够是任意类型。 举个例子,若是你打算建立一个Project表,每一个Project关联到一个字符串上的话 :

 map < string ,Project > projects = 3 ;  

 

  • map字段不能repeated

  • map值的有线格式排序和地图迭代排序未定义,所以您不能依赖于特定顺序的map项目。

  • .proto生成文本格式时,地图按键排序。数字键按数字排序。

  • 从线路解析或合并时,若是有重复的映射键,则使用最后看到的键。从文本格式解析映射时,若是存在重复键,则解析可能会失败。

  • 若是为映射字段提供键但没有值,则字段序列化时的行为取决于语言。在C ++,Java和Python中,类型的默认值是序列化的,而在其余语言中没有任何序列化。

生成的地图API目前可用于全部proto3支持的语言。您能够在相关API参考中找到有关所选语言的地图API的更多信息。

向后兼容性

在通信中,map等价与下面的定义, 这样不支持Map的版本也能够解析你的消息:

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

 

任何支持映射的协议缓冲区实现都必须生成和接受上述定义能够接受的数据。

您能够向.proto文件添加package可选说明符,以防止协议消息类型之间的名称冲突。

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

 

而后,您能够在定义消息类型的字段时使用包说明符:

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

 

包名字的实现取决于你工做的具体编程语言:

  • C ++中,生成的类包含在C ++命名空间中。例如,Open将在命名空间中foo::bar

  • Java中,该包用做Java包,除非您option java_package.proto文件中明确提供了该包。

  • Python中,package指令被忽略,由于Python模块是根据它们在文件系统中的位置进行组织的。

  • Go中,该包用做Go包名称,除非您option go_package.proto文件中明确提供。

  • Ruby中,生成的类包含在嵌套的Ruby命名空间内,转换为所需的Ruby大写形式(首字母大写;若是第一个字符不是字母,PB_则前置)。例如,Open将在命名空间中Foo::Bar

  • C#中,包转换为PascalCase后用做命名空间,除非您option csharp_namespace.proto文件中明确提供。例如,Open将在命名空间中Foo.Bar

包和名称解析

协议缓冲区语言中的类型名称解析与C ++相似:首先搜索最里面的范围,而后搜索下一个范围,依此类推,每一个包被认为是其父包的“内部”。一个领先的'。' (例如,.foo.bar.Baz)意味着从最外层的范围开始。

protobuf 编译器经过解析导入的.proto文件来解析全部类型名称。每种语言的代码生成器都知道如何使用该语言引用每种类型,即便它具备不一样的范围规则。

定义服务

若是要将消息类型与RPC(远程过程调用)系统一块儿使用,则能够在.proto文件中定义RPC服务接口,protobuf 编译器将使用您选择的语言生成服务接口代码和存根。所以,例如,若是要定义RPC服务请求方法为:SearchRequest和返回方法为:SearchResponse,能够.proto按以下方式在文件中定义它:

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

 

与协议缓冲区一块儿使用的最简单的RPC系统是gRPC:一种由Google开发的,平台中立的开源RPC系统。gRPC特别适用于protobuf,并容许在您的.proto文件中使用特殊的protobuf 编译器插件直接生成相关的RPC代码。

若是您不想使用gRPC,也能够将protobuf与您本身的RPC实现一块儿使用。您能够在Proto2语言指南中找到更多相关信息。

还有一些正在进行的第三方项目使用Protocol Buffers开发RPC实现。有关咱们了解的项目的连接列表,请参阅第三方加载项wiki页面

JSON映射

Proto3支持JSON中的规范编码,使得在系统之间共享数据变得更加容易。在下表中逐个类型地描述编码。

若是JSON编码数据中缺乏值null,或者其值为,则在解析为协议缓冲区时,它将被解释为适当的默认值。若是字段在协议缓冲区中具备默认值,则默认状况下将在JSON编码数据中省略该字段以节省空间。实现能够提供用于在JSON编码的输出中发出具备默认值的字段的选项。

proto3 JSON JSON示例 笔记
message object {"fooBar": v, "g": null,…} 生成JSON对象。消息字段名称映射到小写驼峰并成为JSON对象键。若是json_name指定了field选项,则指定的值将用做键。解析器接受小写驼峰名称(或json_name选项指定的名称)和原始proto字段名称。null是全部字段类型的可接受值,并将其视为相应字段类型的默认值。
eunm String "FOO_BAR" 使用proto中指定的枚举值的名称。解析器接受枚举名称和整数值。
map<K,V> object {"k": v, …} 全部键都转换为字符串。
repeated V. array [v, …] null 被接受为空列表[]。
bool true,false true, false  
string string "Hello World!"  
bytes base64 string "YWJjMTIzIT8kKiYoKSctPUB+" JSON值将是使用带填充的标准base64编码编码为字符串的数据。接受带有/不带填充的标准或URL安全base64编码。
int32,fixed32,uint32 string 1, -10, 0 JSON值将是十进制数。接受数字或字符串。
int64,fixed64,uint64 string "1", "-10" JSON值将是十进制字符串。接受数字或字符串。
float,double number 1.1, -10.0, 0, "NaN","Infinity" JSON值将是一个数字或一个特殊字符串值“NaN”,“Infinity”和“-Infinity”。接受数字或字符串。指数表示法也被接受。
any object {"@type": "url", "f": v, … } 若是Any包含具备特殊JSON映射的值,则将按以下方式进行转换:。不然,该值将转换为JSON对象,并将插入该字段以指示实际的数据类型。{"@type": xxx, "value": yyy}``"@type"
Timestamp string "1972-01-01T10:00:20.021Z" 使用RFC 3339,其中生成的输出将始终被Z标准化并使用0,3,6或9个小数位。也接受“Z”之外的偏移。
Duration string "1.000340012s", "1s" 生成的输出始终包含0,3,6或9个小数位,具体取决于所需的精度,后跟后缀“s”。接受的是任何小数位(也没有),只要它们符合纳秒精度而且后缀“s”是必需的。
Struct object { … } 任何JSON对象。见。struct.proto
Wrapper types various types 2, "2", "foo", true,"true", null, 0, … 包装器在JSON中使用与包装基元类型相同的表示形式,除了null在数据转换和传输期间容许和保留的表示形式。
FieldMask string "f.fooBar,h" 见。field_mask.proto
ListValue array [foo, bar, …]  
Value value   任何JSON值
NullValue null   JSON null

JSON选项

proto3 JSON实现能够提供如下选项:

  • 使用默认值发出字段:默认状况下,proto3 JSON输出中省略了具备默认值的字段。实现能够提供覆盖此行为的选项,并使用其默认值输出字段。

  • 忽略未知字段:默认状况下,Proto3 JSON解析器应拒绝未知字段,但能够提供忽略解析中未知字段的选项。

  • 使用proto字段名称而不是小写驼峰名称:默认状况下,proto3 JSON打印机应将字段名称转换为小写驼峰并将其用做JSON名称。实现能够提供使用proto字段名称做为JSON名称的选项。Proto3 JSON解析器须要接受转换后的小写驼峰名称和proto字段名称。

  • 将枚举值发送为整数而不是字符串:默认状况下,在JSON输出中使用枚举值的名称。能够提供选项以使用枚举值的数值。

选项

.proto文件中的各个声明可使用许多选项进行注释。选项不会更改声明的总体含义,但可能会影响在特定上下文中处理它的方式。可用选项的完整列表在中定义google/protobuf/descriptor.proto

一些选项是文件级选项,这意味着它们应该在顶级范围内编写,而不是在任何消息,枚举或服务定义中。一些选项是消息级选项,这意味着它们应该写在消息定义中。一些选项是字段级选项,这意味着它们应该写在字段定义中。选项也能够写在枚举类型,枚举值,服务类型和服务方法上; 可是,目前没有任何有用的选择。

如下是一些最经常使用的选项:

  • java_package(文件选项):用于生成的Java类的包。若是.proto文件中没有给出显式选项java_package,则默认状况下将使用proto包(使用文件中的“package”关键字指定 .proto )。可是,proto包一般不能生成好的Java包,由于proto包不会以反向域名开头。若是不生成Java代码,则此选项无效。

    option java_package =“com.example.foo”;
  • java_multiple_files (文件选项):致使在包级别定义顶级消息,枚举和服务,而不是在.proto文件以后命名的外部类中。

option java_multiple_files = true;
  • java_outer_classname(file option):要生成的最外层Java类(以及文件名)的类名。若是 .proto文件中没有指定 java_outer_classname,则经过将.proto文件名转换为驼峰格式(所以 foo_bar.proto 成为FooBar.java)来构造类名。若是不生成Java代码,则此选项无效。

 option java_outer_classname =“Ponycopter”;

 

  • optimize_for

    (文件选项):能够设置为SPEEDCODE_SIZELITE_RUNTIME。这会如下列方式影响C ++和Java代码生成器(可能还有第三方生成器):

    • SPEED(默认值):protobuf 编译器将生成用于对消息类型进行序列化,解析和执行其余常见操做的代码。此代码通过高度优化。

    • CODE_SIZE:protobuf 编译器将生成最少的类,并依赖于基于反射的共享代码来实现序列化,解析和各类其余操做。所以生成的代码比使用SPEED小得多,但操做会更慢。类仍将实现与SPEED模式彻底相同的公共API 。此模式在包含很是大数量的.proto文件的应用程序中最有用,而且不须要全部文件都很是快速。

    • LITE_RUNTIME:protobuf 编译器将生成仅依赖于“lite”运行时库(libprotobuf-lite而不是libprotobuf)的类。精简版运行时比整个库小得多(大约小一个数量级),但省略了描述符和反射等特定功能。这对于在移动电话等受限平台上运行的应用程序尤为有用。编译器仍然会像在SPEED模式中同样生成全部方法的快速实现。生成的类将仅实现MessageLite每种语言的接口,该接口仅提供完整Message接口的方法的子集。

    option optimize_for = CODE_SIZE;
  • cc_enable_arenas(文件选项):为C ++生成的代码启用竞技场分配

  • objc_class_prefix(文件选项):设置Objective-C类前缀,该前缀预先添加到此.proto的全部Objective-C生成的类和枚举中。没有默认值。您应该使用Apple建议的 3-5个大写字符之间的前缀。请注意,Apple保留全部2个字母的前缀。

  • deprecated(字段选项):若是设置为true,则表示该字段已弃用,新代码不该使用该字段。在大多数语言中,这没有实际效果。在Java中,这成为一个@Deprecated注释。未来,其余特定于语言的代码生成器可能会在字段的访问器上生成弃用注释,这将致使在编译尝试使用该字段的代码时发出警告。若是任何人都没有使用该字段,而且您但愿阻止新用户使用该字段,请考虑使用保留语句替换字段声明。

    int32 old_field = 6 [deprecated = true];

自定义选项

Protocol Buffers还容许您定义和使用本身的选项。这是大多数人不须要的高级功能。若是您确实认为须要建立本身的选项,请参阅Proto2语言指南以获取详细信息。请注意,建立自定义选项使用的扩展名仅容许用于proto3中的自定义选项。

生成您的类

根据实际工做须要,生成如下对应语言的自定义消息类型Java,Python,C ++,Go, Ruby, Objective-C,或C#的.proto文件,你须要运行protobuf 编译器protoc.proto。若是还没有安装编译器,请下载该软件包并按照自述文件中的说明进行操做。对于Go,您还须要为编译器安装一个特殊的代码生成器插件:您能够在GitHub上的golang / protobuf存储库中找到这个和安装说明。

Protobuf 编译器的调用以下:

protoc --proto_path = IMPORT_PATH --cpp_out = DST_DIR --java_out = DST_DIR --python_out = DST_DIR --go_out = DST_DIR --ruby_out = DST_DIR --objc_out = DST_DIR --csharp_out = DST_DIR  path / to / file .proto
  • IMPORT_PATH指定.proto解析import指令时在其中查找文件的目录。若是省略,则使用当前目录。能够经过--proto_path屡次传递选项来指定多个导入目录; 他们将按顺序搜索。 能够用做简短的形式。 -I=*IMPORT_PATH*``--proto_path

  • 您能够提供一个或多个输出指令:

    为了方便起见,若是DST_DIR结束于.zip或.jar,编译器会将输出写入具备给定名称的单个ZIP格式存档文件。.jar输出还将根据Java JAR规范的要求提供清单文件。请注意,若是输出存档已存在,则会被覆盖; 编译器不够智能,没法将文件添加到现有存档中。

  • 您必须提供一个或多个.proto文件做为输入。.proto能够一次指定多个文件。虽然文件是相对于当前目录命名的,但每一个文件必须位于其中一个文件中,IMPORT_PATH以便编译器能够肯定其规范名称。

     

其它

[Protocol Buffers 官网​]  https://developers.google.com/protocol-buffers/docs/proto3 Language Guide(proto3)

关于Protobuf 语言指南(proto3)详解到这里就结束了。

对了,也有使用protobuf整合的Netty项目工程地址: https://github.com/sanshengshui/netty-learning-example/tree/master/netty-springboot-protobuf

原创不易,若是感受不错,但愿给个推荐!您的支持是我写做的最大动力!

版权声明: 做者:穆书伟 博客园出处:https://www.cnblogs.com/sanshengshui github出处:https://github.com/sanshengshui     我的博客出处:https://sanshengshui.github.io/

相关文章
相关标签/搜索