【ProtoBuf】使用指南

一.什么是ProtoBuf

特点:ProtoBuf是用于序列化和反序列化的一种方法,类似xml和json,但是效率更高,体积更小。ProtoBuf具有语⾔⽆关、平台⽆关,扩展性、兼容性好等特点。

ProtoBuf是需要依赖通过编译生成的头文件和源文件来使用的。也就是说他需要参与到编译链接的过程中。

使用方法简单来说,我们之需要写一个包含待处理数据的message,就可以通过protoc编译器编译.proto文件自动生成接口,在后续业务逻辑中我们直接使用这些接口即可。

二.形成proto文件

在protobuf里面定义的结构化对象叫message,在这个结构化对象中定义其对应的属性内容。在这里我们使用proto3语法,需要手动指定。同时指定包(类比命名空间)。

定义消息字段:字段类型+字段名=字段唯⼀编号。

这里的字段类型和c++一致(在类型后面带上位数)。字段唯⼀编号是⽤来标识字段,⼀旦开始使⽤就不能够再改变。但注意,这里有可能存在变长编码,变⻓编码是指:经过protobuf编码后,原本4字节或8字节的数可能会被变为其他字节数。

通过以上知识就能得到如下用例。

syntax = "proto3";  //指定语法
package contacts;   //命名空间message StudentInfo
{string name = 1;  // 姓名int age = 2;    // 年龄  
}

字段唯⼀编号的范围:1~536,870,911(2^29-1),其中19000~19999不可⽤。范围为1~15的字段编号需要⼀个字节进⾏编码,16~2047内的数字需要两个字节进⾏编码。编码后的字节不仅只包含了编号,还包含了字段类型。1~15要⽤来标记出现⾮常频繁的字段。

接下来编译生成C++文件
编译命令格式:protoc [--proto_path=IMPORT_PATH] --cpp_out=DST_DIR path/to/file.proto

【】内可以省略

在这里就可以是protoc --cpp_out=. contacts.proto 

编译后⽣成了两个⽂件: contacts.pb.h  contacts.pb.cc,

对于编译⽣成的C++代码,包含了以下内容:

  • 对于每个message,都会⽣成⼀个对应的消息类。
  • 在消息类中,编译器为每个字段提供了获取和设置⽅法
  • 编辑器会针对于每个.proto ⽂件⽣成 .h 和 .cc ⽂件,分别⽤来存放类的声明与类的实现
     

这样就能得到基本的操作接口,类似这种 

接下来就可以使用序列化和反序列化了在消息类的⽗类中能找到MessageLite,这里提供了序列化反序列化的接口。

//序列化:
bool SerializeToOstream(ostream* output) const; // 将序列化后数据写⼊⽂件
流
bool SerializeToArray(void *data, int size) const;
bool SerializeToString(string* output) const;
//反序列化:
bool ParseFromIstream(istream* input); // 从流中读取数据,再进⾏反序列化
动作
bool ParseFromArray(const void* data, int size);
bool ParseFromString(const string& data);

这样就能把数据结构转换为二进制结构。详情可参考protobuf官网。这样就能实现从序列化到反序列的整体逻辑,注意在编译的时候要告知编译器所使用的库名称。

三.详细语法
 

一.字段规则

  • singular:消息中可以包含该字段零次或⼀次(不超过⼀次)。proto3语法中,字段默认使⽤该规则
  • repeated:消息中可以包含该字段任意多次(包括零次),其中重复值的顺序会被保留。可以理解为定义了⼀个数组
    syntax = "proto3";
    package contacts;message StudentInfo 
    {
    string name = 1;
    int32 age = 2;
    repeated string phone_numbers = 3;    //能有多个电话
    }
    
     

二.使用消息字段

proto里的消息体是可以重复嵌套的。

同时消息类型是可以充当字段来使用的。

syntax = "proto3";
package contacts;message StudentInfo 
{string name = 1;int32 age = 2;message Phone{string number = 1;}repeated Phone phone = 3;}

我们也可以在一个proto内部导入其他proto的消息类型

//使用 import 导入其他类型的proto文件
import "phone.proto"; // 引⼊的⽂件声明了package,使⽤消息时,需要⽤ ‘命名空间.消息类型’ 格式message test
{phone.Phone phone = 1;
}

注意在使用api对消息类型进行赋值的时候有两个接口,mutable_⽅法,返回值为消息类型的指针,这类方法会为我们开辟好空间,可以直接对这块空间的内容进⾏修改。或用 set_allocated_这个函数需要手动传入一个你自己开辟好的空间。

三.enum 类型

