Linux(服务器编程):40---Protobuf 3.0通信协议概述及使用语法

一、Protobuf协议概述

  • protocolbuffer是google 的一种数据交换的格式,它独立于语言,独立于平台。google 提供了多种语言的实现:java、c#、c++、go和 python,每一种实现都包含了相应语言的编译器以及库文件。由于它是一种二进制的格式,比使用xml进行数据交换快许多。可以把它用于分布式应用之间的数据通信或者异构环境下的数据交换。作为一种效率和兼容性都很优秀的二进制数据传输格式,可以用于诸如网络传输、配置文件、数据存储等诸多领域

  • 据Google官方文档介绍,现在Google内部已经有48162个消息类型定义在12183个proto文件中

  • 本文介绍Protobuf 3.0的语法,和2.0语法有兼容性问题

二、消息类型

  • 先来看一个非常简单的例子。假设你想定义一个“搜索请求”的消息格式,每一个请求含有一个查询字符串、你感兴趣的查询结果所在的页数,以及每一页多少条查询结果。可以采用如下的方式来定义消息类型的.proto文件了:
    • 文件的第一行指定了你正在使用proto3语法:如果你没有指定这个,编译器会使用proto2。这个指定语法行必须是文件的非 空非注释的第一个行
    • SearchRequest消息格式有3个字段:在消息中承载的数据分别对应于每一个字段。其中每个字段都有一个名字和一种类型
syntax = "proto3";

//定义一个消息类型
message SearchRequest {
    string query = 1;
    int32 page_number = 2;
    int32 result_per_page = 3;
}

字段类型

  • 在上面的例子中,所有字段都是标量类型:两个整型(page_number和result_per_page),一个string类型(query)
  • 当然,你也可以为字段指定其他的合成类型,包括枚举(enumerations)或其他消息类型

分配标识号

  • 在消息定义中,每个字段都有唯一的一个数字标识符。这些标识符是用来在消息的二进制格式中识别各个字段的, 一旦开始使用就不能够再改变
  • 例如在上面的案例中query的标识号为1,page_number的标识号为2,result_per_page的标识号为3
  • 相关语法:
    • [1,15]之内的标识号在编码的时候会占用一个字节。[16,2047]之内的标识号则占用2个字节。 所以应该为那些频繁出现的消息元素保留 [1,15]之内的标识号。切记:要为将来有可能添加的、频繁出现的标识号预留一些标识号
    • 最小的标识号可以从1开始,最大到2^29­-1,or536,870,911
    • 不可以使用其中的[19000-19999](从 FieldDescriptor::kFirstReservedNumber到FieldDescriptor::kLastReservedNumber)的标识号, Protobuf协议实现中对这些进 行了预留。如果非要在.proto文件中使用这些预留标识号,编译时就会报警
    • 同样你也不能使用早期保留的标识号

指定字段规则

  • 所指定的消息字段修饰符必须是如下之一:
    • singular:一个格式良好的消息应该有0个或者1个这种字段(但是不能超过1个)(默认为singular,而且显示写上singular关键字会报错,这个是Google的一个坑)
    • repeated:在一个格式良好的消息中,这种字段可以重复任意多次(包括0次)。重复的值的顺序会被保留。 在proto3中,repeated的标量域默认情况下使用packed

添加更多消息类型

  • 在一个.proto文件中可以定义多个消息类型
  • 例如,如果想定义与SearchResponse消息类型对应的回复消息格式的话,你可以将它添加到相同的.proto文件中,如:
syntax = "proto3";

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

message SearchResponse {
    //...
}

