gRPC-Protocol语法指南

本指南介绍了如何使用协议缓冲区语言来构造协议缓冲区数据(包括.proto文件语法)以及如何从.proto文件生成数据访问类。 它涵盖了协议缓冲区语言的proto3版本:有关proto2语法的信息,请参见《Proto2语言指南》。
这是参考指南–有关使用本文档中描述的许多功能的分步示例,请参见所选择语言的教程(当前仅适用于proto2;即将推出更多proto3文档)。

语法指南 (proto3)

  • Defining A Message Type
  • Scalar Value Types
  • Default Values
  • Enumerations
  • Using Other Message Types
  • Nested Types
  • Updating A Message Type
  • Unknown Fields
  • Any
  • Oneof
  • Maps
  • Packages
  • Defining Services
  • JSON Mapping
  • Options
  • Generating Your Classes

Defining A Message Type

首先,让我们看一个非常简单的示例。 假设您要定义一个搜索请求消息格式,其中每个搜索请求都有一个查询字符串,您感兴趣的特定结果页面以及每页结果数量。 这是用于定义消息类型的.proto文件。

syntax = "proto3";

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}
  • 文件的第一行指定您使用的是proto3语法:如果不这样做,则协议缓冲区编译器将假定您使用的是proto2。 这必须是文件的第一行非空,非注释行。
  • SearchRequest消息定义指定三个字段(名称/值对),每个字段要包含在此类型的消息中,每个字段对应一个。 每个字段都有一个名称和类型。

Specifying Field Types

在上面的示例中,所有字段均为标量类型:两个整数(page_number和result_per_page)和一个字符串(查询)。 但是,您也可以为字段指定复合类型,包括枚举和其他消息类型。

Assigning Field Numbers

如您所见,消息定义中的每个字段都有一个唯一的编号。这些字段号用于标识消息二进制格式的字段,一旦使用了消息类型,就不应更改这些字段号。请注意,范围为1到15的字段编号需要一个字节来编码,包括字段编号和字段的类型(您可以在协议缓冲区编码中找到更多有关此内容的信息)。 16到2047之间的字段号占用两个字节。因此,您应该为经常出现的消息元素保留数字1到15。切记为将来可能添加的频繁出现的元素留出一些空间。

您可以指定的最小字段号是1,最大字段号是229-1或536,870,911。您也不能使用数字19000到19999(FieldDescriptor :: kFirstReservedNumber到FieldDescriptor :: kLastReservedNumber),因为它们是为协议缓冲区实现保留的-如果在.proto中使用这些保留数之一,协议缓冲区编译器会抱怨。同样,您不能使用任何以前保留的字段号。

Specifying Field Rules

消息字段可以是以下内容之一:

  • 单数:格式正确的邮件可以包含零个或一个此字段(但不能超过一个)。 这是proto3语法的默认字段规则。
  • 重复:此字段可以在格式正确的消息中重复任意次(包括零次)。 重复值的顺序将保留。

在proto3中,标量数字类型的重复字段默认情况下使用打包编码。
您可以在协议缓冲区编码中找到有关打包编码的更多信息。

Adding More Message Types

可以在单个.proto文件中定义多种消息类型。 如果要定义多个相关消息,这很有用–例如,如果要定义与SearchResponse消息类型相对应的答复消息格式,可以将其添加到相同的.proto中:

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

message SearchResponse {
 ...
}

Adding Comments

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

/* SearchRequest represents a search query, with pagination options to
 * indicate which results to include in the response. */

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 Fields

如果您通过完全删除字段或将其注释掉来更新消息类型,则将来的用户在自己对该类型进行更新时可以重用该字段号。 如果他们以后加载同一.proto的旧版本,可能会导致严重的问题,包括数据损坏,隐私错误等。 确保不会发生这种情况的一种方法是指定保留已删除字段的字段编号(和/或名称,这也可能导致JSON序列化问题)。 如果将来有任何用户尝试使用这些字段标识符,则协议缓冲区编译器会抱怨。

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

请注意,您不能在同一保留语句中混用字段名称和字段编号。

What's Generated From Your .proto?