要注意枚举类型的定义有以下几种规则:
1. 0值常量必须存在,且要作为第⼀个元素。这是为了与proto2的语义兼容:第⼀个元素作为默认
值,且值为0。
2. 枚举类型可以在消息外定义,也可以在消息体内定义(嵌套)。
3. 枚举的常量值在32位整数的范围内。但因负值⽆效因⽽不建议使⽤(与编码规则有关)

同时具有相同枚举值名称不能出现在同一级别下。同级(同层)的枚举类型,各个枚举类型中的常量不能重名。
• 单个.proto⽂件下,最外层枚举类型和嵌套枚举类型,不算同级。
• 多个.proto⽂件下,若⼀个⽂件引⼊了其他⽂件,且每个⽂件都未声明package,每个proto⽂
件中的枚举类型都在最外层,算同级。
• 多个.proto⽂件下,若⼀个⽂件引⼊了其他⽂件,且每个⽂件都声明了package,不算同级。
 

在使用枚举类型的时候使用诸如set_,或者type,用来设置和获取枚举类型

四.Any类型

Any类型可以看成c++的auto泛型类型,使⽤时可以在Any中存储任意消息类型,Any类
型的字段也⽤repeated来修饰。注意在使用的时候引入any.proto

import "google/protobuf/any.proto";message test
{google.protobuf.Any data = 1;
}

对于any类型来说,设置方法可以用mutable方法修改。

any类型可以和普通类型之间可以互相转换,使用PackFrom() 方法可以将任意消息类型转为 Any 类型。使⽤ UnpackTo() ⽅法可以将 Any 类型转回之前设置的任意消息类型。使用 Is() 方法可以⽤来判断存放的消息类型是否为 typename T。

五.oneof类型

表示这其中的字段同时只有一个字段会被设置。同时oneof里不能设置repeated,若是在oneof里多次设置,则会保留最后一次设置字段属性,可以用诸如_case方法获取设置了哪一个字段

六.map类型

可以类比c++中的map类型

map<key_type, value_type> map_field = N;

注意key值不能是float或者double.使用map方法也用mutable方法进行设置

四.语法的细节处理

  •  对于字符串,默认值为空字符串。
  •  对于字节,默认值为空字节。
  •  对于布尔值,默认值为false。
  •  对于数值类型,默认值为0。、
  • 对于枚举,默认值是第⼀个定义的枚举值,必须为0。
  • 于设置了repeated的字段的默认值是空的(通常是相应语⾔的⼀个空列表)
  • 对于 消息字段 、 oneof字段 和 any字段 ,C++和Java语⾔中都有has_⽅法来检测当前字段
    是否被设置
     

更新消息字段:

  • 禁⽌修改任何已有字段的字段编号。
  • 若是移除⽼字段,要保证不再使⽤移除字段的字段编号。正确的做法是保留字段编号(reserved),以确保该编号将不能被重复使⽤。不建议直接删除或注释掉字段。
  • int32,uint32,int64,uint64和bool是完全兼容的。可以从这些类型中的⼀个改为另⼀个,⽽不破坏前后兼容性。若解析出来的数值与相应的类型不匹配,会采⽤与C++⼀致的处理⽅案(例如,若将64位整数当做32位进⾏读取,它将被截断为32位)。
  • sint32和sint64相互兼容但不与其他的整型兼容。
  •  string和bytes在合法UTF-8字节前提下也是兼容的。
  •  fixed32与sfixed32兼容,fixed64与sfixed64兼容。
  • oneof:
    ◦ 将⼀个单独的值更改为新oneof类型成员之⼀是安全和⼆进制兼容的。
    ◦ 若确定没有代码⼀次性设置多个值那么将多个字段移⼊⼀个新oneof类型也是可⾏的。
    ◦ 将任何字段移⼊已存在的oneof类型是不安全的。

 如果删除一个字段后用新的字段占据了这个编号,在反序列化的时候就会解析出错误的信息。

若是新设置了字段,但却仍然使用就的方法序列化的话,新增的字段在旧程序中其实并没有丢失,⽽是会作为旧程序的未知字段。

未知字段:

在了解未知字段之前先需要知道一下protobuf之间不同类的关系。

MessageLite仅仅提供序列化、反序列化功能,跟message属于同一个层级,是message类的拓展。

Descriptor类是描述和管理message属性的类,是message类的下层。

Reflection主要提供了动态读写消息字段的接⼝,对消息对象的⾃动读写主要通过该类完成。提供⽅法来动态访问/修改message中的字段,对每种类型,Reflection都提供了⼀个单独的接⼝⽤于读写字段对应的值。类中还包含了访问/修改未知字段的⽅法。是message类的下层

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

UnknownField类

表⽰未知字段集中的⼀个字段,是UnknownFieldSet的下层。这里是未知字段的类型