添加注释

  • 向.proto文件添加注释,可以使用C/C++/java风格的双斜杠(//) 语法格式,如:
syntax = "proto3";

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文 件这会导致严重的问题,包括数据损坏、隐私错误等等
  • 现在有一种确保不会发生这种情况的方法就是指定保留标识符(and/or names,which can also cause issues for JSON serialization不明白什么意思),protocol buffer的编译器会警告未来尝试使用这些域标识符的用户
message Foo {
    reserved 2, 15, 9 to 11;
    reserved "foo", "bar";
}
  • 备注:不要在同一行reserved声明中同时声明域名字和标识号

三、标量数值类型

  • “序列化消息时各种类型如何编码”的更多细节(https://developers.google.com/protocol-buffers/docs/encoding?hl=zh-cn):
    • 在java中,无符号32位和64位整型被表示成他们的整型对应形似,最高位被储存在标志位中
    • 对于所有的情况,设定值会执行类型检查以确保此值是有效
    • 64位或者无符号32位整型在解码时被表示成为ilong,但是在设置时可以使用int型值设定,在所有的情况下,值必须符合其 设置其类型的要求
    • python中string被表示成在解码时表示成unicode。但是一个ASCIIstring可以被表示成str类型
    • Integer在64位的机器上使用,string在32位机器上使用

默认值

  • 当一个消息被解析的时候,如果被编码的信息不包含一个特定的singular元素,被解析的对象锁对应的域被设置位一个默认值
  • 对于不同类型指定如下:
    • 对于strings,默认是一个空string
    • 对于bytes,默认是一个空的bytes
    • 对于bools,默认是false
    • 对于数值类型,默认是0
    • 对于枚举,默认是第一个定义的枚举值,必须为0
    • 对于消息类型(message),域没有被设置,确切的消息是根据语言确定的,详见https://developers.google.com/protocol-buffers/docs/reference/overview?hl=zh-cn
    • 对于可重复域的默认值是空(通常情况下是对应语言中空列表)
  • 注:对于标量消息域,一旦消息被解析,就无法判断域释放被设置为默认值(例如,例如boolean值是否被设置为false)还是根本没有被设置。你应该在定义你的消息类型时非常注意。例如,比如你不应该定义boolean的默认值false作为任何行为的触发方式。也应该注意如果一个标量消息域被设置为标志位,这个值不应该被序列化传输
  • 查看generated code guide(https://developers.google.com/protocol-buffers/docs/reference/overview?hl=zh-cn)选择你的语言的默认值的工作细节

枚举

  • 当需要定义一个消息类型的时候,可能想为一个字段指定某“预定义值序列”中的一个值。例如,假设要为每一个SearchRequest 消息添加一个corpus字段,而corpus的值可能是UNIVERSAL,WEB,IMAGES,LOCAL,NEWS,PRODUCTS或VIDEO中的一个。 其实可以很容易地实现这一点:通过向消息定义中添加一个枚举(enum)并且为每个可能的值定义一个常量就可以了
  • 在下面的例子中,在消息格式中添加了一个叫做Corpus的枚举类型(它含有所有可能的值),以及一个类型为Corpus的字段:
message SearchRequest {
    stirng 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;
}
  • ①每个枚举类型必须将其第一个类型映射为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),使用枚举中的一个类型来表示未识别的值,并且可以使用所支持整型来访问。在其他情况下,如果解析的消息被序列号,未识别的值将保持原样
  • 关于如何在你的应用程序的消息中使用枚举的更多信息,请查看所选择的语言:http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/reference/overview.html

五、其他消息类型

  • 你可以将其他消息类型用作字段类型。例如,假设在每一个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消息内,如:
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;
}

七、 import

  • 通过import引用其他.proto文件,类似于C语言的include

演示案例

  • 例如下面是IM.BaseDefine.proto文件
syntax = "proto3";
package IM.BaseDefine;

option optimize_for = LITE_RUNTIME;

enum PhoneType{
    PHONE_DEFAULT           = 0x0;		
    PHONE_HOME              = 0x0001;   // 家庭电话
    PHONE_WORK              = 0x0002;   // 工作电话     
}
  • 下面是IM.Login.proto文件,其引用了上面的IM.BaseDefine.proto文件
syntax = "proto3";
package IM.Login;
import "IM.BaseDefine.proto"; //引用IM.BaseDefine.proto
option optimize_for = LITE_RUNTIME;

message Phone{
    string number = 1;
    IM.BaseDefine.PhoneType phone_type = 2; //使用IM.BaseDefine.proto中的枚举类型
}

message Book{
    //...
}

message Person{
    //...
}

八、更新一个消息类型

  • 如果一个已有的消息格式已无法满足新的需求——如,要在消息中添加一个额外的字段——但是同时旧版本写的代码仍然可用。 不用担心!更新消息而不破坏已有代码是非常简单的
  • 在更新时只要记住以下的规则即可:
    • 不要更改任何已有的字段的数值标识
    • 如果你增加新的字段,使用旧格式的字段仍然可以被你新产生的代码所解析。你应该记住这些元素的默认值这样你的新代码 就可以以适当的方式和旧代码产生的数据交互。相似的,通过新代码产生的消息也可以被旧代码解析:只不过新的字段会被 忽视掉。注意,未被识别的字段会在反序列化的过程中丢弃掉,所以如果消息再被传递给新的代码,新的字段依然是不可用 的(这和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 ...
    }
}