在.proto上运行协议缓冲区编译器时,编译器会以您选择的语言生成代码,您将需要使用该文件处理文件中描述的消息类型,包括获取和设置字段值,将消息序列化为输出流,并从输入流中解析消息。

  • 对于C ++,编译器从每个.proto生成.h和.cc文件,并为文件中描述的每种消息类型提供一个类。
  • 对于Java,编译器会生成一个.java文件,其中包含每种消息类型的类以及用于创建消息类实例的特殊Builder类。
  • Python稍有不同-Python编译器会在.proto中生成带有每种消息类型的静态描述符的模块,然后将该模块与元类一起使用,以在运行时创建必要的Python数据访问类。
  • 对于Go,编译器会生成一个.pb.go文件,其中包含文件中每种消息类型的类型。
  • 对于Ruby,编译器将使用包含您的消息类型的Ruby模块生成一个.rb文件。
  • 对于Objective-C,编译器从每个.proto生成一个pbobjc.h和pbobjc.m文件,并为文件中描述的每种消息类型提供一个类。
  • 对于C#,编译器从每个.proto生成一个.cs文件,并为文件中描述的每种消息类型提供一个类。
  • 对于Dart,编译器会生成一个.pb.dart文件,其中包含文件中每种消息类型的类。

您可以按照所选语言的教程(即将推出proto3版本)查找有关每种语言使用API​​的更多信息。有关API的更多详细信息,请参见相关的API参考(proto3版本也即将推出)。

Scalar Value Types

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

在协议缓冲区编码中序列化消息时,您可以找到更多有关这些类型如何编码的信息。

[1]在Java中,无符号的32位和64位整数使用带符号的对等体表示,最高位仅存储在符号位中。

[2]在所有情况下,将值设置为字段都会执行类型检查以确保其有效。

[3] 64位或无符号32位整数在解码时始终表示为long,但是如果在设置字段时给出了int,则可以为int。 在所有情况下,该值都必须适合设置时表示的类型。 参见[2]。

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

[5]在64位计算机上使用Integer,在32位计算机上使用string。

Default Values

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

  • 对于字符串,默认值为空字符串。
  • 对于字节,默认值为空字节。
  • 对于布尔值,默认值为false。
  • 对于数字类型,默认值为零。
  • 对于枚举,默认值为第一个定义的枚举值,必须为0。
  • 对于消息字段,未设置该字段。它的确切值取决于语言。有关详细信息,请参见生成的代码指南。
  • 重复字段的默认值为空(通常为相应语言的空列表)。

请注意,对于标量消息字段,一旦解析了一条消息,就无法判断是将字段明确设置为默认值(例如,是否将布尔值设置为false)还是根本没有设置:您应该在定义消息类型时要注意。例如,如果您不希望默认情况下也发生这种情况,则当布尔值设置为false时,没有布尔值会打开某些行为。还要注意,如果将标量消息字段设置为其默认值,则该值将不会在线路上被序列化。

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

Enumerations

在定义消息类型时,您可能希望其字段之一仅具有一个预定义的值列表之一。 例如,假设您要为每个SearchRequest添加一个语料库字段,该语料库可以是UNIVERSAL,WEB,IMAGES,LOCAL,NEWS,PRODUCTS或VIDEO。 您可以通过在消息定义中添加一个枚举以及每个可能值的常量来非常简单地完成此操作。

在下面的示例中,我们添加了一个名为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,否则协议编译器将在找到别名时生成一条错误消息。

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

枚举器常量必须在32位整数范围内。 由于枚举值在导线上使用varint编码,因此负值效率不高,因此不建议使用。 您可以在消息定义内定义枚举,如上例所示,也可以在外部定义-这些枚举可以在.proto文件中的任何消息定义中重复使用。 您还可以使用语法_MessageType . EnumType_将一条消息中声明的枚举类型用作另一条消息中的字段类型。

在使用枚举的.proto上运行协议缓冲区编译器时,生成的代码将具有一个对应的Java或C ++枚举,一个特殊的Python EnumDescriptor类,用于在运行时创建带有整数值的符号常量集 生成的类。

反序列化期间,无法识别的枚举值将保留在消息中,尽管在反序列化消息时如何表示该值取决于语言。 在支持具有超出指定符号范围的值的开放式枚举类型的语言(例如C ++和Go)中,未知的枚举值仅存储为其基础整数表示形式。 在诸如Java之类的具有封闭枚举类型的语言中,枚举中的大小写用于表示无法识别的值,并且可以使用特殊的访问器访问基础整数。 无论哪种情况,如果消息被序列化,则无法识别的值仍将与消息一起序列化。

有关如何在应用程序中使用消息枚举的更多信息,请参见针对所选语言的生成的代码指南。

Reserved Values

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

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

请注意,您不能在同一保留语句中混合使用字段名和数字值。

