【protobuf】ProtoBuf——proto3语法详解、更新消息、更新规则、保留字段、未知字段、前后兼容性、选项option、选项分类、常用选项列举、设置自定义选项

ProtoBuf

在这里插入图片描述

  

5. proto3语法详解

5.8 更新消息

  在 Protobuf(Protocol Buffers),更新消息通常是指对已定义的消息类型进行修改或扩展。Protobuf 具有良好的向前和向后兼容性。

  如果现有的消息类型已经不再满足我们的需求,例如需要扩展一个字段,在不破坏任何现有代码的情况下更新消息类型必须遵循如下规则。

  

5.8.1 更新规则

  规则 1:禁止修改任何已有字段的字段编号。

  示例:假设有个 Person 消息类型,其中 name 字段的编号是 1,就不能将其改为其他数字。

message Person {
  string name = 1;
}

  

  规则 2:int32,uint32,int64,uint64 和 bool 是完全兼容的。可以从这些类型中的一个改为另一个,而不破坏前后兼容性。 若解析出来的数值与相应的类型不匹配,会采用与 C++ 一致的处理方案(例如,若将 64 位整数当做 32 位进行读取,它将被截断为 32 位)。

  示例:原本是 int32 的 score 字段可以改为 int64 。

message Game {
  int64 score = 1;  // 之前可能是 int32
}

  

  规则 3:sint32 和 sint64 相互兼容但不与其他的整型兼容。

  示例:sint32 的 count 可以改为 sint64 。

message Data {
  sint64 count = 1;  // 之前是 sint32
}

  

  规则 4:string 和 bytes 在合法 UTF-8 字节前提下也是兼容的。

  示例:description 字段可以在 string 和 bytes 类型间切换。

message Info {
  bytes description = 1;  // 之前可能是 string
}

  

  规则 5:bytes 包含消息编码版本的情况下,嵌套消息与 bytes 也是兼容的。

  示例:假设有个嵌套的 Version 消息,version_info 字段可以是 bytes 类型。

message Version {
  int32 major = 1;
  int32 minor = 2;
}

message App {
  bytes version_info = 1;
}

  

  规则 6:fixed32 与 sfixed32 兼容,fixed64 与 sfixed64 兼容。

  示例:fixed32 的 id 字段可改为 sfixed32 。

message Item {
  sfixed32 id = 1;  // 之前是 fixed32
}

  

  规则 7:enum 与 int32,uint32,int64 和 uint64 兼容(注意若值不匹配会被截断)。 但要注意当反序列化消息时会根据语言采用不同的处理方案:例如,未识别的 proto3 枚举类型会被保存在消息中,但是当消息反序列化时如何表示是依赖于编程语言的。整型字段总是会保持其的值。

  示例:status 枚举字段可以与 int32 相互转换。

enum Status {
  PENDING = 0;
  COMPLETED = 1;
}

message Task {
  int32 status = 1;  // 之前是 enum Status
}

  

  规则 8:oneof:

  将一个单独的值更改为新 oneof 类型成员之一是安全和二进制兼容的。

  示例:value 原本是普通字段,现在改为 oneof 中的成员。

message Data {
  oneof data {
    string value = 1;
  }
}

  若确定没有代码一次性设置多个值,那么将多个字段移入一个新 oneof 类型也是可行的。

  示例:多个字段 field1 和 field2 移入一个新的 oneof 。

message Data {
  oneof new_data {
    string field1 = 1;
    int32 field2 = 2;
  }
}

  将任何字段移入已存在的 oneof 类型是不安全的,可能会导致数据丢失。

  

5.8.2 保留字段

  如果通过删除或注释掉字段来更新消息类型,这可能会在未来引发问题。 因为未来的用户在添加新字段时,有可能会误用以前已被删除或注释掉的字段编号。当使用旧版本的.proto文件时,这可能会导致数据损坏、隐私错误等诸多问题。

  为确保不发生这种情况,可以使用reserved关键字将指定字段的编号或名称设置为保留项。这样,当再次使用这些编号或名称时,Protocol Buffer 的编译器将会发出警告,提示这些编号或名称不可用。

  
  若是移除老字段,要保证不再使用移除字段的字段编号,不建议直接删除或注释掉字段。应该保留字段编号(reserved),以确保该编号将不能被重复使用。
  

message Message {
	// 设置保留项
	reserved 1, 10, 20 to 30;
	reserved "field3", "field4";
	// 注意:不要在⼀⾏ reserved 声明中同时声明字段编号和名称。
	// reserved 102, "field5";

	// 设置保留项之后,下⾯代码会告警
	int32 field1 = 1; //告警:Field 'field1' uses reserved number 1
	int32 field2 = 2; //告警:Field 'field2' uses reserved number 2
	int32 field3 = 11; //告警:Field name 'field3' is reserved
	int32 field4 = 12; //告警:Field name 'field4' is reserved
}

  使用了 reserved 关键字,ProtoBuf在编译阶段就拒绝了我们使用已经保留的字段编号。

  

