百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

protobuf 3 文档翻译 prop翻译

liebian365 2024-10-24 14:38 13 浏览 0 评论

1. protobuf语法-(proto3)

本文描述了如何使用prototol buffer(简称pb)语言来构建你的pb数据,内容包括:后缀为.proto的文件语法(语法:syntax)以及如何从.proto文件生成自己语言的数据访问类(数据访问类:data access class)。本文档中使用pb的proto3版本,proto2版本的pb语法看Proto2 Language Guide。

本文是一个参考指南,用来一步一步展示文档中的各种特性。

1.1. 定义一个Message

首先,看一个非常简单的示例。假如你想定义一个搜索请求(搜索请求:search request)的message,那么就需要有query的string字段、你感兴趣的结果所在的数据页的页码page_number以及每页上的结果的数量result_per_page。

syntax = "proto3";

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}
  • 文件的第一行声明标识你使用proto3语法:如果你不写这句声明,pb编译器会假设你使用的proto2。它必须是.proto文件中第一个非空、非注释的语句。
  • SearchRequest类型中声明了三个字段,每个字段都是键值对,都是用来在SearchRequest中储存对应信息的。每个字段都有一个名字和类型。

1.1.1. 声明字段类型

上面的代码中,所有的字段都是scale 类型:两个integer类型(page_number和result_per_page)以及一个string类型(query)。然而,你也可以把字段定义成复合类型(复合类型:composite type),包括枚举类型(枚举类型:enumerations)和其他的message类型。

1.1.2. 分配字段编号

如你所见,message定义中的每个字段都有一个唯一编号。这些编号用来在message编码后的二进制数据中来区分各个字段,一旦你的message开始使用就不可以改变其字段的编号。1-15的字段号使用一个字节进行编码,内容包括字段号和字段类型(在Protocol Buffer Encoding这一章中可以找到更多编码信息。16-2047的字段号占用两个字节进行编码。因此,你应该把1-15保留为经常使用的字段编号。记得要保留几个1-15的编号,以便为后续添加的频繁使用的字段进行编号。

可以使用的最小字段号是1,最大是2^{29}-1或536870911.你不可以使用19000到19999作为字段号,因为它们是为Protocol Buffer的实现而保留的,如果你在.proto文件中用了某一个保留的字段号,pb的编译器就会报错。同样的,你也不可以使用之前使用reserved标记的保留字段号。

1.1.3. 指定字段规则

message的字段可以是下面的一种:

  • singular规则:一个message可以有零个或者一个singular字段(但是不会超过一个)。这也是proto3语法的默认字段规则。
  • repeated规则:使用repeated修饰的字段,可以重复任何次(包括零次)。(其实就是一个数组)。repeated数组中元素的顺序是固定的。

在proto3中,repeated修饰的字段默认使用packed编码。

在Protocol Buffer Encoding中可以找到更多关于packed编码的信息。

1.1.4. 添加更多的message

在一个.proto文件中可以定义多个message。如果你想定义多个有联系的message,这个特性是很有用的。例如,你想定义SearchResponse作为你的响应message,你就可以把它加到相同的.proto文件中。

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

message SearchResponse {
 ...
}

1.1.5. 添加注释

在.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.
}

1.1.6. 保留字段

如果你通过删除或注释字段修改了一个message,之后的开发者在修改这个message的时候可能会重新使用这个字段号。如果他们之后加载旧版本的.proto文件,可能导致严重的问题,例如数据污染、权限漏洞等。避免这个问题的方法就是指定你删除的字段号(和字段名,因为字段名重复使用也可能导致JSON序列化的问题)是reserved。后续的开发者如果想使用这些字段标识,pb编译器都会报错。

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

注意,在同一个reserved语句中不能同时声明字段号和字段名。

1.1.7. 从你的.proto文件生成什么东西