Using Other Message Types

您可以使用其他消息类型作为字段类型。 例如,假设您要在每条SearchResponse消息中包括结果消息–为此,您可以在同一.proto中定义结果消息类型,然后在SearchResponse中指定结果类型的字段:

message SearchResponse {
  repeated Result results = 1;
}

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

Importing Definitions

在上面的示例中,“结果”消息类型与SearchResponse定义在同一文件中-如果要在另一个.proto文件中定义要用作字段类型的消息类型,该怎么办?

您可以通过导入其他.proto文件使用它们的定义。 要导入另一个.proto的定义,请在文件顶部添加一个import语句:

import "myproject/other_protos.proto";

默认情况下,您只能使用直接导入的.proto文件中的定义。 但是,有时您可能需要将.proto文件移动到新位置。 现在,您可以直接在原始位置放置一个虚拟.proto文件,而不是直接移动.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标志在协议编译器命令行中指定的一组目录中搜索导入的文件。 如果未给出标志,它将在调用编译器的目录中查找。 通常,应将--proto_path标志设置为项目的根目录,并对所有导入使用完全限定的名称。

Using proto2 Message Types

可以导入proto2消息类型并在proto3消息中使用它们,反之亦然。 但是,不能在proto3语法中直接使用proto2枚举(如果导入的proto2消息使用它们,也可以)。

Nested Types

您可以在其他消息类型中定义和使用消息类型,如以下示例所示–在SearchResponse消息中定义了Result消息:

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

Updating A Message Type

如果现有消息类型不再满足您的所有需求(例如,您希望消息格式具有一个额外的字段),但是您仍然希望使用以旧格式创建的代码,请不要担心!在不破坏任何现有代码的情况下更新消息类型非常简单。只要记住以下规则:

  • 不要更改任何现有字段的字段编号。
  • 如果添加新字段,则仍可以使用新生成的代码来解析使用“旧”消息格式通过代码序列化的任何消息。您应该记住这些元素的默认值,以便新代码可以与旧代码生成的消息正确交互。同样,由新代码创建的消息可以由旧代码解析:旧的二进制文件在解析时只会忽略新字段。有关详细信息,请参见“未知字段”部分。
  • 只要在更新的消息类型中不再使用字段号,就可以删除字段。您可能想要重命名该字段,或者添加前缀“ OBSOLETE_”,或者保留该字段编号,以使.proto的将来用户不会意外重用该编号。
  • int32,uint32,int64,uint64和bool都是兼容的–这意味着您可以将字段从这些类型中的一种更改为另一种,而不会破坏向前或向后的兼容性。如果从对应的类型不适合的导线中解析出一个数字,则将获得与在C ++中将数字强制转换为该类型一样的效果(例如,如果将64位数字读取为int32,它将被截断为32位)。
  • sint32和sint64彼此兼容,但与其他整数类型不兼容。
  • 字符串和字节兼容,只要字节是有效的UTF-8。
  • 如果字节包含消息的编码版本,则嵌入式消息与字节兼容。
  • fixed32与sfixed32兼容,fixed64与sfixed64兼容。
  • 对于字符串,字节和消息字段,可选与重复兼容。给定重复字段的序列化数据作为输入,如果期望该字段是可选的,则如果它是原始类型字段,则将采用最后一个输入值;如果是消息类型字段,则将合并所有输入元素。请注意,这对于数字类型(包括布尔值和枚举)通常并不安全。重复的数字类型字段可以以打包格式序列化,当期望使用可选字段时,该格式将无法正确解析。
  • 在有线格式方面,enum与int32,uint32,int64和uint64兼容(请注意,如果值不合适,该值将被截断)。但是请注意,客户端代码在反序列化消息时可能会以不同的方式对待它们:例如,无法识别的proto3枚举类型将保留在消息中,但是反序列化消息时如何表示这取决于语言。 Int字段始终只是保留其值。
  • 将单个值更改为新的oneof的成员是安全且二进制兼容的。如果您确定一次没有代码设置多个字段,那么将多个字段移动到一个新字段中可能是安全的。将任何字段移至现有字段都不安全。

Unknown Fields

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

最初,proto3消息在解析过程中总是丢弃未知字段,但是在版本3.5中,我们重新引入了保留未知字段以匹配proto2行为的功能。 在版本3.5和更高版本中,未知字段将在解析期间保留并包含在序列化输出中。

Any

