【译】protobuf语法指南

protobuf语法指南

    • 一、写在前面
    • 二、protobuf指南 (proto3)
      • 1、定义消息类型-Defining A Message Type
      • 2、标量值类型-Scalar Value Types
      • 3、默认值-Default Values
      • 4、枚举-Enumerations
      • 5、使用其他消息类型-Using Other Message Types
      • 6、嵌套类型-Nested Types
      • 7、更新消息类型-Using Other Message Types
      • 8、未知字段-Unknown Fields
      • 9、任何-Any
      • 10、一个-Oneof
      • 11、地图-map
      • 12、包-Packages
      • 13、定义服务-Defining Services
      • 14、JSON 映射
      • 15、选项-Options
      • 16、生成访问器类-Generating Your Classes

一、写在前面

使用protobuf一年多的时间,使用过C++版和python的接口,发现使用过程中还会遇到一些问题,本想自己总结一下,但是想想,自己怎么总结也没有官方的权威,这里根据protobuf官网翻译总结整理一下protobuf的语法,以方便自己查询,也可以给需要的人提供一些帮助。如果你英文还可以的话,还是建议你参考官方文档。如果英文不好,更要尝试锻炼一下英文的能力。

二、protobuf指南 (proto3)

定义消息类型-Defining A Message Type
标量值类型-Scalar Value Types
默认值-Default Values
枚举-Enumerations
使用其他消息类型-Using Other Message Types
嵌套类型-Nested Types
更新消息类型-Using Other Message Types
未知字段-Unknown Fields
任何-Any
一个-Oneof
地图-Maps
包-Packages
定义服务-Defining Services
JSON 映射-JSON Mapping
选项-Options
生成访问类-Generating Your Classes

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

1、定义消息类型-Defining A Message Type

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

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

第一行指定使用proto3语法:如果不这样做,protocol buffer编译器将假定使用的是proto2,这必须是文件的第一个非空、非注释行。SearchRequest消息定义指定了三个字段(名称/值对),每个字段都有一个名称和一个类型。

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

分配字段编号
消息定义中的每个字段都有一个唯一的编号。这些字段编号用于在消息二进制格式中标识你的字段,一旦消息类型被使用,就不应更改。

请注意:1 到 15 范围内的字段编号占用一个字节进行编码,包括字段编号和字段类型(可以在协议缓冲区编码中找到更多相关信息),16 到 2047 范围内的字段编号占用两个字节。

因此,要为非常频繁出现的消息元素保留数字 1 到 15。记住为将来可能添加的频繁出现的元素留出一些空间。

可以指定的最小字段编号为 1,最大字段编号为 2^29 - 1 或 536,870,911。不能使用数字 【19000 到 19999】,它们是为 Protocol Buffers预留字段。

指定字段规则
消息字段修饰符必须是以下之一:

  • singular:格式正确的消息可以有零个或一个此字段(但不超过一个),这是 proto3 语法的默认字段规则。
  • repeated:该字段可以在格式良好的消息中重复任意次数(包括零次)。重复值的顺序将被保留。
    在 proto3 中,repeated标量数值类型的字段packed默认使用编码。
    可以packed在Protocol Buffer Encoding 中找到有关编码的更多信息。

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

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

添加评论
要向.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.
}

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

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

请注意:不能在同一reserved语句中混合使用字段名称和字段编号。

你的proto文件是怎么生成的?

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

  • 对于C++编译器从每一个生成一个.h 和 .cc文件,文件中.proto描述的每个消息类型都有一个类。

  • 对于Java编译器,.java为每个消息类型生成一个带有类的文件,以及Builder用于创建消息类实例的特殊类。

  • 对于Kotlin,除了 Java 生成的代码之外,编译器还为.kt每种消息类型生成一个文件,其中包含可用于简化创建消息实例的 DSL。

  • Python有点不同——Python 编译器会生成一个模块,其中包含 中每种消息类型的静态描述符,.proto然后与元类一起使用以在运行时创建必要的 Python 数据访问类。

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

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

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

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

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

2、标量值类型-Scalar Value Types

