• proto3 语法
    • 1、定义消息类型
      • 1.1 指定字段类型
      • 1.2 分配字段编号
      • 1.3 指定字段规则
      • 1.4 添加更多消息类型
      • 1.5 添加注释
      • 1.6 保留字段
      • 1.7 你的.proto生成什么?
    • 2、标量值类型
    • 3、默认值
    • 4、枚举
      • 4.1 保留值
    • 5、使用其他消息类型
      • 5.1 导入定义
      • 5.2 使用proto2消息类型
    • 6、嵌套类型
    • 7、更新消息类型
    • 8、未知字段
    • 9、Any消息类型
    • 10、Oneof
      • 10.1 使用Oneof
      • 10.2 Oneof特点
      • 10.3 向后兼容问题
    • 11、映射
      • 11.1 向后兼容性
    • 12、包
      • 12.1 包和名称解析
    • 13、定义服务
    • 14、JSON映射
      • 14.1 JSON选项
    • 15、选项
      • 15.1 自定义选项
    • 16、生成您的类
    • 17、解析和序列化

    proto3 语法

      

    1、定义消息类型

      这是.proto用于定义消息类型的文件。

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

      • 该文件的第一行指定您正在使用proto3语法:如果您不这样做,协议缓冲区编译器将假定您正在使用proto2。这必须是文件的第一个非空的非注释行。

      • 所述SearchRequest消息定义指定了三个字段(名称/值对),一个用于要在此类型的消息中包含的每个数据片段。每个字段都有一个名称和类型。

      1.1 指定字段类型

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

      1.2 分配字段编号

      如您所见,消息定义中的每个字段都有唯一的编号。这些字段编号用于以消息二进制格式标识字段,并且在使用消息类型后不应更改。

      请注意,115范围内的字段编号需要一个字节进行编码,包括字段编号和字段类型。162047范围内的字段编号占用两个字节。因此,您应该为非常频繁出现的消息元素保留数字115。请记住为将来可能添加的常用元素留出一些空间。

      您可以指定的最小字段数为1,最大字段数为536,870,911。您也不能使用数字1900019999,因为它们是为Protobuf实现保留的,如果您使用其中一个保留号码,编译器会报错。同样,您不能使用任何以前保留的字段编号。

      1.3 指定字段规则

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

    1. 单个字段:字段不能重复。
    2. repeated:此字段可以在格式良好的消息中重复任意次数(包括零)。将保留重复值的顺序。

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

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

      1.4 添加更多消息类型

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

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

      1.5 添加注释

      要为.proto文件添加注释,请使用////语法。

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

      1.6 保留字段

      如果通过完全删除字段或将其注释来更新消息类型,则未来用户可以在对类型进行自己的更新时重用字段编号。如果以后加载相同的.proto旧版本,这可能会导致严重问题,包括数据损坏,隐私错误等。

      确保不会发生这种情况的一种方法是通过reserved指定已删除字段的字段编号(或名称,这也可能导致JSON序列化问题)。如果将来的任何用户尝试使用这些字段标识符,编译器将会报错。

    1. message Foo {
    2. reserved 2, 15, 9 to 11;
    3. reserved "foo", "bar";
    4. }
    请注意,您不能在同一reserved语句中混合字段名称和字段编号。

      1.7 你的.proto生成什么?

      当您通过编译器protoc.exe编译.proto以后,编译器会生成您所选语言的代码,您需要使用您在文件中描述的消息类型,包括获取和设置字段值,将消息序列化为输出流,并从输入流解析您的消息。

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

    编译指令:

    1. protoc.exe --proto_path=./ xxx.proto --csharp_out=./

      

    2、标量值类型

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

    .proto类型C#类型说明
    doubledouble
    floatfloat
    int32int使用可变长度编码。编码负数的效率低,如果您的字段可能有负值,请改用sint32
    int64long使用可变长度编码。编码负数的效率低,如果您的字段可能有负值,请改用sint64
    uint32uint使用可变长度编码。
    uint64ulong使用可变长度编码。
    sint32int使用可变长度编码。签名的int值。这些比常规int32更有效地编码负数。
    sint64long使用可变长度编码。签名的int值。这些比常规int64更有效地编码负数。
    fixed32uint总是四个字节。如果值通常大于2^28,则比uint32更有效。
    fixed64ulong总是八个字节。如果值通常大于2^56 ,则比uint64更有效。
    sfixed32int总是四个字节。
    sfixed64long总是八个字节。
    boolbool
    stringstring字符串必须始终包含UTF-8编码或7位ASCII文本。
    bytesByteString可以包含任意字节序列。

      

    3、默认值

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

      这些默认值是特定于类型的:

    1. 对于字符串,默认值为空字符串。
    2. 对于字节,默认值为空字节。
    3. 对于`bool`,默认值为`false`
    4. 对于数字类型,默认值为零。
    5. 对于枚举,默认值是第一个定义的枚举值,该值必须为`0`
    6. 对于消息字段,未设置该字段。它的确切值取决于语言。

      有关详细信息,请参阅生成的代码。重复字段的默认值为空(通常是相应语言的空列表)。

      

    4、枚举

      在定义消息类型时,您可能希望其中一个字段只有一个预定义的值列表。例如,假设你想每个SearchRequest添加一个corpus字段,其中取值可以UNIVERSALWEBIMAGESLOCALNEWSPRODUCTSVIDEO。您可以非常简单地通过enum为每个可能的值添加一个常量来定义消息定义。

      在下面的示例中,我们添加了一个带有所有可能值的enum调用Corpus,以及一个类型的字段Corpus

    1. message SearchRequest {
    2. string query = 1;
    3. int32 page_number = 2;
    4. int32 result_per_page = 3;
    5. enum Corpus {
    6. UNIVERSAL = 0;
    7. WEB = 1;
    8. IMAGES = 2;
    9. LOCAL = 3;
    10. NEWS = 4;
    11. PRODUCTS = 5;
    12. VIDEO = 6;
    13. }
    14. Corpus corpus = 4;
    15. }

      如您所见,Corpus枚举的第一个常量映射为零:每个枚举定义必须包含一个映射到零的常量作为其第一个元素。这是因为:

    1. 必须有一个零值,以便我们可以使用 0 作为数字默认值
    2. 零值必须是第一个元素,以便与proto2 语义兼容,其中第一个枚举值始终是默认值。

      您可以通过为不同的枚举常量指定相同的值来定义别名。为此,您需要将allow_alias选项设置为true,否则协议编译器将在找到别名时生成错误消息。

    1. enum EnumAllowingAlias {
    2. option allow_alias = true;
    3. UNKNOWN = 0;
    4. STARTED = 1;
    5. RUNNING = 1;
    6. }
    7. enum EnumNotAllowingAlias {
    8. UNKNOWN = 0;
    9. STARTED = 1;
    10. // RUNNING = 1; // 取消注释此行将导致 Google 内部的编译错误和外部的警告消息。
    11. }

      枚举器常量必须在32位整数范围内。由于enum值在online使用varint编码,因此负值效率低,因此不建议使用。

      enum也可以在外部定义,这些可以在.proto文件的任何消息定义中重用。您还可以使用enum语法将一个消息中声明的类型用作另一个消息中的字段类型:MessageType.EnumType

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

      4.1 保留值

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

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

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

      

    5、使用其他消息类型

      您可以使用其他消息类型作为字段类型。例如,假设你想SearchResponse消息包括Result的每个消息,要做到这一点,你可以在.proto定义一个Result消息类型,然后指定SearchResponse类型中的字段Result

    1. message SearchResponse {
    2. repeated Result results = 1;
    3. }
    4. message Result {
    5. string url = 1;
    6. string title = 2;
    7. repeated string snippets = 3;
    8. }

      5.1 导入定义

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

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

    1. import "myproject/other_protos.proto";

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

    1. // new.proto
    2. // 所有定义都移到这里
    3. // old.proto
    4. // 这是所有客户端都要导入的原型。
    5. import public "new.proto";
    6. import "other.proto";
    7. // client.proto
    8. import "old.proto";
    9. // 您使用old.proto和new.proto中的定义,但不使用other.proto

      5.2 使用proto2消息类型

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

      

    6、嵌套类型

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

    1. message SearchResponse {
    2. message Result {
    3. string url = 1;
    4. string title = 2;
    5. repeated string snippets = 3;
    6. }
    7. repeated Result results = 1;
    8. }

      如果要在其父消息类型之外重用此消息类型,请将其称为:Parent.Type

    1. message SomeOtherMessage {
    2. SearchResponse.Result result = 1;
    3. }

      您可以根据需要深入嵌套消息:

    1. message Outer { // Level 0
    2. message MiddleAA { // Level 1
    3. message Inner { // Level 2
    4. int64 ival = 1;
    5. bool booly = 2;
    6. }
    7. }
    8. message MiddleBB { // Level 1
    9. message Inner { // Level 2
    10. int32 ival = 1;
    11. bool booly = 2;
    12. }
    13. }
    14. }

      

    7、更新消息类型

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

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

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

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

        • int32uint32int64uint64,和bool都是兼容的 - 这意味着你可以改变这些类型向前或向后兼容。如果从文件中解析出一个不符合相应类型的数字,您将获得与在 C ++ 中将该数字转换为该类型相同的效果(例如,如果将 64 位数字作为 int32 读取,它将被截断为 32 位)。

        • sint32sint64彼此兼容但与其他整数类型不兼容。

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

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

        • fixed32兼容sfixed32,并且fixed64兼容sfixed64

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

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

      

    8、未知字段

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

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

      

    9、Any消息类型

      该Any消息类型,可以使用网址作为嵌入式类型,而不必自己.proto 定义。一个Any含有任意的序列化消息bytes,以充当一个全局唯一标识符和解析到该消息的类型的URL一起。要使用该Any类型,您需要导入google/protobuf/any.proto

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

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

      

    10、Oneof

      如果您有一个包含许多字段的消息,并且最多只能同时设置一个字段,则可以使用oneof功能强制执行此行为并节省内存。  除了一个共享内存中的所有字段之外,其中一个字段类似于常规字段,并且最多可以同时设置一个字段。设置oneof的任何成员会自动清除所有其他成员。您可以使用特殊case()WhichOneof()方法检查oneof中的哪个值(如果有),具体取决于您选择的语言。

      10.1 使用Oneof

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

    1. message SampleMessage {
    2. oneof test_oneof {
    3. string name = 4;
    4. SubMessage sub_message = 9;
    5. }
    6. }

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

      10.2 Oneof特点

      • 设置oneof字段将自动清除oneof的所有其他成员。因此,如果您设置了多个字段,则只有您设置的最后一个字段仍然具有值。

      • 如果解析器遇到同一个oneof的多个成员,则在解析的消息中仅使用看到的最后一个成员。

      • 字段不能使用repeated

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

      10.3 向后兼容问题

      添加或删除其中一个字段时要小心。如果检查oneof返回的值None/NOT_SET,这可能意味着oneof尚未设置或已在不同版本的oneof的被设置为一个字段。没有办法区分,因为没有办法知道线上的未知字段是否是其中一个成员。

      标签重用问题

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

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

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

      

    11、映射

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

    1. map<key_typevalue_type> map_field = N;

      … 其中key_type可以是任何整数或字符串类型(因此,除了浮点类型之外的任何标量、bytes类型)。请注意,枚举不是有效的 key_typevalue_type可以是任何类型的除另一映射。

      因此,例如,如果要创建项目映射,其中每条Project消息都与字符串键相关联,则可以像下面这样定义它:

    1. map<stringProject> projects = 3;

      • 映射字段不能repeated

      • 映射值的有线格式排序和映射迭代排序未定义,因此您不能依赖于特定顺序的映射项目。

      • 为.proto生成文本格式时,映射按键排序。数字键按数字排序。

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

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

      11.1 向后兼容性

      映射语法在线上等效于以下内容,因此不支持映射的Protobuf实现仍可处理您的数据:

    1. message MapFieldEntry {
    2. key_type key = 1;
    3. value_type value = 2;
    4. }
    5. repeated MapFieldEntry map_field = N;

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

      

    12、包

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

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

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

    1. message Foo {
    2. ...
    3. foo.bar.Open open = 1;
    4. ...
    5. }

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

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

      12.1 包和名称解析

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

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

      

    13、定义服务

      如果要将消息类型与RPC(远程过程调用)系统一起使用,则可以在 .proto文件中定义RPC服务接口,Protobuf编译器将使用您选择的语言生成服务接口代码和存根。因此,例如,如果要使用带有SearchRequest和返回的SearchResponse方法定义RPC服务,可以按如下方式在.proto文件中定义它:

    1. service SearchService {
    2. rpc Search (SearchRequest) returns (SearchResponse);
    3. }

      与Protobuf一起使用的最简单的RPC系统是gRPC:一种在Google开发的语言和平台中立的开源RPC系统。gRPC特别适用于Protobuf,并允许您使用特殊的Protobuf编译器插件直接从您的.proto文件生成相关的RPC代码。

      

    14、JSON映射

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

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

    proto3JSONJSON exampleNotes
    messageobject{"fooBar":v, "g":null,…}生成JSON对象。消息字段名称映射到lowerCamelCase并成为JSON对象键。如果json_name指定了field选项,则指定的值将用作键。解析器接受lowerCamelCase名称(或json_name选项指定的名称)和原始proto字段名称。null是所有字段类型的可接受值,并将其视为相应字段类型的默认值。
    enumstring"FOO_BAR"使用proto中指定的枚举值的名称。解析器接受枚举名称和整数值。
    map<K,V>object{"k":v, …}所有键都转换为字符串。
    repeated Varray[v, …]null被接受为空列表[]。
    booltrue, falsetrue,false
    stringstring"hello world"
    bytesbase64 string"YWJjMTIzIT8+"JSON值将是使用带填充的标准base64编码编码为字符串的数据。接受带有/不带填充的标准或URL安全base64编码。
    int32,fixed32,uint32number1,-10,0JSON值将是十进制数。接受数字或字符串。
    int64,fixed64,uint64string"1", "-10"JSON值将是十进制字符串。接受数字或字符串。
    float, doublenumber1.1, -10.0, 0, "Nan", "Infinity"JSON值将是一个数字或一个特殊字符串值“NaN”,“Infinity”和“-Infinity”。接受数字或字符串。指数表示法也被接受。
    Anyobject{"@type":"url" "f": v, …}如果Any包含具有特殊JSON 映射的值,则将按如下方式进行转换:。否则,该值将转换为JSON对象,并将插入该字段以指示实际的数据类型。{"@type": xxx, "value": yyy}"@type"
    Timestampstring"1972-01-01T10:00:20.021Z"使用RFC3339,其中生成的输出将始终被Z标准化并使用0,3,6或9个小数位。也接受“Z”以外的偏移。
    Durationstring"1.000340012s", "1s"生成的输出始终包含0,3,69个小数位,具体取决于所需的精度,后跟后缀“s”。接受的是任何小数位(也没有),只要它们符合纳秒精度并且后缀“s”是必需的。
    Structobject{ … }任何JSON对象。见struct.proto
    Wrapper typesvarious types2,"2","foo",true,"true",null,0,…包装器在JSON中使用与包装基元类型相同的表示形式,除了null在数据转换和传输期间允许和保留的表示形式。
    FieldMaskstring"f.fooBar, h"field_mask.proto
    ListValuevalue[foo, bar, …]
    Valuevalue任何JSON值
    NullValuenullJSON null

      14.1 JSON选项

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

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

        • 忽略未知字段:默认情况下,proto3 JSON解析器应拒绝未知字段,但可以提供忽略解析中未知字段的选项。

        • 使用proto字段名称而不是lowerCamelCase名称:默认情况下,proto3 JSON打印机应将字段名称转换为lowerCamelCase并将其用作JSON名称。实现可以提供使用proto字段名称作为JSON名称的选项。proto3 JSON解析器需要接受转换后的lowerCamelCase名称和proto字段名称。

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

      

    15、选项

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

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

      15.1 自定义选项

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

      

    16、生成您的类

      要生成你需要在定义的消息类型的工作使用 Java, Python, C++, Go, Ruby, Objective-C, or C# 代码 .proto 文件,你需要运行Protobuf编译器protoc 编译.proto 。如果尚未安装编译器,请下载该软件包 并按照自述文件中的说明进行操作。

    1. protoc --proto_path=IMPORT_PATH --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以便编译器可以确定其规范名称。

    17、解析和序列化

      使用协议缓冲区的全部目的是序列化您的数据,以便可以在其他地方解析它。每个生成的类都有一个WriteTo(CodedOutputStream)方法,其中CodedOutputStream是协议缓冲区运行时库中的一个类。但是,通常您将使用其中一种扩展方法写入常规System.IO.Stream或将消息转换为字节数组或ByteString。这些扩展消息在Google.Protobuf.MessageExtensions类中,因此当您想要序列化时,通常需要命名空间的using指令Google.Protobuf。例如:

    1. using Google.Protobuf;
    2. ...
    3. Person john = ...; // Code as before
    4. using (var output = File.Create("john.dat"))
    5. {
    6. john.WriteTo(output);
    7. }

    解析也很简单。每个生成的类都有一个静态Parser属性,该属性返回该类型的MessageParser<T>对象。这反过来又有解析流,字节数组和ByteString的方法。因此,要解析我们刚刚创建的文件,我们可以使用:

    1. Person john;
    2. using (var input = File.OpenRead("john.dat"))
    3. {
    4. john = Person.Parser.ParseFrom(input);
    5. }

    ?