Any消息类型使您可以将消息用作嵌入式类型,而无需定义它们的.proto。 Any包含任意序列化的消息(以字节为单位)以及URL,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

不同的语言实现将支持运行时库帮助程序以类型安全的方式打包和解压缩Any值-例如,在Java中,Any类型将具有特殊的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 ...
  }
}

当前,正在开发用于任何类型的运行时库。

如果您已经熟悉proto2语法,则Any可以保存任意proto3消息,类似于可以允许扩展的proto2消息。

Oneof

要在.proto中定义oneof,请使用oneof关键字,后跟您的oneof名称,在本例中为test_oneof:

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

然后,将oneof字段添加到oneof定义。 您可以添加任何类型的字段,但地图字段和重复字段除外。

在生成的代码中,oneof字段具有与常规字段相同的getter和setter。 您还将获得一种特殊的方法来检查oneof中的哪个值(如果有)。 您可以在相关的API参考中找到有关所选语言的oneof API的更多信息。

Oneof Features

  • 设置oneof字段将自动清除oneof的所有其他成员。 因此,如果您设置了多个字段,则只有您设置的最后一个字段仍具有值。
SampleMessage message;
message.set_name("name");
CHECK(message.has_name());
message.mutable_sub_message();   // Will clear name field.
CHECK(!message.has_name());
  • 如果解析器在线路上遇到同一个对象的多个成员,则在解析的消息中仅使用最后看到的成员。

  • 一个不能重复。

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

  • 如果将oneof字段设置为默认值(例如将int32 oneof字段设置为0),则将设置该oneof字段的“大小写”,并且该值将在线路上序列化。

  • 如果您使用的是C++,请确保您的代码不会导致内存崩溃。 以下示例代码将崩溃,因为通过调用set_name()方法已经删除了sub_message。

SampleMessage message;
SubMessage* sub_message = message.mutable_sub_message();
message.set_name("name");      // Will delete sub_message
sub_message->set_...            // Crashes here
  • 同样,在C ++中,如果您用aofs交换(两条)消息,则每条消息都将以另一种形式的oneof结尾:在下面的示例中,msg1将具有sub_message,而msg2将具有名称。
SampleMessage msg1;
msg1.set_name("name");
SampleMessage msg2;
msg2.mutable_sub_message();
msg1.swap(&msg2);
CHECK(msg1.has_sub_message());
CHECK(msg2.has_name());

Backwards-compatibility issues

添加或删除字段之一时请多加注意。 如果检查oneof的值返回None / NOT_SET,则可能表示尚未设置oneof或已将其设置为oneof的不同版本中的字段。 由于无法知道导线上的未知字段是否是oneof的成员,因此无法分辨出差异。

Maps

如果要在数据定义中创建关联映射,则协议缓冲区提供了方便的快捷方式语法:

map<key_type, value_type> map_field = N;

...其中key_type可以是任何整数或字符串类型(因此,浮点类型和字节除外的任何标量类型)。 请注意,枚举不是有效的key_type。 value_type可以是除另一个映射以外的任何类型。

因此,例如,如果您想创建一个项目地图,其中每个Project消息都与一个字符串键相关联,则可以这样定义它:

map<string, Project> projects = 3;

  • 映射字段不能重复。
  • 地图值的线格式排序和地图迭代排序是不确定的,因此您不能依赖于地图项的特定顺序。
  • 为.proto生成文本格式时,地图按键排序。 数字键按数字排序。
  • 从导线解析或合并时,如果存在重复的映射键,则使用最后看到的键。 从文本格式解析地图时,如果键重复,则解析可能会失败。
  • 如果为映射字段提供键但没有值,则序列化字段时的行为取决于语言。 在C ++,Java和Python中,类型的默认值是序列化的,而在其他语言中,则没有序列化的值。

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

Backwards compatibility

映射语法与网上的以下语法等效,因此不支持映射的协议缓冲区实现仍可以处理您的数据:

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

repeated MapFieldEntry map_field = N;

任何支持映射的协议缓冲区实现都必须产生并接受上述定义可以接受的数据。

Packages

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

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

然后,您可以在定义消息类型的字段时使用包说明符:

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

