关于后端:proto32语法

7次阅读

共计 14417 个字符,预计需要花费 37 分钟才能阅读完成。

Protobuf2

定义一个音讯类型

先来看一个非常简单的例子。假如你想定义一个“搜寻申请”的音讯格局,每一个申请含有一个查问字符串、你感兴趣的查问后果所在的页数,以及每一页多少条查问后果。能够采纳如下的形式来定义音讯类型的.proto 文件了:

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

文件的第一行指定了你正在应用 proto3 语法:如果你没有指定这个,编译器会应用 proto2。这个指定语法行必须是文件的非空非正文的第一个行。

SearchRequest 音讯格局有 3 个字段,在音讯中承载的数据别离对应于每一个字段。其中每个字段都有一个名字和一种类型。

指定字段类型

在下面的例子中,所有字段都是标量类型:两个整型(page_number 和 result_per_page),一个 string 类型(query)。当然,你也能够为字段指定其余的合成类型,包含枚举(enumerations)或其余音讯类型。

调配标识号

正如你所见,在音讯定义中,每个字段都有惟一的一个数字标识符。这些标识符是用来在音讯的二进制格局中辨认各个字段的,一旦开始应用就不可能再扭转。注:[1,15]之内的标识号在编码的时候会占用一个字节。[16,2047]之内的标识号则占用 2 个字节。所以应该为那些频繁呈现的音讯元素保留 [1,15]之内的标识号。切记:要为未来有可能增加的、频繁呈现的标识号预留一些标识号。

最小的标识号能够从 1 开始,最大到 2^29 – 1, or 536,870,911。不能够应用其中的[19000-19999]((从 FieldDescriptor::kFirstReservedNumber 到 FieldDescriptor::kLastReservedNumber))的标识号,Protobuf 协定实现中对这些进行了预留。如果非要在.proto 文件中应用这些预留标识号,编译时就会报警。同样你也不能应用晚期保留的标识号。

指定字段规定

所指定的音讯字段修饰符必须是如下之一:

  • singular:一个格局良好的音讯应该有 0 个或者 1 个这种字段(然而不能超过 1 个)。
  • repeated:在一个格局良好的音讯中,这种字段能够反复任意屡次(包含 0 次)。反复的值的程序会被保留。
    在 proto3 中,repeated 的标量域默认状况虾应用 packed。

你能够理解更多的 pakced 属性在 Protocol Buffer 编码

增加更多音讯类型

在一个.proto 文件中能够定义多个音讯类型。在定义多个相干的音讯的时候,这一点特地有用——例如,如果想定义与 SearchResponse 音讯类型对应的回复音讯格局的话,你能够将它增加到雷同的.proto 文件中,如:

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

增加正文

向.proto 文件增加正文,能够应用 C /C++/Java 格调的双斜杠(//)语法格局,如:

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)

如果你通过删除或者正文所有域,当前的用户在更新这个类型的时候可能重用这些标识号。如果你应用旧版本加载雷同的.proto 文件会导致重大的问题,包含数据损坏、隐衷谬误等等。当初有一种确保不会产生这种状况的办法就是为字段 tag(reserved name 可能会 JSON 序列化的问题)指定 reserved 标识符,protocol buffer 的编译器会正告将来尝试应用这些域标识符的用户。

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

注:不要在同一行 reserved 申明中同时申明域名字和 tag number。

从.proto 文件生成了什么?

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

  • 对 C ++ 来说,编译器会为每个.proto 文件生成一个.h 文件和一个.cc 文件,.proto 文件中的每一个音讯有一个对应的类。
  • 对 Java 来说,编译器为每一个音讯类型生成了一个.java 文件,以及一个非凡的 Builder 类(该类是用来创立音讯类接口的)。
  • 对 Python 来说,有点不太一样——Python 编译器为.proto 文件中的每个音讯类型生成一个含有动态描述符的模块,,该模块与一个元类(metaclass)在运行时(runtime)被用来创立所需的 Python 数据拜访类。
  • 对 go 来说,编译器会位每个音讯类型生成了一个.pd.go 文件。
  • 对于 Ruby 来说,编译器会为每个音讯类型生成了一个.rb 文件。
  • javaNano 来说,编译器输入相似域 java 然而没有 Builder 类
  • 对于 Objective- C 来说,编译器会为每个音讯类型生成了一个 pbobjc.h 文件和 pbobjcm 文件,.proto 文件中的每一个音讯有一个对应的类。
  • 对于 C# 来说,编译器会为每个音讯类型生成了一个.cs 文件,.proto 文件中的每一个音讯有一个对应的类。

