C++中protobuffer的具体使用方法以及重要原理的实现

2024-09-04 22:20

本文主要是介绍C++中protobuffer的具体使用方法以及重要原理的实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、protobuffer的具体使用

对于基本的知识可以看我之前的文章。 那一片文章主要是知识点,这一片是实战。

1、头部

我们通过syntax 这个来指定版本号,如果不写的话就会默认为proto2,2这个版本是一个比较旧的版本。旧的版本写起来就比较繁琐。

syntax = "proto3";    // 默认是proto2
package tutorial;     // package类似C++命名空间   比如:  std::cout  这样// 可以引用本地的,也可以引用include里面的,已经写好的proto文件是可以引用
import "google/protobuf/timestamp.proto";    //这里的是用来调用Timestamp 类型适合高精度时间戳的场合

2、优化

        2.1、speed:这是默认的优化级别,他生成代码注重于解码和编码的速度,但是可能专用较多的内存和生成较大的二进制文件。

        2.2、code_size:这个和speed正好相反,这个级别生成的代码较小,适合于对于代码大小有严格要求的场景,但是,他可能会牺牲一些性能。

        2.3、lite_runtime:这个级别旨在生成尽可能小的运行库,同时保持合理的解码和编码性能。它特别适用于移动设备和资源受限的环境,因为减少代码大小可以减少应用的内存占用和加载时间。

option optimize_for = LITE_RUNTIME;         //编译优化选项
option optimize_for = SPEED;
option optimize_for = CODE_SIZE;

3、生成位置

  其中路径1为.proto所在的路径,路径2为.cc和.h生成的位置。将指定proto文件生成.pb.cc和.pb.h。

protoc -I=/路径1 --cpp_out=./路径2 /路径1/addressbook.protoprotoc -I=./ --cpp_out=./ addressbook.proto    //生成到当前目录中//将对应目录的所有proto文件生成.pb.cc和.pb.h
protoc -I=./ --cpp_out=./ *.proto

4、定制选项

当需要用到以下语言的时候,就可以定制特定语言的可选选项。

//下面是针对不同的语言进行的优化选项,或者是定制的选项
// [START java_declaration]
option java_package = "com.example.tutorial";
option java_outer_classname = "AddressBookProtos";
// [END java_declaration]// [START csharp_declaration]
option csharp_namespace = "Google.Protobuf.Examples.AddressBook";
// [END csharp_declaration]

 5、主体部分

我们通过创造通讯录的方法来进行实战。其中protobuffer的写法类似于C++中类的写法。我们来看看这里面都写了什么吧:

        首先就是创建类,可以写上类的名字,接下来就是保留id号,可以保留以后想要使用的id号,这样就可以避免不小心使用掉这些数字。下面就是指定名称了,他们每个名称都要通过这些编号进行搜索。

        当使用的是旧版本,我们需要在每一个名称前面进行加入关键字:singular,repeated,option。但是新版本就解决了这个小问题,可以不用全部加了,减轻负担,默认都是singular,但是特殊的需要加。

        接下来就是enum的枚举类型了,比如我们想让用户写入的是我们指定的数据,那么就可以通过枚举来实现。

        然后就是嵌套类型了,可以多种不同的类型进行嵌套,后面就是oneof的类型,主要是解决多个字段的中的某一个,当选择了其中一个之后别的别的就会自动进行删除操作,这样可以节省内存。


// 通讯录
// [START messages]  
message Person {    // message类似C++的classreserved 8,15,9 to 11;        //这里的reserved是保留字reserved "foo","bar";string name   = 1;  // 名字int32 id      = 2;  // Unique ID number for this person. 每个人的唯一idstring email  = 3;enum PhoneType {    // enum 枚举类型MOBILE = 0;HOME = 1;WORK = 2;}message PhoneNumber {string number = 1;    // 字符串 电话号码PhoneType type = 2;   //}repeated PhoneNumber phones = 4;    // 重复0~多个,一个人有多个电话   在前面加入repeated 后,这个就可以支持多个数据。google.protobuf.Timestamp last_updated = 5; // import "google/protobuf/timestamp.proto"   这里是一个时间戳类型的值。
}// Our address book file is just one of these.
message AddressBook {repeated Person people = 1;   // 电话簿有多人的电话message Samplemessage{oneof test_oneof{string name = 2;submessage sub_message = 3;}
}
// [END messages]