包说明符影响生成的代码的方式取决于您选择的语言:

  • 在C ++中,生成的类包装在C ++名称空间中。例如,Open将位于名称空间foo :: bar中。
  • 在Java中,除非您在.proto文件中明确提供选项java_package,否则该包将用作Java包。
  • 在Python中,package指令将被忽略,因为Python模块是根据其在文件系统中的位置进行组织的。
  • 在Go中,除非您在.proto文件中明确提供了go_package选项,否则该包将用作Go包名称。
  • 在Ruby中,生成的类被包装在嵌套的Ruby名称空间中,转换为所需的Ruby大写样式(首字母大写;如果首字符不是字母,则以PB_开头)。例如,Open将位于命名空间Foo :: Bar中。
  • 在C#中,除非转换为.proto文件中明确提供选项csharp_namespace,否则在转换为PascalCase之后,该程序包将用作命名空间。例如,Open将位于命名空间Foo.Bar中。

软件包和名称解析

协议缓冲语言中的类型名称解析类似于C ++:首先搜索最里面的作用域,然后搜索最里面的作用域,依此类推,每个包都被视为其父包“内部”。领先的“。” (例如.foo.bar.Baz)表示从最外面的范围开始。

协议缓冲区编译器通过解析导入的.proto文件来解析所有类型名称。每种语言的代码生成器都知道如何引用该语言中的每种类型,即使它具有不同的范围规则。

Defining Services

如果要将消息类型与RPC(远程过程调用)系统一起使用,则可以在.proto文件中定义RPC服务接口,并且协议缓冲区编译器将以您选择的语言生成服务接口代码和存根。 因此,例如,如果要使用接收SearchRequest并返回SearchResponse的方法来定义RPC服务,则可以在.proto文件中对其进行定义,如下所示:

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

与协议缓冲区一起使用的最直接的RPC系统是gRPC:这是Google开发的与语言和平台无关的开源RPC系统。 gRPC与协议缓冲区配合使用特别好,并允许您使用特殊的协议缓冲区编译器插件直接从.proto文件生成相关的RPC代码。

如果您不想使用gRPC,也可以在自己的RPC实现中使用协议缓冲区。 您可以在《 Proto2语言指南》中找到有关此内容的更多信息。

还有许多正在进行的第三方项目正在为协议缓冲区开发RPC实现。 有关我们知道的项目的链接列表,请参见第三方加载项Wiki页面。

JSON Mapping

Proto3支持JSON中的规范编码,从而使在系统之间共享数据更加容易。 下表按类型对编码进行了描述。

如果JSON编码的数据中缺少某个值,或者该值为null,则在解析为协议缓冲区时,它将被解释为适当的默认值。 如果字段在协议缓冲区中具有默认值,则默认情况下会在JSON编码数据中将其省略以节省空间。 一个实现可以提供选项,以在JSON编码的输出中发出具有默认值的字段。

JSON options

一个proto3 JSON实现可以提供以下选项:

  • 发出具有默认值的字段:默认情况下,proto3 JSON输出中省略具有默认值的字段。 一个实现可以提供一个选项,以使用其默认值覆盖此行为和输出字段。
  • 忽略未知字段:默认情况下,Proto3 JSON解析器应拒绝未知字段,但可以提供在解析时忽略未知字段的选项。
  • 使用proto字段名称代替lowerCamelCase名称:默认情况下,proto3 JSON打印机应将字段名称转换为lowerCamelCase并将其用作JSON名称。 一个实现可以提供一个选项,改为使用原型字段名称作为JSON名称。 Proto3 JSON解析器必须接受转换后的lowerCamelCase名称和原型字段名称。
  • 将枚举值作为整数而不是字符串发送:枚举值的名称在JSON输出中默认使用。 可以提供一个选项来代替使用枚举值的数字值。

Options

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

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

以下是一些最常用的选项:

  • java_package(文件选项):要用于生成的Java类的包。 如果.proto文件中未提供显式的java_package选项,则默认情况下将使用proto软件包(在.proto文件中使用“ package”关键字指定)。 但是,proto软件包通常不能作为Java包,因为proto软件包不应以反向域名开头。 如果未生成Java代码,则此选项无效。
option java_package = "com.example.foo";
  • java_multiple_files(文件选项):使顶级消息,枚举和服务在程序包级别定义,而不是在以.proto文件命名的外部类内部定义。
option java_multiple_files = true;
  • java_outer_classname(文件选项):您要生成的最外层Java类的类名(以及文件名)。 如果在.proto文件中未指定显式的java_outer_classname,则通过将.proto文件名转换为驼峰式大小写来构造类名(因此foo_bar.proto变为FooBar.java)。 如果未生成Java代码,则此选项无效。