当你在.proto文件上运行pb编译器,编译器以所选语言生成代码。你是用这种语言来操作这些message类型,包括获取和设置字段值,将message序列化为输出流,以及从输入流解析message。

  • 对于C ++,编译器从每个.proto生成一个.h和.ccc文件,以及文件中描述的每条message的类。
  • 对于Java,编译器为每个message的都生成一个类并写在.java文件,以及用于创建message类实例的特殊Builder类。
  • 对于Kotlin,除了Java生成的代码之外,编译器还为每个message生成一个.kt文件,其中包含可用于简化创建消息实例的DSL。
  • Python有点不同 - Python编译器为.proto中的每一个message生成一个具有静态描述符的模块,然后与metaclass一起使用,以在运行时创建必要的python数据访问类。
  • 对于Go,编译器在.pb.go文件中为每一个message生成对应的类型。
  • 对于Ruby,编译器生成一个.rb文件,内容是包括每个message的Ruby模块。
  • ...

文档后续有相关语言的API,具体可见API reference

1.2. Scalar值类型

一个message的字段可以是以下的类型 - 这个表中显示了.proto文件中可以使用的类型以及在不同语言中生成对应类型。

.proto Type

Notes

C++ Type

Java Type

Python Type[2]

Go Type

double


double

double

float

*float64

float


float

float

float

*float32

int32

使用动态长度编码,编码负数时效率很低。如果你的字段可能有负数,使用sint32。

int32

int

int

*int32

int64

使用动态长度编码,编码负数时效率很低。如果你的字段可能有负数,使用sint64。

int64

long

int/long[3]

*int64

uint32

使用动态长度编码。

uint32

int[1]

int/long[3]

*uint32

uint64

使用动态长度编码。

uint64

long[1]

int/long[3]

*uint64

sint32

使用动态长度编码。有符号整型。编码负数的效率要高于int32。

int32

int

int

*int32

sint64

使用动态长度编码。有符号整型。编码负数的效率要高于int64。

int64

long

int/long[3]

*int64

fixed32

使用四个字节编码。如果值大多大于228,编码效率要高于uint32。

uint32

int[1]

int/long[3]

*uint32

fixed64

使用四个字节编码。如果值大多大于256,编码效率要高于uint64。

uint64

long[1]

int/long[3]

*uint64

sfixed32

使用四个字节编码。

int32

int

int

*int32

sfixed64

使用八个字节编码。

int64

long

int/long[3]

*int64

bool


bool

boolean

bool

*bool

string

字符串应该是UTF-8编码或者七位ASCII编码。

string

String

unicode (Python 2) or str (Python 3)

*string

bytes

任意的字节数组。

string

ByteString

bytes

[]byte

在Protocol Buffer Encoding中你可以看到这些类型在序列化时是如何编码的。

1.3. 默认值

解析编码后的message时,如果message有个singular类型的字段没有指定值,那么解析出来的相关字段都会设置为默认值。

  • string:默认值是空字符串
  • bytes:默认值是空byte数组
  • bool:默认是false
  • 数字:默认是0
  • enum:默认值是定义的第一个枚举值,肯定是0
  • message:它的默认值取决于语言的实现。

repeated字段的默认值为空(通常是一个空数组)。

注意:message的字段中,一旦一个message被解析出来,我们就不知道它是被设置为默认值(例如有个bool被设置为false)或者它并没有被设置;你在定义message类型的时候需要注意这一点。例如,不要定义一个在某些情况需要设置为false的bool类型。同时注意,如果一个message字段设置为默认值,它的值在传输时就不会被序列化。

在generated code guide可以查看你使用的语言如何生成代码。

1.4. Enumerations

当你定义一个message,可能需要某一个字段的值在一个预定义的列表中。例如,你想要为SearchRequest添加一个corpus字段,corpus有这几种值:UNIVERSAL、WEB、IMAGES、LOCAL、NEWS、PRODUCES或VIDEO。通过在你的message定义处添加一个具有多个常量的枚举类型就可以实现这个功能。

下面代码中,我们添加了一个Corpus的枚举类型,这个类型具有上面提到的七种值;同时在message中添加了一个类型为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:每个枚举类型的定义必须包括一个映射为0的常量作为它的第一个成员。这是因为:

  • 必须存在一个零值,因为需要使用0作为默认值
  • 零值需要是第一个元素,这是为了与proto2语义兼容,proto2中第一个枚举值始终是默认值