enum Type {
TYPE_VARINT,
TYPE_FIXED32,
TYPE_FIXED64,
TYPE_LENGTH_DELIMITED,
TYPE_GROUP
};

当我们想拿到未知字段需要一层一层获取它的上层对象

//这里以people的字段为例
const Reflection* reflection = PeopleInfo::GetReflection();
const UnknownFieldSet& unknowSet = reflection->GetUnknownFields(people);for (int j = 0; j < unknowSet.field_count(); j++)
{const UnknownField& unknow_field = unknowSet.field(j);
}

五.选项option

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


常用选项列举
 

 optimize_for:

该选项为文件选项,可以设置protoc编译器的优化级别,分别为SPEED 、
CODE_SIZE 、 LITE_RUNTIME 。受该选项影响,设置不同的优化级别,编译.proto⽂件后⽣
成的代码内容不同

SPEED :protoc编译器将⽣成的代码是⾼度优化的,代码运⾏效率⾼,但是由此⽣成的代码编译后会占⽤更多的空间。 SPEED 是默认选项
CODE_SIZE :proto编译器将⽣成最少的类,会占⽤更少的空间,是依赖基于反射的代码来
实现序列化、反序列化和各种其他操作。但和 SPEED 恰恰相反,它的代码运⾏效率较低。这
种⽅式适合⽤在包含⼤量的.proto⽂件,但并不盲⽬追求速度的应⽤中。

LITE_RUNTIME :⽣成的代码执⾏效率⾼,同时⽣成代码编译后的所占⽤的空间也是⾮常
少。这是以牺牲ProtocolBuffer提供的反射功能为代价的,仅仅提供encoding+序列化功能,
所以我们在链接库时仅需链接libprotobuf-lite,⽽⾮libprotobuf。这种模式通常⽤于资源
有限的平台,例如移动⼿机平台中
 

 option optimize_for = SPEED;

allow_alias:

 允许将相同的常量值分配给不同的枚举常量,⽤来定义别名。该选项为枚举选项。

 enum TestType

{
        option allow_alias = true;
        test1 = 0;
        test2= 1;
        test3 = 1; // 若不加 option allow_alias = true; 这⼀⾏会编译报错
}

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

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

相关文章

1.25学习总结

今天学习了二叉树&#xff0c;了解了二叉树的创建和遍历的过程 今天所了解的遍历过程主要分为三种&#xff0c;前序中序和后序&#xff0c;都是DFS的想法 前序遍历&#xff1a;先输出在遍历左节点和右节点&#xff08;输出->左->右&#xff09; 中序遍历&#xff1a;先…

华为云磁盘性能指标(参考)

MD[华为云磁盘性能指标(参考)] 云硬盘&#xff08;Elastic Volume Service, EVS&#xff09; 根据性能&#xff0c;磁盘可分为极速型SSD V2、极速型SSD、通用型SSD V2、超高IO、通用型SSD、高IO、普通IO。 性能指标(参考)&#xff0c;测速说明&#xff1a;操作系统-windows …

如何配置Tomcat服务环境并实现无公网ip访问本地站点

文章目录 前言1.本地Tomcat网页搭建1.1 Tomcat安装1.2 配置环境变量1.3 环境配置1.4 Tomcat运行测试1.5 Cpolar安装和注册 2.本地网页发布2.1.Cpolar云端设置2.2 Cpolar本地设置 3.公网访问测试4.结语 前言 Tomcat作为一个轻量级的服务器&#xff0c;不仅名字很有趣&#xff0…

conda环境下使用youtube_dl模块下载youtube视频异常解决方法

1 问题描述 使用python的youtube_dl模块下载youtube视频时失败&#xff0c;错误信息如下&#xff1a; ERROR: Unable to extract uploader id; please report this issue on https://yt-dl.org/bug . Make sure you are using the latest version; see https://yt-dl.org/up…

jquery练习

jquery练习 1.多图片展示 <!doctype html> <html> <head> <meta charset"utf-8"> <title>多图片展示效果</title> <link href"zns_style.css" rel"stylesheet" type"text/css" /> <sc…

从 Apk 提取代码到单独 dex

从 Apk 提取代码到单独 dex Android 中动态加载是指应用程序在运行时加载和执行 Dex 文件的过程&#xff0c;可以在运行时加载不同的代码或功能&#xff0c;而无需重新编译整个应用程序&#xff0c;动态加载 Dex 文件通常涉及以下步骤&#xff1a; 创建 Dex 文件 我们接触到的…

Redis的五种常用数据类型详解及相关面试问题

目录 Redis的五种常用数据类型详解 简述 Redis五种基本数据类型 String字符串 常用命令 应用场景 Hash散列表 常用命令 使用场景 List链表 常用命令 应用场景 Set( 集合) 常用命令 应用场景 SortedSet( 有序集合) zset 常用命令介绍 应用场景 面试题常问的数…