你能够从如下的文档链接中获取每种语言更多 API(proto3 版本的内容很快就颁布)。API Reference

标量数值类型

一个标量音讯字段能够含有一个如下的类型——该表格展现了定义于.proto 文件中的类型,以及与之对应的、在主动生成的拜访类中定义的类型:

你能够在文章 Protocol Buffer 编码中,找到更多“序列化音讯时各种类型如何编码”的信息。

  1. 在 java 中,无符号 32 位和 64 位整型被示意成他们的整型对应模式,最高位被贮存在标记位中。
  2. 对于所有的状况,设定值会执行类型查看以确保此值是无效。
  3. 64 位或者无符号 32 位整型在解码时被示意成为 ilong,然而在设置时能够应用 int 型值设定,在所有的状况下,值必须合乎其设置其类型的要求。
  4. python 中 string 被示意成在解码时示意成 unicode。然而一个 ASCIIstring 能够被示意成 str 类型。
  5. Integer 在 64 位的机器上应用,string 在 32 位机器上应用

默认值

当一个音讯被解析的时候,如果被编码的信息不蕴含一个特定的 singular 元素,被解析的对象锁对应的域被设置位一个默认值,对于不同类型指定如下:

  • 对于 string,默认是一个空 string
  • 对于 bytes,默认是一个空的 bytes
  • 对于 bool,默认是 false
  • 对于数值类型,默认是 0
  • 对于枚举,默认是第一个定义的枚举值,必须为 0;
  • 对于音讯类型(message),域没有被设置,确切的音讯是依据语言确定的,详见 generated code guide
  • 对于可反复域的默认值是空(通常状况下是对应语言中空列表)。

注:对于标量音讯域,一旦音讯被解析,就无奈判断域开释被设置为默认值(例如,例如 boolean 值是否被设置为 false)还是基本没有被设置。你应该在定义你的音讯类型时十分留神。例如,比方你不应该定义 boolean 的默认值 false 作为任何行为的触发形式。也应该留神如果一个标量音讯域被设置为标记位,这个值不应该被序列化传输。

查看 generated code guide 抉择你的语言的默认值的工作细节。

枚举

当须要定义一个音讯类型的时候,可能想为一个字段指定某“预约义值序列”中的一个值。例如,假如要为每一个 SearchRequest 音讯增加一个 corpus 字段,而 corpus 的值可能是 UNIVERSAL,WEB,IMAGES,LOCAL,NEWS,PRODUCTS 或 VIDEO 中的一个。其实能够很容易地实现这一点:通过向音讯定义中增加一个枚举(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:每个枚举类型必须将其第一个类型映射为 0,这是因为:

  • 必须有有一个 0 值,咱们能够用这个 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 值是应用可变编码方式的,对正数不够高效,因而不举荐在 enum 中应用正数。如上例所示,能够在 一个音讯定义的外部或内部定义枚举——这些枚举能够在.proto 文件中的任何音讯定义里重用。当然也能够在一个音讯中申明一个枚举类型,而在另一个不同 的音讯中应用它——采纳 MessageType.EnumType 的语法格局。

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

在反序列化的过程中,无奈辨认的枚举值会被保留在音讯中,尽管这种示意形式须要根据所应用语言而定。在那些反对凋谢枚举类型超出指定范畴之外的语言中(例如 C ++ 和 Go),为辨认的值会被示意成所反对的整型。在应用关闭枚举类型的语言中(Java),应用枚举中的一个类型来示意未辨认的值,并且能够应用所反对整型来拜访。在其余状况下,如果解析的音讯被序列号,未辨认的值将放弃原样。

对于如何在你的应用程序的音讯中应用枚举的更多信息,请查看所抉择的语言 generated code guide。

应用其余音讯类型

你能够将其余音讯类型用作字段类型。例如,假如在每一个 SearchResponse 音讯中蕴含 Result 音讯,此时能够在雷同的.proto 文件中定义一个 Result 音讯类型,而后在 SearchResponse 音讯中指定一个 Result 类型的字段,如:

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

导入定义

在下面的例子中,Result 音讯类型与 SearchResponse 是定义在同一文件中的。如果想要应用的音讯类型曾经在其余.proto 文件中曾经定义过了呢?
你能够通过导入(importing)其余.proto 文件中的定义来应用它们。要导入其余.proto 文件的定义,你须要在你的文件中增加一个导入申明,如:

import "myproject/other_protos.proto";

默认状况下你只能应用间接导入的.proto 文件中的定义. 然而,有时候你须要挪动一个.proto 文件到一个新的地位,能够不间接挪动.proto 文件,只需放入一个伪 .proto 文件在老的地位,而后应用 import public 转向新的地位。import public 依赖性会通过任意导入蕴含 import public 申明的 proto 文件传递。例如:

// 这是新的 proto
// All definitions are moved here
// 这是久的 proto
// 这是所有客户端正在导入的包
import public "new.proto";
import "other.proto";
// 客户端 proto
import "old.proto";
// 当初你能够应用新旧两种包的 proto 定义了。

通过在编译器命令行参数中应用 -I/–proto_pathprotocal 编译器会在指定目录搜寻要导入的文件。如果没有给出标记,编译器会搜寻编译命令被调用的目录。通常你只有指定 proto_path 标记为你的工程根目录就好。并且指定好导入的正确名称就好。

应用 proto2 音讯类型

在你的 proto3 音讯中导入 proto2 的音讯类型也是能够的,反之亦然,而后 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;
    }
  }
}