标量消息字段可以具有以下类型之一——该表显示了.proto文件中指定的类型,以及自动生成的类中的相应类型:
【译】protobuf语法指南_第1张图片
【译】protobuf语法指南_第2张图片
[1] Kotlin 使用来自Java的相应类型,即使是无符号类型,以确保在混合 Java/Kotlin 代码库中的兼容性。
[2]在Java中,无符号32位和64位整数使用它们的有符号对应物表示,最高位简单地存储在符号位中。
[3]在所有情况下,为字段设置值将执行类型检查以确保其有效。
[4]64位或无符号32位整数在解码时总是表示为 long,但如果在设置字段时给出 int,则可以是 int。在所有情况下,该值必须适合设置时表示的类型。见[2]。
[5]Python 字符串在解码时表示为 unicode,但如果给出 ASCII 字符串,则可以是 str(这可能会发生变化)。
[6]64 位机器上使用整数,32 位机器上使用字符串。

3、默认值-Default Values

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

  • 对于string,默认值为空字符串。
  • 对于bytes,默认值为空字节。
  • 对于bool,默认值为 false。
  • 对于numeric,默认值为零。
  • 对于enums,默认值是第一个定义的 enum value,它必须是 0。
  • 对于message fields,未设置该字段,它的确切值取决于语言。
  • 重复字段的默认值为空(通常是相应语言的空列表)。

请注意,对于标量消息字段,一旦消息被解析,就无法判断字段是否明确设置为默认值(例如,布尔值是否设置为false)或根本没有设置。在定义消息类型时,应该记住这一点。例如,false如果不希望默认情况下也发生该行为,则不要设置一个布尔值来开启某些行为。还要注意的是,如果一个标消息字段被设置为默认值,该值将不会进行序列化。

4、枚举-Enumerations

当定义消息类型时,可能希望它的一个字段只有一个预定义的值列表。例如,假设想添加一个corpus字段每个SearchRequest,其中语料库可以UNIVERSAL,WEB,IMAGES,LOCAL,NEWS,PRODUCTS或VIDEO。可以通过enum为每个可能的值添加一个常量到消息定义中来非常简单地做到这一点。
在下面的示例中,添加了一个具有所有可能值的enum调用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 位整数范围内。由于enum值使用可变的编码,负值效率低下,因此不推荐使用。enum可以在消息定义中,enum也可以在外部定义 。这些值可以在.proto文件中的任何消息定义中重用。还可以使用enum在一条消息中声明的类型作为另一条消息中的字段类型,使用语法_MessageType_.EnumType.
当对一个使用了枚举的.proto文件运行protocol buffer编译器的时候,生成的代码将具有对应enum的 Java、Kotlin 或 C++ 或EnumDescriptorPython的特殊类,用于在运行时创建一组具有整数值的符号常量 -生成的类。

在反序列化期间,无法识别的枚举值将保留在消息中,尽管在反序列化消息时如何表示取决于语言。在支持值超出指定符号范围的开放枚举类型的语言(例如 C++ 和 Go)中,未知枚举值只是作为其底层整数表示存储。在 Java 等具有封闭枚举类型的语言中,枚举中的 case 用于表示无法识别的值,并且可以使用特殊访问器访问底层整数。在任何一种情况下,如果消息被序列化,无法识别的值仍将与消息一起序列化。

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

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

注意:不能在同一reserved语句中混合使用字段名称和数值。

5、使用其他消息类型-Using Other Message Types

使用其他消息类型作为字段类型。例如,假设Result在每条SearchResponse消息中包含消息 - 可以在其中定义一个Result消息类型.proto,然后指定一个类型为Resultin的字段SearchResponse:

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

导入定义
注意:此功能在 Java 中不可用。
在上面的例子中,Result消息类型是在同一个文件中定义的SearchResponse——如果想用作字段类型的消息类型已经在另一个.proto文件中定义了怎么办?
可以.proto通过导入其他文件中的定义来使用它们。要导入 another.proto的定义,请在文件顶部添加一个 import 语句:

import "myproject/other_protos.proto";

默认情况下,只能使用来自直接导入.proto文件的定义。但是,有时可能需要将.proto文件移动到新位置。无需.proto直接移动文件并在一次更改中更新所有调用站点,现在可以.proto在旧位置放置一个虚拟文件,以使用该import public概念将所有导入转发到新位置。import public任何导入包含该import public语句的 proto 都可以传递地依赖该proto。例如:

// 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.protoimport "old.proto";// You use definitions from old.proto and new.proto, but not other.proto

protocol buffer编译器使用-I/–proto_path标志在协议编译器命令行上指定的一组目录中搜索导入的文件。如果没有给出标志,它会在调用编译器的目录中查找。通常,应该将–proto_path标志设置为项目的根目录,并对所有导入使用完全限定名称。

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

6、嵌套类型-Nested Types

可以在其他消息类型中定义和使用消息类型,如下例所示——这里的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;    
             }  
     }
 }

7、更新消息类型-Using Other Message Types

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

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

8、未知字段-Unknown Fields

未知字段是格式良好的protocol buffer序列化数据,表示解析器无法识别的字段。例如,当旧二进制文件用新字段解析新二进制文件发送的数据时,这些新字段将成为旧二进制文件中的未知字段。

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

9、任何-Any

该Any消息类型,可以使用邮件作为嵌入式类型,而不必自己.proto定义。一个Any包含一个任意序列化的消息 as bytes,以及一个 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()andunpack()访问器,而在 C++ 中有PackFrom()andUnpackTo()方法:

// 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可以保存任意 proto3 消息,类似于可以允许扩展的proto2 消息。

10、一个-Oneof

如果消息包含多个字段并且最多同时设置一个字段,则可以强制执行此行为并使用 oneof 功能节省内存。

oneof字段和普通字段一样,除了oneof共享内存中的所有字段外,最多可以同时设置一个字段。设置 oneof 的任何成员会自动清除所有其他成员。可以使用特殊case()或WhichOneof()方法检查 oneof 中设置的值(如果有),具体取决于选择的语言。

使用 Oneof
要在.proto中使用oneof,可以在要使用的名字前加上关键字oneof,如下test_oneof:

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

然后将 oneof 字段添加到proto消息定义中。可以添加任何类型的字段,map和repeated字段除外。
在生成的代码中,oneof 字段与常规字段具有相同的 getter 和 setter。还可以获得一种特殊的方法来检查设置了 oneof 中的哪个值(如果有)。

一个特点
设置 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 的多个成员,则在解析的消息中仅使用看到的最后一个成员。
  • oneof 不能是repeated。
  • 反射 API 适用于 oneof 字段。
  • 如果将 oneof 字段设置为默认值(例如将 int32 oneof 字段设置为 0),则将设置该 oneof 字段的“大小写”,并且该值将被序列化。
  • 如果使用 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()两条消息带有 oneof,则每条消息都会以另一个的 oneof 情况结束:在下面的示例中,msg1将有一个sub_message和msg2将有一个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 字段。
拆分或合并 oneof:这与移动常规字段有类似的问题。

11、地图-map

如果想创建一个关联映射作为数据定义的一部分,protocol buffers 提供了一个方便的快捷语法:
map map_field = N;
…其中key_type可以是任何整数或字符串类型(因此,除了浮点类型和bytes之外的任何标量类型)。请注意, enum 不是有效的key_type, value_type可以是任何类型的,除了map类型。
因此,例如,想创建一个项目映射,其中每个Project消息都与一个字符串键相关联,可以像这样定义它:

map<string, Project> projects = 3;

map字段不能是repeated。
映射值的Wire格式排序和映射迭代排序未定义,因此不能依赖特定顺序的映射项。
为 一个.proto生成文本格式时,map按键存储,数字键按数字排序。
从关联进行分析或合并时,如果存在重复的映射键,则使用最后看到的键。从文本格式解析映射时,如果存在重复键,则解析可能会失败。
如果为映射字段提供键但没有值,则该字段序列化时的行为取决于语言。在 C++、Java、Kotlin 和 Python 中,类型的默认值是序列化的,而在其他语言中则没有序列化。
生成的map API目前可用于所有 proto3 支持的语言。