你可以通过给不同的枚举变量赋予相同的值来定义别名(别名:alias)。前提是你需要设置allow_alias选项为true,否则pb编译器会报error异常。

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位的integer。因为枚举使用varint encoding进行编码,负数在编码时的效率很低因此是不推荐使用的。如上面代码所示,你可以在message内部定义枚举,也可以在message外部定义。你也可以在把一个message的枚举作为另一个message中的字段,使用这个语法:_MessageType_._EnumType_。

当使用pb编译器编译.proto文件中的枚举类型时,生成的代码中将具有对应的 Java、Kotlin 或 C++ 枚举,或用于 Python 的特殊 EnumDescriptor 类,用于在运行时生成的类中创建一组具有整数值的符号常量。

注意: 生成的代码可能会受到特定语言的枚举数限制(一种语言低至数千)。请查看你使用的语言的限制。

反序列化时,message中会保留未识别出来的枚举值,具体的表现形式取决于语言的实现。在C++和golang中,枚举类型的值可以在其指定范围之外,因此未识别出来的值会简单存储为一个integer。在Java等的枚举类型中,枚举中专门有一种情况用来标识未识别的值,它可以通过特殊的访问机制来获取具体内容。在其他情况下,如果message被序列化了,这个无法识别的值也会随message序列化。

1.4.1. 保留值

如果你通过删除或注释掉枚举的一个条目来更新这个枚举类型,未来的开发者可能会重新使用这个枚举值来实现他们对这个枚举类型的修改。如果他们在之后使用了旧版本的.proto文件会导致严重问题,例如数据污染,权限漏洞等。保证这个不再发生的方法就是枚举条目的值和名字都声明为reserved。如果未来开发者使用这些条目名或者值,pb编译器就会报错。你可以使用max关键字来声明你保留的条目值的范围一直到最大值。

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

注意:不能在同一行reserved语句中同时添加枚举成员的名和值。

1.5. 使用其他message类型

你可以使用其他的message作为当前message字段的类型。例如,你想在SearchResponse中包括Result,你可以在当前.proto文件中定义一个Result,然后在SearchResponse中声明一个Result字段。

message SearchResponse {
  repeated Result results = 1;
}

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

1.5.1. 导入已定义的数据类型

本节的特性不适合Java。

在上面的例子中,Result和SearchResponse定义在同一个.proto文件中,如果你想使用的message已经定义在另一个.proto文件中呢?

你可以通过在当前.proto文件中import其他的.proto文件以使用已有的类型,例如,在文件的开头可以这么写:

import "myproject/other_protos.proto";

