Protobuf:消息更新

Protobuf:消息更新

    • 更新字段
    • 保留字段
    • 未知字段
    • option选项


在开发中,需要对产品进行版本迭代。迭代前后,类的成员可能就会有所改动,一旦类成员改动,那么老版本的对象,新版本可能就无法解析,此时就会出现问题。为此,Protobuf设计了一套机制,用于对消息进行更新,使其可以向前向后兼容。

更新字段

如果说,消息前后,部分变量的类型发生改变,此时在部分情况下,是可以完成兼容的。

更新前:

message Person {int32 id = 1;int32 age = 2;string name = 3;
}

更新后:

message Person {int64 id = 1;int64 age = 2;string name = 3;
}

这就是一个合法的类型修改。

  1. int32uint32int64uint64bool这几个类型之间相互兼容,可以进行转化。

变长整型家族的类型,都是基于VarInt编码 ,编码方式是一样的,因此可以进行转化。如果从64位整型转到32位整型,此时就会发生截断,与C/C++的整型处理策略一致。

  1. sint32sint64相互兼容

这两个整型使用VarInt编码 + ZigZag编码,因为相比于上面四个类型,引入了ZigZag编码来提高负数的存储效率,所以无法与之前的整型兼容,这两个整型自成一派。

  1. stringbytes在采用UTF-8对字符进行编码的情况下,可以相互兼容

  2. fixed32sfixed32兼容

  3. fixed64sfixed64兼容

这些采用的都是定长编码,所以可以相互兼容。但是不能跨位数进行兼容,32位之间相互兼容,64位之间相互兼容。

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

如果你有一个现有的字段,并决定将其移入一个新的 oneof,这是安全的。因为在 ProtoBuf 的序列化格式中,oneof 的字段与普通字段的序列化格式是相同的。

假设有以下消息定义:

message OriginalMessage {string name = 1;int32 age = 2;
}

可以安全地将 name 移入一个新的 oneof

message UpdatedMessage {oneof identifier {string name = 1;}int32 age = 2;
}
  1. 若确定没有代码一次性设置多个值,那么将多个字段移入一个新 oneof 类型也是可行的

如果确保在所有使用该消息的代码中,多个字段不会同时被设置,那么可以将这些字段一起移入一个新的 oneof,以实现更严格的数据约束。

message OriginalMessage {string first_name = 1;string last_name = 2;
}

假设确定 first_namelast_name 从未同时被设置,可以重构为:

message UpdatedMessage {oneof name {string first_name = 1;string last_name = 2;}
}
  1. 将任何字段移入已存在的 oneof 类型是不安全的

如果将一个字段移入到已经存在的 oneof 中,这可能会导致数据不兼容,因为现在的 oneof 中可能已经存在其他字段,数据的含义会发生改变。

message OriginalMessage {oneof contact {string email = 1;string phone_number = 2;}string address = 3;
}

如果你尝试将 address 移入现有的 oneof contact 中:

message UnsafeMessage {oneof contact {string email = 1;string phone_number = 2;string address = 3;  // 移动到 oneof 中}
}

这样做是不安全的,因为现有的二进制数据可能已经使用了 address 字段,而将其移入 oneof 会导致反序列化时数据被误解。


保留字段

有的时候,需要删除消息中的字段,此时就要用到保留字段

因为protobuf中使用字段编号来标识一个数据,如果说直接删除一个字段,那么此时就可能出现数据错误的问题。

message Person {int32 id = 1;int32 age = 2;string name = 3;
}

删除name字段后,又增加了gender字段:

message Person {int32 id = 1;int32 age = 2;string gender = 3;
}

此时就会导致问题,如果旧版本的客户发送了旧版本的消息,此时根据字段编号,gender性别就会收到name姓名的信息,此时就会发生错误。

也就是说:删除字段后,字段编号不能重复使用

为此,protobuf提供了一个reserved关键字,用于保留字段编号与变量名,被保留的字段编号与变量名就不能再被使用。

message Person {reserved 3;reserved "name";int32 id = 1;int32 age = 2;// string name = 3;string gender = 3;
}

此处通过reserved保留了字段编号3,以及变量名"name",那么这两个内容就不能在该message内部使用,string gender = 3会报错。

reserved还支持多种格式:

一次保留多个字段编号:

reserved x, y, z;

一次保留多个变量名:

reserved "aaa", "bbb", "ccc";

保留一个区间内的字段编号:

reserved x to y;

未知字段

如果一个旧版本的客户端,收到一个新版本的消息,会发生什么?

protobuf接收消息时,如果发现消息内含有自己无法识别的字段,会将该字段放到未知字段

现有以下消息类型:

syntax = "proto3";
package test_pkg;message Person {int32 id = 1;int32 age = 2;string name = 3;
}

编译后,生成对应的Person对象,然后把对象序列化至文件test.txt中:

#include <iostream>
#include <fstream>
#include "test.pb.h"using namespace std;
using namespace test_pkg;int main()
{Person person;person.set_id(1);person.set_age(18);person.set_name("张三");ofstream ofs("test.txt", ios_base::binary);string str;person.SerializeToString(&str);ofs << str;ofs.close();return 0;
}

序列化成功后,可以通过protoc --decode查看序列化结果,命令格式:

protoc --decode=包名.消息名 源文件.proto < 被解析文件

此处 源文件.proto指定消息所处的文件,包名.消息名是要解析的消息类型,被解析文件内部是序列化后的结果。

在这里插入图片描述

可以看到,test.txt内的数据,被正确解析出来了。此处name字段存储的是字符串的编码。

随后修改消息类型:

syntax = "proto3";
package test_pkg;message Person {reserved 2, 3;int32 id = 1;
}

删掉了agename字段。

再次通过新的消息类型解析test.txt

在这里插入图片描述

可以看到,无法再识别agename字段了,但是它依然可以判断数据存储的内容,以及对应的字段编号。

如果客户端得到反序列化后的消息,不会把无法识别的字段丢弃,而是存储到未知字段

protobuf的类架构:

在这里插入图片描述

注意后续的用于:Message表示上图的类,message表示用户定义的消息

  • Message
    • 所有message继承Message
    • 提供了GetDescriptorGetReflection接口,用于访问ReflectionDescriptor
  • MessaageLite
    • 该类提供序列化与反序列化的接口,Message继承该类
  • Descriptor
    • message的描述,比如message的名字,所包含的字段等
  • Reflection
    • 提供了messagesetget等基本操作接口
    • 提供GetUnknownFields接口,用于访问未知字段
  • UnknownFieldSet:未知字段集
    • 该类包含了所有无法解析的字段
  • UnknownField:未知字段
    • 用于描述一个具体的未知字段

接下讲解如何操作未知字段。

UnknownFieldSet类中,维护了一个集合,内部存储所有的未知字段,在unknown_field_set.h头文件中,包含了该类的声明:

class PROTOBUF_EXPORT UnknownFieldSet {public:UnknownFieldSet();~UnknownFieldSet();inline void Clear();inline bool empty() const;inline int field_count() const;inline const UnknownField& field(int index) const;
};

这些都是很简单的接口,field_count统计未知字段集中有多少个未知字段,field(int index)获取指定下标的未知字段,返回一个UnknownField类型引用。

`UnknownField声明如下:

// Represents one field in an UnknownFieldSet.
class PROTOBUF_EXPORT UnknownField {public:enum Type {TYPE_VARINT,TYPE_FIXED32,TYPE_FIXED64,TYPE_LENGTH_DELIMITED,TYPE_GROUP};inline int number() const;inline Type type() const;// Accessors -------------------------------------------------------// Each method works only for UnknownFields of the corresponding type.inline uint64_t varint() const;inline uint32_t fixed32() const;inline uint64_t fixed64() const;inline const std::string& length_delimited() const;inline const UnknownFieldSet& group() const;inline void set_varint(uint64_t value);inline void set_fixed32(uint32_t value);inline void set_fixed64(uint64_t value);inline void set_length_delimited(const std::string& value);inline std::string* mutable_length_delimited();inline UnknownFieldSet* mutable_group();
};

第一个枚举用于表示这个未知字段的具体类型,其中TYPE_LENGTH_DELIMITED是字符串string

  • number:返回该未知字段的字段编号
  • type:返回该未知字段的类型

当检测到具体类型后,调用下面的接口来获取与设置值,比如varint()就是获取字段的整型值。

接下来写一个代码,解析刚才的test.txt

#include <iostream>
#include <fstream>
#include <google/protobuf/unknown_field_set.h>
#include "test.pb.h"using namespace std;
using namespace test_pkg;
using namespace google::protobuf;int main()
{ifstream ifs("test.txt", ios_base::binary);Person ps;ps.ParseFromIstream(&ifs);cout << "Id: " << ps.id() << endl;const Reflection* ref = Person::GetReflection();const UnknownFieldSet& ufs = ref->GetUnknownFields(ps);for (int i = 0; i < ufs.field_count(); i++){const UnknownField& uf = ufs.field(i);cout << "未知字段" << endl;cout << "   字段编号: " << uf.number() << endl;cout << "   字段类型: " << uf.type() << endl;switch (uf.type()){case UnknownField::Type::TYPE_LENGTH_DELIMITED: // 字符串cout << "   string: " << uf.length_delimited() << endl;break;case UnknownField::Type::TYPE_VARINT:cout << "   varint: " << uf.varint() << endl;}}return 0;
}

首先,如果要使用位置字段,先要包含头文件<google/protobuf/unknown_field_set.h>

一开始通过ifstream读取test.txt文件,然后通过文件流反序列化数据到对象ps中。

由于访问未知字段集,要通过Reflection类,所以通过GetReflection构造一个该类。

再通过ref->GetUnknownFields(ps),获取到ps内的未知字段集。

一层for循环,遍历未知字段集的所有元素,并且输出。

输出结果:

在这里插入图片描述

可以得知,protobuf确实是吧无法识别的字段放到未知字段集了,并且给出了接口,让用户可以访问未知字段。


option选项

protobuf中,提供了一个option关键字,其用于指定编译器的处理方式。

语法:

option 选项 =;
  • optimize_for:文件选项,设置protoc编译器的优化级别
    • SPEED:高度优化代码,运行效率最高,但是会占用更多空间,是默认选项
    • CODE_SIZE:减少空间占用,但是会降低代码都运行效率,如果proto文件比较多,并且对效率要求不高时,建议启用该选项
    • LITE_RUNTIME:生成的代码效率高,空间占用也少,但是只提供序列化与反序列化接口,也就是只继承MessageLite类,不再提供Reflection接口

示例:

syntax = "proto3";
package test_pkg;option optimize_for = CODE_SIZE;message Person {int32 id = 1;
}

此时该proto文件就会以CODE_SIZE模式进行编译,此时空间占用降低,但是代码效率也会降低。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/446749.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

一文了解 Linux 系统的文件权限管理

文章目录 引入Linux文件权限模型查看文件权限权限信息解析修改文件权限符号模式八进制数字模式 引入 在Linux操作系统中&#xff0c;我们想查看我们对文件拥有哪些权限时&#xff0c;可以在终端键入ls -l或ll命令&#xff0c;终端会输出当前路径下的文件信息&#xff0c;如文件…

【网络】【Linux】多路转接技术

多路转接技术 文章目录 1.select1.1select系统调用及参数介绍1.2select基本工作流程1.3select技术实现echo服务器1.4select优缺点1.5select的适用场景 2.poll&#xff08;了解&#xff09;2.1poll系统调用及参数介绍2.2poll技术实现echo服务器2.3poll优缺点 3.epoll3.1epoll系…

【新人系列】Python 入门(二):Python IDE 介绍

✍ 个人博客&#xff1a;https://blog.csdn.net/Newin2020?typeblog &#x1f4dd; 专栏地址&#xff1a;https://blog.csdn.net/newin2020/category_12801353.html &#x1f4e3; 专栏定位&#xff1a;为 0 基础刚入门 Python 的小伙伴提供详细的讲解&#xff0c;也欢迎大佬们…

【Windows命令】Windows下启动Nginx后,在任务管理器里面没有发现nginx.exe进程

如题&#xff0c;当在本地Windows环境下想用反向代理时&#xff0c;突然发现在任务管理器里面没有发现nginx.exe进程&#xff0c;但是端口又是占用的。这时就要用Windows命令了。 查询端口占用 netstat -ano | findstr :80 根据进程ID&#xff08;pid&#xff09;查询进程名称…

ESP32移植Zephyr RTOS(一)-----hello world

硬件平台&#xff1a;实战派ESP32-C3开发板 zephyr版本&#xff1a;Zephyr version 3.7.99 开发环境&#xff1a;ubuntu 24.4 之前一直想用正点原子阿波罗F4来写zephyr系列教程来自&#xff0c;但是本人水平有限RGB LCD实在是搞不懂&#xff0c;遂放弃&#xff0c;正好手头有一…

行业标准丨《变电站智能巡检导则:图像识别》(征求意见稿)

2024年8月30日&#xff0c;能源行业电网设备智能巡检标准化技术委员会秘书处组织召开行业标准《变电站智能巡检导则第6部分:图像识别》编制启动会&#xff0c;2024年9月30日&#xff0c;能源行业电网设备智能巡检标准化技术委员会秘书处将征求意见稿在委员单位、有关单位和中国…

SLM883x系列SLM8834两个零漂移可设置和稳定TEC温度 超紧凑高效率高精度TEC控制器

SLM883x系列SLM8834是集成了双路功率调节器的单片TEC控制器。内部带有一个线性功率级、一个脉宽调制&#xff08;PWM&#xff09;功率级和两个零漂移、轨对轨运算放大器。线性功率级与PWM功率级同时工作&#xff0c;以控制H桥配置中的内部功率级的双向输出。通过测量热传感器反…

TinyOS 点对基站通信

文章目录 一、前言1.1 发包的BlinkToRadio的数据包格式 二、混淆基站源码分析2.1 Makefile2.2 组件连接2.3 主逻辑代码 一、前言 1.1 发包的BlinkToRadio的数据包格式 如下&#xff0c;注意&#xff1a;AM层类型(1byte)即handlerID使可以在组件中修改的。 二、混淆基站源码…

请确保已在git上配置你的user.name和user.email

问题&#xff1a;使用vscode在远程服务器上暂存修改报错&#xff1a; 原因&#xff1a;未在远程服务器上配置该项目对应的git的username和useremail 解决方法&#xff1a; 在vscode中新建一个终端 命名&#xff1a; git config --global user.email "youexample.com&qu…

2015年国赛高教杯数学建模C题月上柳梢头解题全过程文档及程序

2015年国赛高教杯数学建模 C题 月上柳梢头 月上柳梢头&#xff0c;人约黄昏后”是北宋学者欧阳修的名句&#xff0c;写的是与佳人相约的情景。请用天文学的观点赏析该名句&#xff0c;并进行如下的讨论&#xff1a;   1. 定义“月上柳梢头”时月亮在空中的角度和什么时间称为…

SketchUp Pro 2024 for Mac 3D建模 草图设计大师软件安装【保姆级教程,简单小白轻松上手】

Mac分享吧 文章目录 SketchUp Pro 3D建模 草图设计大师软件 安装完成&#xff0c;软件打开效果一、Mac中安装SketchUp Pro 3D建模 草图设计大师软件——v241️⃣&#xff1a;下载软件2️⃣&#xff1a;安装软件&#xff0c;将安装包从左侧拖入右侧文件夹中3️⃣&#xff1a;应…

树莓派应用--AI项目实战篇来啦-5.OpenCV绘画函数的使用

1. 介绍 OpenCV作为一款功能强大的计算机视觉库&#xff0c;被广泛地应用于图像处理和计算机视觉领域。 除了在机器视觉和人工智能领域有者广泛的应用&#xff0c;OpenCV 还能够媲美艺术家的创造力&#xff0c;通过其强大的绘图函数&#xff0c;绘制出令人叹为观止的艺术画作。…

子组件向父组件传值$emit

点击子组件的按钮&#xff0c;将子组件的值传递给父组件&#xff0c;并进行提示。 子组件 <template><div><button click"emitIndex">clickme</button></div> </template> <script> export default {methods: {emitInde…

petalinux 自动登陆 自动启动程序

PetaLinux 自动登陆 (1) cd 到项目工程目录下&#xff1b; (2) 运行命令&#xff1a;petalinux-config -c rootfs (3) 依次选择 Image Features -> serial-autologin-root 保存退出 创建APP petalinux-create apps --template install --name init-app --enable编辑文件 …

轧钢测径仪安装前要做哪些准备工作?

轧钢测径仪是用于检测线材、棒材、管材的外径、椭圆度尺寸&#xff0c;螺纹钢的内径、横肋、纵肋尺寸的精密仪器&#xff0c;它是在线检测设备&#xff0c;被按照在环境复杂的轧制现场&#xff0c;为了保证测径仪的顺利安装&#xff0c;必要的前期准备工作要做好。 现场勘查&…

QT元对象系统特性详细介绍(信号槽、类型信息、动态设置属性)(注释)

目 录 一、元对象系统简介 二、信号和槽 三、类型信息 四、动态设置属性 一、元对象系统简介 QT中的元对象系统Q_OBJECT并不是C标准代码&#xff0c;因此在使用时需要QT的MOC&#xff08;元对象编译器&#xff09;进行预处理&#xff0c;MOC会在编译时期读取C代码中的特定…

【华为】配置BGP协议

边界网关协议BGP是一种实现自治系统AS之间的路由可达&#xff0c;并选择最佳路由的距离矢量路由协议。BGP在不同自治系统之间进行路由转发&#xff0c;分为EBGP&#xff08;外部边界网关协议&#xff09;和IBGP&#xff08;内部边界网关协议&#xff09;两种情况。 [A]in g0/0/…

自动泊车变自动撞车?高速连接器如何助力智驾安全

当ADAS成为人们行车过程中的常伴辅助&#xff0c;颠覆人类驾驶方式的无人驾驶何时才能到来&#xff1f; 今年上半年&#xff0c;搭载了L2级辅助驾驶功能的新能源汽车渗透率达到了66.4%。自动驾驶领赛道作为新能源汽车智能化竞演中的“嫡赛道”&#xff0c;有股要席卷整个市场的…

执行vue create XXX报错The operation was rejected by your operating system

创建项目&#xff1a; vue create my-project 报错&#xff1a; npm ERR! code EPERM npm ERR! syscall open npm ERR! path D:\Program Files\nodejs\node_cache\_cacache\tmp\5d2a6f8e npm ERR! errno -4048 npm ERR! Error: EPERM: operation not permitted, open D:\Pro…

macOS Sequoia 15.0.1

macOS Sequoia 推出了一系列新功能&#xff0c;可助你在 Mac 上提高生产力和创造力。通过最新连续互通功能 iPhone 镜像&#xff0c;你可以在 Mac 上访问整个 iPhone。轻松平铺窗口快速打造理想工作空间&#xff0c;还可查看通过演讲者前置演示时即将共享的内容。经过重大更新的…