向后兼容
map 语法等效于以下内容,因此不支持 map的protocol buffers实现仍然可以处理数据:

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

任何支持映射的protocol buffers实现都必须生成和接受上述定义可以接受的数据。

12、包-Packages

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

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

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

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

包说明符影响生成代码的方式取决于选择的语言:
在C++ 中,生成的类被包装在 C++ 命名空间中。例如,Open将在命名空间中foo::bar。
在Java和Kotlin 中,该包用作 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。

包和名称解析
protocol buffer语言中的类型名称解析的工作方式与 C++ 类似:首先搜索最内部的范围,然后搜索下一个最内部的范围,依此类推,每个包都被认为是其父包的“内部”。一个领先的’.’ (例如,.foo.bar.Baz) 表示从最外层作用域开始。
protocol buffer编译器通过解析导入的.proto文件来解析所有类型名称。每种语言的代码生成器都知道如何引用该语言中的每种类型,即使它具有不同的范围规则。

13、定义服务-Defining Services

如果想在 RPC(远程过程调用)系统中使用你的消息类型,可以在.proto文件中定义一个 RPC 服务接口,protocol buffer编译器将以选择的语言生成服务接口代码和存根。因此,例如,如果想使用接受SearchRequest并返回一个的方法定义 RPC 服务SearchResponse,可以在你的.proto文件中定义它,如下所示:

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

与protocol buffer一起使用的最直接的 RPC 系统是gRPC:Google 开发的一种语言和平台中立的开源 RPC 系统。gRPC 特别适用于协议缓冲区,并允许.proto使用特殊的协议缓冲区编译器插件直接从文件中生成相关的 RPC 代码。如果不想使用 gRPC,也可以在自己的 RPC 实现中使用协议缓冲区。

14、JSON 映射

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

【译】protobuf语法指南_第3张图片
【译】protobuf语法指南_第4张图片

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

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

15、选项-Options

.proto文件中的各个声明可以用许多选项进行注释。选项不会改变声明的整体含义,但可能会影响它在特定上下文中的处理方式。可用选项的完整列表在 中定义google/protobuf/descriptor.proto。
有些选项是文件级选项,这意味着它们应该写在顶级范围内,而不是在任何消息、枚举或服务定义中。一些选项是消息级别的选项,这意味着它们应该写在消息定义中。有些选项是字段级选项,这意味着它们应该写在字段定义中。选项也可以写在枚举类型、枚举值、一个字段、服务类型和服务方法上;然而,目前没有任何有用的选择。

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

  • java_package(文件选项):要用于生成的 Java/Kotlin 类的包。如果文件中未java_package给出显式选项.proto,则默认情况下将使用 proto 包(使用文件中的“package”关键字指定.proto)。然而,proto 包通常不会成为好的 Java 包,因为 proto 包不会以反向域名开头。如果不生成 Java 或 * Kotlin 代码,则此选项无效。
    option java_package = “com.example.foo”;

  • java_outer_classname(文件选项):要生成的包装 Java 类的类名(以及文件名)。如果文件中没有明确java_outer_classname指定.proto,类名将通过将.proto文件名转换为驼峰式大小写来构造(因此foo_bar.proto变为FooBar.java)。如果该java_multiple_files选项被禁用,则所有其他类/枚举/等。为.proto文件生成的将在此外部包装器 Java 类中生成为嵌套类/枚举/等。如果不生成 Java 代码,则此选项无效。
    option java_outer_classname = “Ponycopter”;

  • java_multiple_files(文件选项):如果为 false,则只.java为该.proto文件生成一个文件,所有 Java 类/枚举/等。为顶级消息、服务和枚举生成的消息将嵌套在外部类中(请参阅 参考资料java_outer_classname)。如果为 true,.java将为每个 Java 类/枚举/等生成单独的文件。为顶级消息、服务和枚举生成,并且为此.proto文件生成的包装器 Java 类将不包含任何嵌套类/枚举/等。这是一个布尔选项,默认为false。如果不生成 Java 代码,则此选项无效。
    option java_multiple_files = true;

  • optimize_for(文件选项):可以设置为SPEED、CODE_SIZE、 或LITE_RUNTIME。这会通过以下方式影响 C++ 和 Java 代码生成器(以及可能的第三方生成器):