这样,你只能使用other_protos.proto中的类型定义。然而,你可能需要把一个.proto文件移动到一个新的位置。你可以把.proto文件移动到新的位置并在所有import它的.proto文件中更新其路径;但是还有更简单的方法,就是在原来的路径上放一个假的.proto文件,使用import public来将import重定向到新的.proto文件中。任何导入具有import public声明的.proto文件的.proto文件,可以进行依赖的传递。(这么说有点不明白,看下面的代码。new.proto就是原来的.proto文件的新名字,而在.proto文件的原来位置即old.proto中使用了import public声明。这样,当client.proto中import了old.proto文件,client.proto也可以使用new.proto的定义。从另一个角度说,old.proto继承了new.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.proto
import "old.proto";
// You use definitions from old.proto and new.proto, but not other.proto

pb编译器通过-I/--proto_path标志获取的路径下查找导入的.proto文件。如果没有这个标志的参数,它会在运行编译器的当前路径下查找。通常情况下,你应该将--proto_path标志设置为项目的根目录,并为所有的需要导入的.proto文件提供完全的路径名。

1.5.2. 使用proto2的message类型

在proto3的message中可以导入使用proto2的message,反之亦然。然而,proto2的枚举无法直接在proto3语法中使用,除非一个ptoro2的message使用了它们。

1.6. 嵌套类型

你可以在一个message定义内部再定义或使用另一个message,如下代码所示。这里,这个Result就是定义在SearchResponse内部。

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

如果你想在与SearchResponse同级的message中使用Result,就需要使用SearchResponse.Result来进行定义。

message SomeOtherMessage {
  SearchResponse.Result result = 1;
}

在message中你想嵌套多少就能嵌套多少层message。

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

1.7. 更新一个message

如果已有的message已经无法满足你的需求了,例如,你需要这个message增加一个额外的字段,但是你还需要使用原来的message来开发。不要怕,更新你的message是很简单的,而且不会破坏你原有的代码。只要记住以下几条规则:

  • 不要改变已有字段的字段号
  • 如果你新增一个字段,新代码也可以解析你的旧message序列化后的内容。你应该记住这些字段的默认值,以便新代码与旧代码生成的message能够正确交互。同样,由你的新代码创建的消息可以由旧代码解析:旧代码在解析时会忽略新的字段。具体内容见未知字段的具体内容。
  • 字段可以被删除,但是它的字段号在更新的message不可以使用。你可能想重命名这些字段,例如在字段名开头加OBSULETE,或者让字段编号保留(保留:reserved),所以未来的开发者不会使用这些字段号。
  • int32,uint32,int64,uint64和bool是互相兼容的,这意味着你可以把一个这些类型的字段任意转换成另一种类型。如果从线上获取的一个数字无法满足这个字段的类型,你得到的结果会和C++中把这个数字转换成那个字段类型的结果一致。
  • sint32和sint64是可以互相转换的,但是无法与其他的integer类型相互转换。
  • string和bytes是相互转换的,前提是bytes是utf-8编码。
  • 嵌套的message与bytes是互相转换的,前提是bytes中包含这个message的编码版本。
  • fixed32与sfixed32可以互相转换,fixed64和sfixed64互相转换。
  • 对于string和bytes以及message的字段来说,optional和repeated声明是可以相互转换的。假设现在有repeated的字段被序列化后作为输入,对于想要optional字段的客户端来说,如果字段是原始数据类型,它就会只取最后一个值;然而如果它是一个message类型,则会合并所有的输入值。注意,这种方式对于bool、枚举等类型并不安全。repeated字段和bool、枚举等类型使用packed格式进行序列化,但是它们不会正确的解析到optional字段中。
  • 枚举和int32、uint32、int64、uint64是可相互转换的,注意它们转换时可能存在截断的情况。然而,客户端在反序列化message时可能会出现不同的情况:例如,一个未识别的proto3枚举类型储存在message中,但是message在反序列化时这个类型的表现形式取决于具体语言实现。
  • 把一个字段值转换为新的oneof的一个成员这种操作是安全的,同时是可以互相转换的。把多个字段移动到一个新的oneof中可能是安全的,前提是你确定没有其他的代码同时设置它们中的多个字段值。把任何的字段移动到现有的oneof中是不安全的。

1.8. 未知的字段

未知字段(未知字段:unknown field)是序列化的数据中,pb解析器无法识别的内容。例如,当使用基于旧.proto文件的解析器解析具有新增字段的数据时,这些新字段就是未知字段。

起初,proto3的message在解析时会丢弃未知字段,但是3.5版本我们重新引入了对未知字段的保留以兼容proto2。在3.5或更高版本中,未知字段在解析过程中会保留并且包含在序列化的输出中。

1.9. Any类型

Any类型可以让你在不知道message定义的情况下把一个message作为字段的类型。一个Any类型中包含任何类型message序列化后的数据,同时还有一个URL作为标识符并且用于解析该数据的类型。使用Any,需要导入google/protobuf/any.proto。

import "google/protobuf/any.proto";

message ErrorStatus {
  string message = 1;
  repeated google.protobuf.Any details = 2;
}

对于给定message类型的URL是:type.googleapis.com/_packagename_._messagename_。

不同的语言实现将支持runtime library helper以类型安全的方式打包和解包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 ...
  }
}

目前,关于Any的runtime libraries还在开发中。

如果你已经熟悉了proto2的语法,Any可以持有任意proto3的message与proto2的message可以允许extension是相似的。