5.8.3 未知字段

  未知字段是在解析 Protocol Buffer 已序列化数据时,对未被识别的新字段的表示方式。

  如果我们向服务器中新增了字段,但是对于我们的客户端的代码没有新增相同的字段类型时,因为编译器会解析服务端发送的所有信息,如果此时客户端解析到了来自服务端的新增的字段代码,客户端不会将它丢弃,而是进行保留,作为旧程序的未知字段。

  
  未知字段:解析结构良好的 protocol buffer 已序列化数据中的未识别字段的表示方式。例如,当旧程序解析带有新字段的数据时,这些新字段就会成为旧程序的未知字段。

  本来,proto3 在解析消息时总是会丢弃未知字段,但在 3.5 版本中重新引⼊了对未知字段的保留机制。所以在 3.5 或更高版本中,未知字段在反序列化时会被保留,同时也会包含在序列化的结果中。

  
  未知字段的获取:

在这里插入图片描述

  

  MessageLite类:

  MessageLite从名字看是轻量级的message,仅仅提供序列化、反序列化功能。

  类定义在google提供的message_lite.h中。

在这里插入图片描述

  

  Message类:

  我们自定义的message类都是继承于Message类。

  Message类中最重要的两个接口GetDescriptor/GetReflection,可以获取该类型对应的Descriptor对象指针和Reflection对象指针。

  类定义在google提供的message.h中。

在这里插入图片描述
在这里插入图片描述

  

  Descriptor类:

  是对与我们自定义的message类的描述,包括自定义message的名字、所有字段的描述、原始的proto文件等。

  类定义在google提供的descriptor.h中。

在这里插入图片描述
在这里插入图片描述

  

  Reflection类:

  主要提供了动态读写消息字段的接口,对消息对象的自动读写主要通过该类完成。

  提供方法来动态访问/修改message中的字段,对每种类型,Reflection都提供了⼀个单独的接口用于读写字段对应的值。

  针对所有不同的field类型 FieldDescriptor::TYPE_* ,需要使用不同的 Get*()/Set*()/Add*() 接口;

  repeated类型需要使用 GetRepeated*()/SetRepeated*() 接口,不可以和非repeated类型接口混用;

  message对象只可以被由它自身的 reflection(message.GetReflection()) 来操作;

  类中还包含了访问/修改未知字段的方法。

  定义在google提供的message.h中。

在这里插入图片描述
在这里插入图片描述

  

  UnknownFieldSet类:

  UnknownFieldSet包含在分析消息时遇到但未由其类型定义的所有字段。

  若要将UnknownFieldSet附加到任何消息,请调用Reflection::GetUnknownFields()。

  类定义在unknown_field_set.h中。

在这里插入图片描述
在这里插入图片描述

  

  UnknownField类:

  表示未知字段集中的⼀个字段。

  类定义在unknown_field_set.h中。

在这里插入图片描述

  

5.8.4 前后兼容性

  在 Protocol Buffer 中,向前兼容指老模块能正确识别新模块生成或发出的协议,如在 pb 3.5 版本及之后,新增加的属性会被老模块当作未知字段处理。向后兼容则是新模块能正确识别老模块生成或发出的协议

  前后兼容在维护庞大的分布式系统时非常重要,因为无法同时升级所有模块,为保证升级过程中系统尽可能不受影响,就需要尽量保证通讯协议的向后兼容或向前兼容。

  

5.9 选项option

  .proto 文件中可以声明许多选项,使用 option 标注。选项能影响 proto 编译器的某些处理方式。

  

5.9.1 选项分类

  选项的列表在google/protobuf/descriptor.proto中定义。

  选项分为文件级、消息级、字段级 等等, 但并没有一种选项能作用于所有的类型。

在这里插入图片描述

  

5.9.2 常用选项列举

  optimize_for 是 Protocol Buffer 的文件选项,可设置 protoc 编译器的优化级别,有 “SPEED”“CODE_SIZE”“LITE_RUNTIME” 三种。不同优化级别编译.proto 文件后生成的代码内容不同。

  
  SPEED:是默认选项,protoc 编译器生成高度优化的代码,运行效率高,但编译后占用更多空间。

  CODE_SIZE:编译器生成最少的类,占用更少空间,依赖反射实现序列化等操作,运行效率较低,适合包含大量.proto 文件且不盲目追求速度的应用。

  LITE_RUNTIME:生成的代码执行效率高且编译后占用空间少,以牺牲 Protocol Buffer 的反射功能为代价,仅提供编码和序列化功能,在链接库时仅需链接 libprotobuf-lite 而非 libprotobuf,通常用于资源有限的平台如移动手机平台。

  例如:option optimize_for = LITE_RUNTIME;

  
  allow_alias 是枚举选项,允许将相同的常量值分配给不同的枚举常量以定义别名。例如:

enum PhoneType {
  option allow_alias = true; // 若不加 “option allow_alias = true;” 这一行,会编译报错
  MP = 0;
  TEL = 1;
  LANDLINE = 1; 
}

  

5.9.3 设置自定义选项

自定义协议介绍

            

  • 10
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鳄鱼麻薯球

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值