二、重要原理-编码原理

        首先我们要理解变长编码(Varints:base128)和固定编码。举个例子:int32_t :0x12345678占用四字节,int32_t:0x12 占用四字节,这里都是占用的四字节,这里采用的就是定长编码,当使用变长编码,0x12345678占用四字节,而0x12 占用一字节。

        原理:base128 使用每个字节的最高有效位作为标志位, 而剩余的 7 位以二进制补码的形式来存储数字值本身, 当最高有效位为 1 时, 代表其后还跟有字节, 当最高有效位为 0 时, 代表已经是该数字的最后的一个字节;

        我们假设数字1的int类型占四个字节。那么他的标准整型存储的二进制应该是这样的:

        可以看到只有最后一个字节存储了有效数值,前三个字节都是0。

        为什么设计变⻓编码:普通的 int 数据类型, 无论其值的大小, 所占用的存储空间都是相等的,比如不管是0x12345678 还是0x12都占用4字节,那能否让0x12在表示的时候只占用1个字节呢?

        是否可以根据数值的大小来动态地占用存储空间, 使得值比较小的数字占用较少的字节数, 值相对比较大的数字占用较多的字节数, 这即是变⻓整型编码的基本思想。

        采用变⻓整型编码的数字, 其占用的字节数不是完全一致的, Varints 编码使用每个字节的最高有效位作为标志位, 而剩余的 7 位以二进制补码的形式来存储数字值本身, 当最高有效位为 1 时, 代表其后还跟有字节, 当最高有效位为 0 时, 代表已经是该数字的最后的一个字节。

如果采用Varints编码,那么二进制就是:

 我们可以再举个例子:666 。从下面的编码解码过程可以看出, 可变⻓整型编码对于不同大小的数字, 其所占用的存储空间是不同的。

三、protobuffer的总结

1:Protobuf 采用 Varints 编码和 Zigzag 编码来编码数据, 其中 Varints 编码的思想是移除数字高位的 0, 用变⻓的二进制位来描述一个数字, 对于小数字, 其编码⻓度短, 可提高数据传输效率, 但由于它在每个字节的最高位额外采用了一个标志位来标记其后是否还跟有有效字节, 因此对于大的正数, 它会比使用普通的定⻓格式占用更多的空间, 另外对于负数, 直接采用 Varints编码将恒定占用 10 个字节, Zigzag 编码可将负数映射为无符号的正数, 然后采用 Varints 编码进行数据压缩, 在各种语言的 Protobuf 实现中, 对于 int32 类型的数据, Protobuf 都会转为 uint64 而后使用 Varints 编码来处理, 因此当字段可能为负数时,我们应使用 sint32 或 sint64, 这样 Protobuf 会按照 Zigzag 编码将数据变换后再采用 Varints 编码进行压缩, 从而缩短数据的二进制位数。

2:Protobuf 不是完全自描述的信息描述格式, 接收端需要有相应的解码器(即 proto 定义)才可解析数据格式, 序列化后的 Protobuf 数据不携带字段名, 只使用字段编号来标识一个字段, 因此更改 proto 的字段名不会影响数据解析(但这显然不是一种好的行为), 字段编号会被编码进二进制的消息结构中, 因此我们应尽可能地使用小字段编号。

3:Protobuf 是一种紧密的消息结构, 编码后字段之间没有间隔, 每个字段头由两部分组成: 字段编号和 wire type, 字段头可确定数据段的⻓度, 因此其字段之前无需加入间隔, 也无需引入特定的数据来标记字段末尾, 因此 Protobuf 的编码⻓度短, 传输效率高。

四、协议升级

当我们要将之前的protobuffer进行升级的时候,我们就会体会到他的优势了,我们需要满足一些规则:

1:不要修改之前字段的数据结构。

2:如果您添加新字段,则任何由代码使用“旧”消息格式序列化的消息仍然可以通过新生成的代码进行分析。您应该记住这些元素的默认值,以便新代码可以正确地与旧代码生成的消息进行交互。同样,由新代码创建的消息可以由旧代码解析:旧的二进制文件在解析时会简单地忽略新字段。

3:int32,uint32,int64,uint64 和 bool 全都兼容。这意味着您可以将字段从这些类型之一更改为另一个字段而不破坏向前或向后兼容性。如果一个数字从不适合相应类型的线路中解析出来,则会得到与在 C++ 中将该数字转换为该类型相同的效果(例如,如果将 64 位数字读为 int32,它将被截断为 32 位)。