1.10. Oneof

如果你在一个message中有很多个字段,但是同时最多只有一个字段被设置,你可以通过oneof来解决这个问题同时节省内存。

oneof与普通的字段相似,但是oneof中的所有字段共享一块内存,并且最多只有一个字段可以被设置。设置oneof中的任意字段都会清除其他的字段。你可以使用case()或者WhichOneof()方法来判断哪个字段被设置,具体实现依赖于具体的语言。

1.10.1. 使用Oneof

使用oneof关键字后面跟oneof名来定义一个oneof类型。

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

接下来可以向oneof定义中添加oneof字段。除了map和repeated字段,其他类型都可以作为oneof的字段。

在生成的代码中,oneof字段与普通的字段都有getter和setter方法。你也会有一个特殊的方法来检查oneof中哪个字段被设置了。具体查看API 文档。

1.10.2. 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的多个字段,那么解析message的时候只使用遇到的最后一个字段。
  • oneof不能被repeated修饰。
  • oneof字段使用反射的API。
  • 如果你设置了oneof某一个字段为默认值(例如设置一个int32字段为0),这个字段的"case"就会被设置,同时它的值在传输时会被序列化。
  • 如果你是用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的message,每个message最后都会有对方的oneof字段:下面的代码中,msg1会有sub_message,msg2会有name。

1.10.3. 向后兼容性问题

当删除或增加oneof的字段时需要小心。如果检查一个oneof字段返回None或者NOT_SET,这说明这个字段没有被设置或者在oneof的之前某一个版本中是被设置的。这是很难区分的,因为无法知道传输过来的一个未知字段是不是oneof的成员。

1.10.3.1. 标签重用问题

  • 从一个oneof移出或移入字段:message序列化和解析后可能会丢失一部分信息,因为有的字段会被清除掉。然而,单个字段是可以安全的添加到一个新的oneof中。多个字段的话,如果这些字段同时最多只有一个字段被设置,那么也是可以添加到oneof中的。
  • 删除一个oneof中的字段然后添加回去:当message被序列化和解析后,可能会清除掉当前设置的字段。
  • 拆分或者合并oneof:这与移动字段产生的问题相同。

1.11. 字典(字典:map)

如果你想使用字典,pb提供了一个语法:

map<key_type, value_type> map_field = N;

其中,key_type可以是数字或者string类型(可以是任何的scalar类型,除了浮点类型和bytes)。注意,枚举不是一个有效的key_type。value_type可以是除了map以外的任何的类型。

例如,你想创建一个map,其中每个Project都和一个string相关联:

map<string, Project> projects = 3;
  • map作为字段时不能用repeated修饰。即没有[]map。
  • map在传输前后,其元素的顺序不是固定的。因此你不能指望你的map元素在传输后也按照固定的顺序进行遍历。
  • 当为.proto文件生成代码时,map会根据key进行排序。数字key会按照数字顺序排序。
  • 当解析传输的数据或者合并map时,如果有重复的key,map只会使用最后一个遇到的key。当从文件中解析map时,如果遇到了重复的key可能会导致解析失败。
  • 如果你提供了一个没有value的key作为map的字段,这个字段在序列化时的行为取决于语言实现。在C++、Java、Kotlin和Python中,这个类型的默认值是会被序列化的,但是其他的语言中不会序列化这个值。

map的API见API文档。

1.11.1 向后兼容

map在传输时等效于下面的代码,因此不支持map的pb实现仍然可以解析map数据。

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

repeated MapFieldEntry map_field = N;

任意支持map的pb实现在构造和接收的数据时都必须与上述代码中定义生成的数据相同。

1.12. package

在一个.proto文件中,你可以添加一个可选的package标识来防止message重名。

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

在你自己的message中定义字段时可以使用上述的包定义:

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