更新一个音讯类型

如果一个已有的音讯格局已无奈满足新的需要——如,要在音讯中增加一个额定的字段——然而同时旧版本写的代码依然可用。不必放心!更新音讯而不毁坏已有代码是非常简单的。在更新时只有记住以下的规定即可。

  • 不要更改任何已有的字段的数值标识。
  • 如果你减少新的字段,应用旧格局的字段依然能够被你新产生的代码所解析。你应该记住这些元素的默认值这样你的新代码就能够以适当的形式和旧代码产生的数据交互。类似的,通过新代码产生的音讯也能够被旧代码解析:只不过新的字段会被忽视掉。留神,未被辨认的字段会在反序列化的过程中抛弃掉,所以如果音讯再被传递给新的代码,新的字段仍然是不可用的(这和 proto2 中的行为是不同的,在 proto2 中未定义的域仍然会随着音讯被序列化)
  • 非 required 的字段能够移除——只有它们的标识号在新的音讯类型中不再应用(更好的做法可能是重命名那个字段,例如在字段前增加“OBSOLETE_”前缀,那样的话,应用的.proto 文件的用户未来就不会无心中从新应用了那些不该应用的标识号)。
  • int32, uint32, int64, uint64, 和 bool 是全副兼容的,这意味着能够将这些类型中的一个转换为另外一个,而不会毁坏向前、向后的兼容性。如果解析进去的数字与对应的类型不相符,那么后果就像在 C ++ 中对它进行了强制类型转换一样(例如,如果把一个 64 位数字当作 int32 来 读取,那么它就会被截断为 32 位的数字)。
  • sint32 和 sint64 是相互兼容的,然而它们与其余整数类型不兼容。
    = string 和 bytes 是兼容的——只有 bytes 是无效的 UTF- 8 编码。
  • 嵌套音讯与 bytes 是兼容的——只有 bytes 蕴含该音讯的一个编码过的版本。
  • fixed32 与 sfixed32 是兼容的,fixed64 与 sfixed64 是兼容的。
  • 枚举类型与 int32,uint32,int64 和 uint64 相兼容(留神如果值不相兼容则会被截断),然而在客户端反序列化之后他们可能会有不同的解决形式,例如,未辨认的 proto3 枚举类型会被保留在音讯中,然而他的示意形式会按照语言而定。int 类型的字段总会保留他们的

Any

Any 类型音讯容许你在没有指定他们的.proto 定义的状况下应用音讯作为一个嵌套类型。一个 Any 类型包含一个能够被序列化 bytes 类型的任意音讯,以及一个 URL 作为一个全局标识符和解析音讯类型。为了应用 Any 类型,你须要导入 import 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 ...
  }
}

目前,用于 Any 类型的动静库仍在开发之中
如果你曾经很相熟 proto2 语法,应用 Any 替换扩大。

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 字段到 oneof 定义中. 你能够减少任意类型的字段, 然而不能应用 repeated 关键字.