十、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介绍(https://developers.google.com/protocol-buffers/docs/reference/overview?hl=zh-cn)

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字段
  • Tage 重用问题:
    • 将字段移入或移除oneof:在消息被序列号或者解析后,你也许会失去一些信息(有些字段也许会被清除)
    • 删除一个字段或者加入一个字段:在消息被序列号或者解析后,这也许会清除你现在设置的oneof字段
    • 分离或者融合oneof:行为与移动常规字段相似

十一、Map(映射)

  • 如果你希望创建一个关联映射,protocol buffer提供了一种快捷的语法:
    • key_type可以是任意Integer或者string类型(所以,除了floating和bytes的任意标量类型都是可以的)
    • value_type可以是任意类型
map<key_type, value_type> map_field = N;
  • 例如,如果你希望创建一个project的映射,每个Projecct使用一个string作为key,你可以像下面这样定义:
    • Map的字段可以是repeated
    • 序列化后的顺序和map迭代器的顺序是不确定的,所以你不要期望以固定顺序处理Map
    • 当为.proto文件产生生成文本格式的时候,map会按照key的顺序排序,数值化的key会按照数值排序
    • 从序列化中解析或者融合时,如果有重复的key则后一个key不会被使用,当从文本格式中解析map时,如果存在重复的key
map<string, Project> projects = 3;

向后兼容性问题

  • map语法序列化后等同于如下内容,因此即使是不支持map语法的protocol buffer实现也是可以处理你的数据的:
message MapFieldEntry {
    key_type key = 1;
    value_type value = 2;
}

repeated MapFieldEntry map_field = N;

十二、包(package)

  • 当然可以为.proto文件新增一个可选的package声明符,用来防止不同的消息类型有命名冲突
  • 例如,如果下面是一个C++项目的.proto文件,那么该.proto文件生成的代码中会生成两个命名空间,一个为命名空间foo,一个为命名空间bar,其中bar命名空间嵌套在foo命名空间中
//生成的C++代码中会有一个foo命名空间和一个foo::bar命名空间
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名称空间中

演示案例

  • 例如下面是一个名为IM.Login.proto的文件,其定义了package

  • 当生成C++代码(截取部分)之后,在代码中会有如下的命名空间

包及名称的解析

  • Protocol buffer语言中类型名称的解析与C++是一致的:首先从最内部开始查找,依次向外进行,每个包会被看作是其父类包的 内部类。当然对于 (foo.bar.Baz)这样以“.”分隔的意味着是从最外围开始的。
  • ProtocolBuffer编译器会解析.proto文件中定义的所有类型名。 对于不同语言的代码生成器会知道如何来指向每个具体的类型,即使它们使用了不同的规则

十三、定义服务

  • 如果想要将消息类型用在RPC(远程方法调用)系统中,可以在.proto文件中定义一个RPC服务接口,protocol buffer编译器将会根据所选择的不同语言生成服务接口代码及存根
  • 例如,想要定义一个RPC服务并具有一个方法,该方法能够接收 SearchRequest并返回一个SearchResponse,此时可以在.proto文件中进行如下定义:
service SearchService {
    rpc Search (SearchRequest) returns (SearchResponse);
}

十四、JSON映射

  • Proto3 支持JSON的编码规范,使他更容易在不同系统之间共享数据,在下表中逐个描述类型

  • 如果JSON编码的数据丢失或者其本身就是null,这个数据会在解析成protocol buffer的时候被表示成默认值。如果一个字段在 protocol buffer中表示为默认值,体会在转化成JSON的时候编码的时候忽略掉以节省空间。具体实现可以提供在JSON编码中可选的默认值

十五、选项

  • 在定义.proto文件时能够标注一系列的options。Options并不改变整个文件声明的含义,但却能够影响特定环境下处理方式。完整的可用选项可以在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编译器将通过在消息类型上执行序列化、语法分析及其他通用的操作。这种代码是最优的。
    • ODE_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(https://developers.google.com/protocol-buffers/docs/reference/arenas?hl=zh-cn)
  • objc_class_prefix(文件选项):设置Objective­C类的前缀,添加到所有Objective­C从此.proto文件产生的类和枚举类型。没 有默认值,所使用的前缀应该是苹果推荐的3­5个大写字符,注意2个字节的前缀是苹果所保留的。
  • deprecated(字段选项):如果设置为true则表示该字段已经被废弃,并且不应该在新的代码中使用。在大多数语言中没有实际 的意义。在java中,这回变成@Deprecated注释,在未来,其他语言的代码生成器也许会在字标识符中产生废弃注释,废弃 注释会在编译器尝试使用该字段时发出警告。如果字段没有被使用你也不希望有新用户使用它,尝试使用保留语句替换字段声明
int32 old_field = 6 [deprecated=true];

自定义选项

十六、从.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:https://developers.google.com/protocol-buffers/docs/reference/overview?hl=zh-cn

猜你喜欢

转载自blog.csdn.net/qq_41453285/article/details/106731318