package标识符对后续生成的代码的影响取决于你使用的语言:

  • 在C++中,生成的类都在C++的同一个命名空间下。例如,Open会在foo::bar里面。
  • 在Java和Kotlin中,package默认用作java包,除非你显式提供.proto文件中的选项java_package。
  • 在Python中,忽略package指令,因为Python模块根据文件系统中的位置组织。
  • 在Go中,package默认用作Go包名,除非你显式提供.proto文件中的选项go_package。
  • 在Ruby中,生成的类被包装在嵌套的Ruby命名空间内,转换为所需的Ruby大写样式(首字母大写;如果第一个字符不是字母,则使用PB_)。例如,Open将在Foo :: Bar中。
  • 在C#中,package默认被用作转换为PascalCase之后的命名空间,除非你在.proto文件中明确提供选项csharp_namespace。例如,Open将在命名空间Foo.Bar中。

1.12.1. package和名称解析

在pb语言中类型名称的解析工作和C++一样:首先搜索最内层的范围,然后是下一个最内层,依此类推,每个包都比它的父包更靠内。一个前置的'.'(例如,.foo.bar.baz)意味着从最外面的包开始。

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

1.13. 定义service

如果你想在RPC系统中使用你定义的message,你可以在.proto文件中定义一个RPC服务接口,pb编译器会根据你使用的语言生成一个服务接口代码与桩代码(桩代码:stub)。例如,如果你想定义一个RPC服务,里面有一个方法,输入SearchRequest,输出SearchResponse,你可以在.proto文件中这么写:

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

最直接了当使用pb的RPC系统是gRPC:Google实现的语言和平台中立性的开源RPC系统。gRPC特别适用于pb,并且通过一个特殊的pb编译器插件让你直接从.proto文件中生成RPC代码。

如果你不想使用gRPC,也可以在你自己的RPC实现上使用pb。具体看Proto2 Language Guide。

目前还有很多第三方工程在开发适用于pb的RPC实现。具体见third-party add-on wiki page。

1.14. JSON映射

proto3支持JSON的规范编码,使系统之间的数据传输更加容易。编码方式在下表中。

如果JSON数据中一个值丢失或者是null,转换成pb时会使用该类型默认值。如果pb中一个字段是默认值,转换成JSON时就会被省略掉。有的实现可能会提供选项,从而可以在JSON中保留具有默认值的字段。

proto3

JSON

JSON example

Notes

message

object

{"fooBar": v, "g": null, …}

生成JSON对象。message的字段名映射成小写驼峰格式成为JSON对象的键。如果json_name字段定义了,它的值就被设置为键。解析器可以解析小驼峰名和原始的proto字段名。JSON的null对所有的字段类型都适用,并且映射成该类型的默认值。

enum

string

"FOO_BAR"

JSON中使用在.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

number

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。数字和字符串都可以解析为JSON。指数符号也可以使用。-0等效为0。

Any

object

{"@type": "url", "f": v, … }

如果Any中的内容是有JSON映射的值,它会转换成 {"@type": xxx, "value": yyy}。否则,它会被转换成JSON对象,@type字段会插入JSON对象中。

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对象。

Wrapper types

various types

2, "2", "foo", true, "true", null, 0, …

在JSON中,包装器使用与包装的原始类型一样的表示形式,并且null在数据转换和传输时不会被忽略。

FieldMask

string

"f.fooBar,h"

See field_mask.proto.

ListValue

array

[foo, bar, …]


Value

value


转换为JSON的value。具体见:google.protobuf.Value 。

NullValue

null


对应JSON的null

Empty

object

{}

对应一个空的JSON对象。

1.14.1. JSON选项

一个proto3的JSON实现可能会提供如下选项:

  • 传输具有默认值的字段:在proto3的JSON输出中,pb中带有默认值的字段在序列化时会被省略掉。有的pb实现可能会提供一个选项来重载这个行为并且输出具有默认值的字段。
  • 忽略未知字段:proto3的JSON解析器默认会驳回未知字段,但是也可以提供一个选项在解析时忽略这些字段。
  • 不使用小写驼峰名,使用proto的字段名:proto3默认的在序列化时应该把字段名转换成小写驼峰名并用做JSON的键。有的实现可能会直接使用message的字段名作为JSON的名字。proto3的JSON解析器必须都能够转换小写驼峰名和proto字段名。
  • 以整型而不是字符串形式传输枚举类型:pb默认序列化后的JSON输出中使用枚举的名字进行传输。其他的实现可能会使用枚举值而不是枚举名进行传输。