在产生的代码中, oneof 字段领有同样的 getters 和 setters,就像失常的可选字段一样. 也有一个非凡的办法来查看到底那个字段被设置. 你能够在相应的语言 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 中有多个成员,只有最会一个会被解析成音讯。
  • oneof 不反对 repeated.
  • 反射 API 对 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 音讯,每个音讯,两个音讯将领有对方的值,例如在上面的例子中,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 字段时肯定要小心. 如果查看 oneof 的值返回 None/NOT_SET, 它意味着 oneof 字段没有被赋值或者在一个不同的版本中赋值了。你不会晓得是哪种状况,因为没有方法判断如果未辨认的字段是一个 oneof 字段。

Tag 重用问题:

  • 将字段移入或移除 oneof:在音讯被序列号或者解析后,你兴许会失去一些信息(有些字段兴许会被革除)
  • 删除一个字段或者退出一个字段:在音讯被序列号或者解析后,这兴许会革除你当初设置的 oneof 字段
  • 拆散或者交融 oneof:行为与挪动惯例字段类似。

Map

如果你心愿创立一个关联映射,protocol buffer 提供了一种快捷的语法:

map<key_type, value_type> map_field = N;

其中 key_type 能够是任意 Integer 或者 string 类型(所以,除了 floating 和 bytes 的任意标量类型都是能够的)value_type 能够是任意类型。

例如,如果你心愿创立一个 project 的映射,每个 Projecct 应用一个 string 作为 key,你能够像上面这样定义:

map<string, Project> projects = 3;
  • Map 的字段能够是 repeated。
  • 序列化后的程序和 map 迭代器的程序是不确定的,所以你不要冀望以固定程序解决 Map
  • 当为.proto 文件产生生成文本格式的时候,map 会依照 key 的程序排序,数值化的 key 会依照数值排序。
  • 从序列化中解析或者交融时,如果有反复的 key 则后一个 key 不会被应用,当从文本格式中解析 map 时,如果存在反复的 key。
    生成 map 的 API 当初对于所有 proto3 反对的语言都可用了,你能够从 API 指南找到更多信息。

向后兼容性问题

map 语法序列化后等同于如下内容,因而即便是不反对 map 语法的 protocol buffer 实现也是能够解决你的数据的:

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

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 模块是依照其在文件系统中的地位进行组织的。
  • 对于 Go,包能够被用做 Go 包名称,除非你显式的提供一个 option go_package 在你的.proto 文件中。
  • 对于 Ruby,生成的类能够被包装在内置的 Ruby 名称空间中,转换成 Ruby 所需的大小写款式(首字母大写;如果第一个符号不是一个字母,则应用 PB_前缀),例如 Open 会在 Foo::Bar 名称空间中。
  • 对于 javaNano 包会应用 Java 包,除非你在你的文件中显式的提供一个 option java_package。
  • 对于 C# 包能够转换为 PascalCase 后作为名称空间,除非你在你的文件中显式的提供一个 option csharp_namespace,例如,Open 会在 Foo.Bar 名称空间中

包及名称的解析

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 buffer 的 RPC 零碎是 gRPC, 一个由谷歌开发的语言和平台中的开源的 PRC 零碎,gRPC 在应用 protocl buffer 时十分无效,如果应用非凡的 protocol buffer 插件能够间接为您从.proto 文件中产生相干的 RPC 代码。

如果你不想应用 gRPC,也能够应用 protocol buffer 用于本人的 RPC 实现,你能够从 proto2 语言指南中找到更多信息

还有一些第三方开发的 PRC 实现应用 Protocol Buffer。参考第三方插件 wiki 查看这些实现的列表。

JSON 映射

Proto3 反对 JSON 的编码标准,使他更容易在不同零碎之间共享数据,在下表中一一形容类型。

如果 JSON 编码的数据失落或者其自身就是 null,这个数据会在解析成 protocol buffer 的时候被示意成默认值。如果一个字段在 protocol buffer 中示意为默认值,领会在转化成 JSON 的时候编码的时候疏忽掉以节俭空间。具体实现能够提供在 JSON 编码中可选的默认值。

选项

定义.proto 文件时可能标注一系列的 option。Option 并不扭转整个文件申明的含意,但却可能影响特定环境下解决形式。残缺的可用选项能够在 google/protobuf/descriptor.proto 找到。