退货通知单下推销售退货单,无法下推问题排查

文章目录 退货通知单下推销售退货单&#xff0c;无法下推问题排查报错界面排查原因 退货通知单下推销售退货单&#xff0c;无法下推问题排查 报错界面 排查 检验单已做。 原因 合格未勾选判退。

按键协管指南针加速计陀螺仪GPS等原理图纸2

1.imu电路。 加速计包含重力感应。 到传感器芯片u8, U16, U18的信息都是用的spi接口&#xff0c;如下图所示。OSCAR_TO_IMU_SPI_SCLK_FL, IMU_TO_OSCAR_SPI_MISO_FL, OSCAR_TO_IMU_SPI_MOSI_FL接了u8, u16, u18,通过片选信号cs选择哪个芯片接收。 加速计&#xff0c;陀螺仪&…

Redash 默认key漏洞(CVE-2021-41192)复现

Redash是以色列Redash公司的一套数据整合分析解决方案。该产品支持数据整合、数据可视化、查询编辑和数据共享等。 Redash 10.0.0及之前版本存在安全漏洞&#xff0c;攻击者可利用该漏洞来使用已知的默认值伪造会话。 1.漏洞级别 中危 2.漏洞搜索 fofa "redash"…

289. 生命游戏

根据 百度百科 &#xff0c; 生命游戏 &#xff0c;简称为 生命 &#xff0c;是英国数学家约翰何顿康威在 1970 年发明的细胞自动机。 给定一个包含 m n 个格子的面板&#xff0c;每一个格子都可以看成是一个细胞。每个细胞都具有一个初始状态&#xff1a; 1 即为 活细胞 &am…

主播考核体系相关基础

1.主播薪资类型 2.主播考核体系 1.分为日常考核、月度考核 日常考核分为三部曲&#xff1a;播前、播中、播后 &#xff08;1&#xff09;播前 &#xff08;2&#xff09;播中 &#xff08;3&#xff09;播后 月度考核 月度考核表列举 主播等级划分要素 主播晋升考核方…

JVM篇----第六篇

系列文章目录 文章目录 系列文章目录前言一、堆(Heap-线程共享) -运行时数据区二、方法区/永久代(线程共享)三、JVM 运行时内存四、新生代前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看…

Qt6入门教程 11:父子对象关系

在上一篇中的纯手写部分&#xff0c;不管是创建菜单、工具栏还是状态栏&#xff0c;我们new完之后都未显式的调用delete进行销毁&#xff0c;这样难道不会有内存泄漏么&#xff1f; QMenuBar *menuBar new QMenuBar(this); QToolBar *toolBar new QToolBar(this); QStatusBa…

web前端之不一样的居中方式、解决tabBar选项卡居中问题、css支持嵌套、auto

MENU 前言htmlstyle效果 前言 这里不能使用justify-content: center;&#xff0c;因为在小屏幕上&#xff0c;这种方式无法显示最前面的两个tabBar。 html <div id"box" class"d_f o_a mt_50 mb_50 ml_20 mr_20"><div class"ws_n">…

【MySQL】如何通过DDL去创建和修改员工信息表

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-fmKISDBsFq74ab2Z {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…

【vue】Vue2和Vue3中的代码逻辑复用对比(mixins、自定义hook):

文章目录 一、前言&#xff1a;二、mixins&#xff1a;【1】mixins是什么&#xff1f;【2】mixins如何使用&#xff1f;【3】mixins的一些特性&#xff1a;【4】mixins的缺点&#xff1a; 三、hook&#xff1a;【1】Vue3.x中的自定义hook函数是什么&#xff1f;【2】mixins和Co…

4.【SpringBoot3】文章管理接口开发

序言 在文章管理模块&#xff0c;有以下接口需要开发&#xff1a; 新增文章文章列表&#xff08;条件分页&#xff09;获取文章详情更新文章删除文章 数据库表字段和实体类属性&#xff1a; 1. 新增文章 需求分析 当用户点击左侧菜单中的“文章管理”后&#xff0c;页面主…

SpringBoot_基础

学习目标 基于SpringBoot框架的程序开发步骤 熟练使用SpringBoot配置信息修改服务器配置 基于SpringBoot的完成SSM整合项目开发 一、SpringBoot简介 1. 入门案例 问题导入 SpringMVC的HelloWord程序大家还记得吗&#xff1f; SpringBoot是由Pivotal团队提供的全新框架&…

java数据结构与算法刷题-----LeetCode697. 数组的度

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 方法一&#xff1a;hash表 此方法是工作中时间可以使用的&#xff0c;因为…