1.15. 选项

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

有些选项是文件级别的,意味着它们应该写在最顶层的范围内,而不是在message、枚举或者service定义中。有些选项是message级的,意味着他们应该写在message定义内部。有些选项是字段级的,意味着它们应该被写在字段定义的内部。选项也可以写在枚举类型、枚举值、oneof、service类型和service方法上,然而,现在没有选项提供使用。

具体的选项在生成各种语言的桩代码中用的比较多,这个用到再查就可以了。

1.16. 生成你的类代码

通过.proto文件中的message定义,你可以生成Java、Kotlin、Python、C++、Go、Ruby等的类代码,这需要在.proto文件上执行pb编译器protoc得到。如果你没有安装这个编译器,下载安装包然后按照README的指引进行。对于Go语言,你还需要安装一个特殊的代码生成插件:你可以在github的golang/protobuf上找到它和它的安装指南。

这个pb编译器的运行指令如下:

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定义了一个文件夹,当解析import的时候会在这个文件夹下查找.proto文件。如果省略了,就会使用当前文件夹。多个import文件夹可以通过多次声明--proto_path来添加;它们会被按照顺序依次查找。-I=_IMPORT_PATH_可以用作缩写的--proto_path。

你可以提供一个或多个输出文件夹:

  • --cpp_out将生成的C++代码放到DST_DIR。更多信息见:C++ generated code reference
  • --java_out 在 DST_DIR 中生成 Java 代码。有关更多信息,请参阅Java generated code reference。
  • --kotlin_out 在 DST_DIR 中生成额外的 Kotlin 代码。有关更多信息,请参阅Kotlin generated code reference。
  • --python_out 在 DST_DIR 中生成 Python 代码。有关更多信息,请参阅Python generated code reference。
  • --go_out 在 DST_DIR 中生成 Go 代码。有关更多信息,请参阅 Go generated code reference。
  • --ruby_out 在 DST_DIR 中生成 Ruby 代码。 Ruby 生成的代码参考即将推出!
  • --objc_out 在 DST_DIR 中生成 Objective-C 代码。有关更多信息,请参阅Objective-C generated code reference。
  • --csharp_out 在 DST_DIR 中生成 C# 代码。有关更多信息,请参阅C# generated code reference。
  • --php_out 在 DST_DIR 中生成 PHP 代码。有关更多信息,请参阅 PHP generated code reference。为方便起见,如果 DST_DIR 以 .zip 或 .jar 结尾,则编译器会将输出写入具有给定名称的单个 ZIP 格式存档文件。 .jar 输出还将根据 Java JAR 规范的要求提供清单文件。请注意,如果输出存档已经存在,它将被覆盖;编译器不够聪明,无法将文件添加到现有存档中。
  • 你必须提供一个或多个.proto 文件作为输入。可以一次指定多个 .proto 文件。尽管文件是相对于当前目录命名的,但每个文件都必须在IMPORT_PATH之一中,以便编译器可以确定其规范名称。

相关推荐

快递查询教程,批量查询物流,一键管理快递

作为商家,每天需要查询许许多多的快递单号,面对不同的快递公司,有没有简单一点的物流查询方法呢?小编的回答当然是有的,下面随小编一起来试试这个新技巧。需要哪些工具?安装一个快递批量查询高手快递单号怎么快...

一键自动查询所有快递的物流信息 支持圆通、韵达等多家快递

对于各位商家来说拥有一个好的快递软件,能够有效的提高自己的工作效率,在管理快递单号的时候都需要对单号进行表格整理,那怎么样能够快速的查询所有单号信息,并自动生成表格呢?1、其实方法很简单,我们不需要一...

快递查询单号查询,怎么查物流到哪了

输入单号怎么查快递到哪里去了呢?今天小编给大家分享一个新的技巧,它支持多家快递,一次能查询多个单号物流,还可对查询到的物流进行分析、筛选以及导出,下面一起来试试。需要哪些工具?安装一个快递批量查询高手...