一些选项是文件级别的,意味着它能够作用于最外范畴,不蕴含在任何音讯外部、enum 或服务定义中。一些选项是音讯级别的,意味着它能够用在音讯定义的外部。当然有些选项能够作用在域、enum 类型、enum 值、服务类型及服务办法中。到目前为止,并没有一种无效的选项能作用于所有的类型。

如下就是一些罕用的选项:

  • java_package (文件选项) : 这个选项表明生成 java 类所在的包。如果在.proto 文件中没有明确的申明 java_package,就采纳默认的包名。当然了,默认形式产生的 java 包名并不是最好的形式,依照利用名称倒序形式进行排序的。如果不须要产生 java 代码,则该选项将不起任何作用。如:

    option java_package = "com.example.foo";
  • java_outer_classname (文件选项): 该选项表明想要生成 Java 类的名称。如果在.proto 文件中没有明确的 java_outer_classname 定义,生成的 class 名称将会依据.proto 文件的名称采纳驼峰式的命名形式进行生成。如(foo_bar.proto 生成的 java 类名为 FooBar.java), 如果不生成 java 代码,则该选项不起任何作用。如:

    option java_outer_classname = "Ponycopter";
  • optimize_for(文件选项): 能够被设置为 SPEED, CODE_SIZE, 或者 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;
  • cc_enable_arenas(文件选项): 对于 C ++ 产生的代码启用 arena allocation
  • objc_class_prefix(文件选项): 设置 Objective- C 类的前缀,增加到所有 Objective- C 从此.proto 文件产生的类和枚举类型。没有默认值,所应用的前缀应该是苹果举荐的 3 - 5 个大写字符,留神 2 个字节的前缀是苹果所保留的。
  • deprecated(字段选项): 如果设置为 true 则示意该字段曾经被废除,并且不应该在新的代码中应用。在大多数语言中没有理论的意义。在 java 中,这回变成 @Deprecated 正文,在将来,其余语言的代码生成器兴许会在字标识符中产生废除正文,废除正文会在编译器尝试应用该字段时收回正告。如果字段没有被应用你也不心愿有新用户应用它,尝试应用保留语句替换字段申明。

    int32 old_field = 6 [deprecated=true];

    自定义选项

    ProtocolBuffers 容许自定义并应用选项。该性能应该属于一个高级个性,对于大部分人是用不到的。如果你确实心愿创立本人的选项,请参看 Proto2 Language Guide。留神创立自定义选项应用了拓展,拓展只在 proto3 中可用。

生成拜访类

能够通过定义好的.proto 文件来生成 Java,Python,C++, Ruby, JavaNano, Objective-C, 或者 C# 代码,须要基于.proto 文件运行 protocol buffer 编译器 protoc。如果你没有装置编译器,下载安装包并遵循 README 装置。对于 Go, 你还须要装置一个非凡的代码生成器插件。你能够通过 GitHub 上的 protobuf 库找到装置过程

通过如下形式调用 protocol 编译器:

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 --javanano_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 在目标目录 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 代码,参考正在制作中。
    • –javanano_out 在目标目录 DST_DIR 中生成 JavaNano,JavaNano 代码生成器有一系列的选项用于定制自定义生成器的输入:你能够通过生成器的 README 查找更多信息,JavaNano 参考正在制作中。
    • –objc_out 在目标目录 DST_DIR 中产生 Object 代码,能够在 Objective- C 代码生成参考中查看更多。
    • –csharp_out 在目标目录 DST_DIR 中产生 Object 代码,能够在 C# 代码生成参考中查看更多。
    • –php_out 在目标目录 DST_DIR 中产生 Object 代码,能够在 PHP 代码生成参考中查看更多。
      作为一个不便的拓展,如果 DST_DIR 以.zip 或者.jar 结尾,编译器会将输入写到一个 ZIP 格式文件或者合乎 JAR 规范的.jar 文件中。留神如果输入曾经存在则会被笼罩,编译器还没有智能到能够追加文件。
  • 你必须提议一个或多个.proto 文件作为输出,多个.proto 文件能够只指定一次。尽管文件门路是绝对于当前目录的,每个文件必须位于其 IMPORT_PATH 下,以便每个文件能够确定其标准的名称。

    原文地址 : https://colobu.com/2017/03/16…

关注 vx
golang 技术实验室。获取更多好文

本文由 mdnice 多平台公布

正文完
 0