option java_outer_classname = "Ponycopter";
  • optimize_for(文件选项):可以设置为SPEED,CODE_SIZE或LITE_RUNTIME。 这会通过以下方式影响C ++和Java代码生成器(可能还有第三方生成器):
    [1] SPEED(默认):协议缓冲区编译器将生成代码,用于对消息类型进行序列化,解析和执行其他常见操作。此代码已高度优化。
    [2] CODE_SIZE:协议缓冲区编译器将生成最少的类,并将依赖于基于反射的共享代码来实现序列化,解析和其他各种操作。因此,生成的代码将比使用SPEED的代码小得多,但是操作会更慢。类仍将实现与在SPEED模式下完全相同的公共API。此模式在包含大量.proto文件且不需要所有文件都快速达到要求的应用程序中最有用。
    [3] LITE_RUNTIME:协议缓冲区编译器将生成仅依赖于“ lite”运行时库的类(libprotobuf-lite而非libprotobuf)。精简版运行时比完整库要小得多(大约小一个数量级),但省略了某些功能,例如描述符和反射。这对于在受限平台(例如手机)上运行的应用程序特别有用。编译器仍将像在SPEED模式下一样快速生成所有方法的实现。生成的类将仅以每种语言实现MessageLite接口,该接口仅提供完整Message接口方法的子集。
option optimize_for = CODE_SIZE;
  • cc_enable_arenas(文件选项):启用C ++生成代码的舞台分配。

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

  • 不推荐使用(字段选项):如果设置为true,则表示不推荐使用该字段,新代码不应使用该字段。 在大多数语言中,这没有实际效果。 在Java中,这成为@Deprecated注释。 将来,其他特定于语言的代码生成器可能会在该字段的访问器上生成弃用注释,这反过来将导致在编译尝试使用该字段的代码时发出警告。 如果该字段未被任何人使用,并且您想阻止新用户使用它,请考虑使用保留语句替换字段声明。

int32 old_field = 6 [deprecated = true];

Custom Options

协议缓冲区还允许您定义和使用自己的选项。 这是大多数人不需要的高级功能。 如果您确实需要创建自己的选项,请参阅《 Proto2语言指南》以了解详细信息。 请注意,创建自定义选项使用扩展名,扩展名仅适用于proto3中的自定义选项。

Generating Your Classes

要生成Java,Python,C ++,Go,Ruby,Objective-C或C#代码,您需要使用.proto文件中定义的消息类型,您需要在.proto上运行协议缓冲区编译器协议。 如果尚未安装编译器,请下载软件包并按照自述文件中的说明进行操作。 对于Go,您还需要为编译器安装一个特殊的代码生成器插件:您可以在GitHub上的golang / 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文件的目录。 如果省略,则使用当前目录。 可以通过多次传递--proto_path选项来指定多个导入目录。 将按顺序搜索它们。 -I = _IMPORT_PATH_可以用作--proto_path的缩写。

  • 您可以提供一个或多个输出指令:
    --cpp_out在DST_DIR中生成C ++代码。有关更多信息,请参见C ++生成的代码参考。
    --java_out在DST_DIR中生成Java代码。有关更多信息,请参见Java生成的代码参考。
    --python_out在DST_DIR中生成Python代码。有关更多信息,请参见Python生成的代码参考。
    --go_out在DST_DIR中生成Go代码。有关更多信息,请参见Go生成的代码参考。
    --ruby_out在DST_DIR中生成Ruby代码。 Ruby生成的代码参考即将推出!
    --objc_out在DST_DIR中生成Objective-C代码。有关更多信息,请参见Objective-C生成的代码参考。
    --csharp_out在DST_DIR中生成C#代码。有关更多信息,请参见C#生成的代码参考。
    --php_out在DST_DIR中生成PHP代码。欲了解更多便利,请参见PHP生成的代码参考。如果DST_DIR以.zip或.jar结尾,则编译器会将输出写入给定名称的单个ZIP格式存档文件。根据Java JAR规范的要求,还将为.jar输出提供清单文件。注意,如果输出存档已经存在,它将被覆盖;编译器不够 智能,无法将文件添加到现有存档中。

  • 您必须提供一个或多个.proto文件作为输入。 可以一次指定多个.proto文件。 尽管这些文件是相对于当前目录命名的,但是每个文件都必须位于IMPORT_PATH之一中,以便编译器可以确定其规范名称。

参考文档:https://developers.google.com/protocol-buffers/docs/proto3

posted @ 2020-09-28 13:46  ddockerman  阅读(2439)  评论(1编辑  收藏  举报