SPEED(默认):协议缓冲区编译器将生成用于序列化、解析和对您的消息类型执行其他常见操作的代码。这段代码是高度优化的。

CODE_SIZE:protocol buffer 编译器将生成最少的类,并将依赖共享的、基于反射的代码来实现序列化、解析和各种其他操作。因此生成的代码将比 with 小得多SPEED,但操作会更慢。类仍将实现与它们在SPEED模式中完全相同的公共 API 。此模式在包含大量.proto文件且不需要所有文件都非常快的应用程序中最有用。

LITE_RUNTIME:protocol buffer 编译器将生成仅依赖于“lite”运行时库(libprotobuf-lite而不是libprotobuf)的类。lite 运行时比完整库小得多(大约小一个数量级),但省略了某些功能,如描述符和反射。这对于在手机等受限平台上运行的应用程序特别有用。编译器仍然会像在SPEEDmode 中那样生成所有方法的快速实现。生成的类只会实现MessageLite每种语言的接口,它只提供完整Message接口的方法的一个子集。option optimize_for = CODE_SIZE;

  • cc_enable_arenas(文件选项):为 C++ 生成的代码启用arena 分配。

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

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

自定义选项
Protocol Buffers 还允许定义和使用自己的选项。这是大多数人不需要的高级功能。如果确实认为需要创建自己的选项,请参阅Proto2 语言指南了解详细信息。请注意,创建自定义选项使用extensions,仅允许用于 proto3 中的自定义选项。

16、生成访问器类-Generating Your Classes

要生成 Java、Kotlin、Python、C++、Go、Ruby、Objective-C 或 C# 代码,需要使用.proto文件中定义的消息类型,需要protoc在.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解析import指令时查找文件的目录。如果省略,则使用当前目录。通过–proto_path多次传递选项可以指定多个导入目录;将按顺序搜索它们。-I=_IMPORT_PATH_可以用作–proto_path.

可以提供一个或多个输出指令:

  • –cpp_out生成 C++ 代码DST_DIR。有关更多信息,请参阅C++ 生成的代码参考。
  • –java_out生成 Java 代码DST_DIR。有关更多信息,请参阅Java 生成的代码参考。
  • –kotlin_out生成额外的 Kotlin 代码DST_DIR。有关更多信息,请参阅Kotlin 生成的代码参考。
  • –python_out生成 Python 代码DST_DIR。有关更多信息,请参阅Python 生成的代码参考。
  • –go_out在DST_DIR. 有关更多信息,请参阅Go 生成的代码参考。
  • –ruby_out生成 Ruby 代码DST_DIR。Ruby 生成的代码参考即将推出!
  • –objc_out在DST_DIR. 有关更多信息,请参阅Objective-C 生成的代码参考。
  • –csharp_out生成 C# 代码DST_DIR。有关更多信息,请参阅C# 生成的代码参考。
  • –php_out生成 PHP 代码DST_DIR。有关更多信息,请参阅PHP 生成的代码参考。

为方便起见,如果DST_DIR以.zip或结尾.jar,编译器会将输出写入具有给定名称的单个 ZIP 格式存档文件。.jar输出还将根据 Java JAR 规范的要求提供清单文件。请注意,如果输出存档已经存在,它将被覆盖;编译器不够聪明,无法将文件添加到现有存档中。

各种语言的相关生成代码,请参考官方文档:https://developers.google.com/protocol-buffers/docs/tutorials

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

本文根据protobuf官网翻译整理而成,能力有限,时间有限,整理匆忙,如有错误,欢迎指正!

你可能感兴趣的:(无人驾驶,protobuf,protobuf3,protobuf使用,protobuf语法,protobuf语法指南)