4:enum 就数组而言,是可以与 int32,uint32,int64 和 uint64 兼容(请注意,如果它们不适合,值将被截断)。但是请注意,当消息反序列化时,客户端代码可能会以不同的方式对待它们:例如,未识别的 proto3 枚举类型将保留在消息中,但消息反序列化时如何表示是与语言相关的。(这点和语言相关,上面提到过了)Int 域始终只保留它们的值。

5:将单个值更改为新的成员是安全和二进制兼容的。如果您确定一次没有代码设置多个字段,则将多个字段移至新的字段可能是安全的。将任何字段移到现有字段中都是不安全的。(注意字段和值的区别,字段是 field,值是 value)。

这篇关于C++中protobuffer的具体使用方法以及重要原理的实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1137192

相关文章

Python并行处理实战之如何使用ProcessPoolExecutor加速计算

《Python并行处理实战之如何使用ProcessPoolExecutor加速计算》Python提供了多种并行处理的方式,其中concurrent.futures模块的ProcessPoolExecu... 目录简介完整代码示例代码解释1. 导入必要的模块2. 定义处理函数3. 主函数4. 生成数字列表5.

Python中help()和dir()函数的使用

《Python中help()和dir()函数的使用》我们经常需要查看某个对象(如模块、类、函数等)的属性和方法,Python提供了两个内置函数help()和dir(),它们可以帮助我们快速了解代... 目录1. 引言2. help() 函数2.1 作用2.2 使用方法2.3 示例(1) 查看内置函数的帮助(

Linux脚本(shell)的使用方式

《Linux脚本(shell)的使用方式》:本文主要介绍Linux脚本(shell)的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录概述语法详解数学运算表达式Shell变量变量分类环境变量Shell内部变量自定义变量:定义、赋值自定义变量:引用、修改、删

Python实现精准提取 PDF中的文本,表格与图片

《Python实现精准提取PDF中的文本,表格与图片》在实际的系统开发中,处理PDF文件不仅限于读取整页文本,还有提取文档中的表格数据,图片或特定区域的内容,下面我们来看看如何使用Python实... 目录安装 python 库提取 PDF 文本内容:获取整页文本与指定区域内容获取页面上的所有文本内容获取

基于Python实现一个Windows Tree命令工具

《基于Python实现一个WindowsTree命令工具》今天想要在Windows平台的CMD命令终端窗口中使用像Linux下的tree命令,打印一下目录结构层级树,然而还真有tree命令,但是发现... 目录引言实现代码使用说明可用选项示例用法功能特点添加到环境变量方法一:创建批处理文件并添加到PATH1

Java使用HttpClient实现图片下载与本地保存功能

《Java使用HttpClient实现图片下载与本地保存功能》在当今数字化时代,网络资源的获取与处理已成为软件开发中的常见需求,其中,图片作为网络上最常见的资源之一,其下载与保存功能在许多应用场景中都... 目录引言一、Apache HttpClient简介二、技术栈与环境准备三、实现图片下载与保存功能1.

Python中使用uv创建环境及原理举例详解

《Python中使用uv创建环境及原理举例详解》uv是Astral团队开发的高性能Python工具,整合包管理、虚拟环境、Python版本控制等功能,:本文主要介绍Python中使用uv创建环境及... 目录一、uv工具简介核心特点:二、安装uv1. 通过pip安装2. 通过脚本安装验证安装:配置镜像源(可

C++ 函数 strftime 和时间格式示例详解

《C++函数strftime和时间格式示例详解》strftime是C/C++标准库中用于格式化日期和时间的函数,定义在ctime头文件中,它将tm结构体中的时间信息转换为指定格式的字符串,是处理... 目录C++ 函数 strftipythonme 详解一、函数原型二、功能描述三、格式字符串说明四、返回值五

LiteFlow轻量级工作流引擎使用示例详解

《LiteFlow轻量级工作流引擎使用示例详解》:本文主要介绍LiteFlow是一个灵活、简洁且轻量的工作流引擎,适合用于中小型项目和微服务架构中的流程编排,本文给大家介绍LiteFlow轻量级工... 目录1. LiteFlow 主要特点2. 工作流定义方式3. LiteFlow 流程示例4. LiteF

使用Python开发一个现代化屏幕取色器

《使用Python开发一个现代化屏幕取色器》在UI设计、网页开发等场景中,颜色拾取是高频需求,:本文主要介绍如何使用Python开发一个现代化屏幕取色器,有需要的小伙伴可以参考一下... 目录一、项目概述二、核心功能解析2.1 实时颜色追踪2.2 智能颜色显示三、效果展示四、实现步骤详解4.1 环境配置4.