3分钟查询物流,教你一键批量查询全部物流信息

很多朋友在问,如何在短时间内把单号的物流信息查询出来,查询完成后筛选已签收件、筛选未签收件,今天小编就分享一款物流查询神器,感兴趣的朋友接着往下看。第一步,运行【快递批量查询高手】在主界面中点击【添...

快递单号查询,一次性查询全部物流信息

现在各种快递的查询方式,各有各的好,各有各的劣,总的来说,还是有比较方便的。今天小编就给大家分享一个新的技巧,支持多家快递,一次能查询多个单号的物流,还能对查询到的物流进行分析、筛选以及导出,下面一起...

快递查询工具,批量查询多个快递快递单号的物流状态、签收时间

最近有朋友在问,怎么快速查询单号的物流信息呢?除了官网,还有没有更简单的方法呢?小编的回答当然是有的,下面一起来看看。需要哪些工具?安装一个快递批量查询高手多个京东的快递单号怎么快速查询?进入快递批量...

快递查询软件,自动识别查询快递单号查询方法

当你拥有多个快递单号的时候,该如何快速查询物流信息?比如单号没有快递公司时,又该如何自动识别再去查询呢?不知道如何操作的宝贝们,下面随小编一起来试试。需要哪些工具?安装一个快递批量查询高手快递单号若干...

教你怎样查询快递查询单号并保存物流信息

商家发货,快递揽收后,一般会直接手动复制到官网上一个个查询物流,那么久而久之,就会觉得查询变得特别繁琐,今天小编给大家分享一个新的技巧,下面一起来试试。教程之前,我们来预览一下用快递批量查询高手...

简单几步骤查询所有快递物流信息

在高峰期订单量大的时候,可能需要一双手当十双手去查询快递物流,但是由于逐一去查询,效率极低,追踪困难。那么今天小编给大家分享一个新的技巧,一次能查询多个快递单号的物流,下面一起来学习一下,希望能给大家...

物流单号查询,如何查询快递信息,按最后更新时间搜索需要的单号

最近有很多朋友在问,如何通过快递单号查询物流信息,并按最后更新时间搜索出需要的单号呢?下面随小编一起来试试吧。需要哪些工具?安装一个快递批量查询高手快递单号若干怎么快速查询?运行【快递批量查询高手】...

连续保存新单号功能解析,导入单号查询并自动识别批量查快递信息

快递查询已经成为我们日常生活中不可或缺的一部分。然而,面对海量的快递单号,如何高效、准确地查询每一个快递的物流信息,成为了许多人头疼的问题。幸运的是,随着科技的进步,一款名为“快递批量查询高手”的软件...

快递查询教程,快递单号查询,筛选更新量为1的单号

最近有很多朋友在问,怎么快速查询快递单号的物流,并筛选出更新量为1的单号呢?今天小编给大家分享一个新方法,一起来试试吧。需要哪些工具?安装一个快递批量查询高手多个快递单号怎么快速查询?运行【快递批量查...

掌握批量查询快递动态的技巧,一键查找无信息记录的两种方法解析

在快节奏的商业环境中,高效的物流查询是确保业务顺畅运行的关键。作为快递查询达人,我深知时间的宝贵,因此,今天我将向大家介绍一款强大的工具——快递批量查询高手软件。这款软件能够帮助你批量查询快递动态,一...

从复杂到简单的单号查询,一键清除单号中的符号并批量查快递信息

在繁忙的商务与日常生活中,快递查询已成为不可或缺的一环。然而,面对海量的单号,逐一查询不仅耗时费力,还容易出错。现在,有了快递批量查询高手软件,一切变得简单明了。只需一键,即可搞定单号查询,一键处理单...

物流单号查询,在哪里查询快递

如果在快递单号多的情况,你还在一个个复制粘贴到官网上手动查询,是一件非常麻烦的事情。于是乎今天小编给大家分享一个新的技巧,下面一起来试试。需要哪些工具?安装一个快递批量查询高手快递单号怎么快速查询?...

取消回复欢迎 发表评论: