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

相关文章

Java实现字节字符转bcd编码

《Java实现字节字符转bcd编码》BCD是一种将十进制数字编码为二进制的表示方式,常用于数字显示和存储,本文将介绍如何在Java中实现字节字符转BCD码的过程,需要的小伙伴可以了解下... 目录前言BCD码是什么Java实现字节转bcd编码方法补充总结前言BCD码(Binary-Coded Decima

python获取指定名字的程序的文件路径的两种方法

《python获取指定名字的程序的文件路径的两种方法》本文主要介绍了python获取指定名字的程序的文件路径的两种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要... 最近在做项目,需要用到给定一个程序名字就可以自动获取到这个程序在Windows系统下的绝对路径,以下

SpringBoot全局域名替换的实现

《SpringBoot全局域名替换的实现》本文主要介绍了SpringBoot全局域名替换的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录 项目结构⚙️ 配置文件application.yml️ 配置类AppProperties.Ja

Java使用Javassist动态生成HelloWorld类

《Java使用Javassist动态生成HelloWorld类》Javassist是一个非常强大的字节码操作和定义库,它允许开发者在运行时创建新的类或者修改现有的类,本文将简单介绍如何使用Javass... 目录1. Javassist简介2. 环境准备3. 动态生成HelloWorld类3.1 创建CtC

JavaScript中的高级调试方法全攻略指南

《JavaScript中的高级调试方法全攻略指南》什么是高级JavaScript调试技巧,它比console.log有何优势,如何使用断点调试定位问题,通过本文,我们将深入解答这些问题,带您从理论到实... 目录观点与案例结合观点1观点2观点3观点4观点5高级调试技巧详解实战案例断点调试:定位变量错误性能分

使用Python批量将.ncm格式的音频文件转换为.mp3格式的实战详解

《使用Python批量将.ncm格式的音频文件转换为.mp3格式的实战详解》本文详细介绍了如何使用Python通过ncmdump工具批量将.ncm音频转换为.mp3的步骤,包括安装、配置ffmpeg环... 目录1. 前言2. 安装 ncmdump3. 实现 .ncm 转 .mp34. 执行过程5. 执行结

Python实现批量CSV转Excel的高性能处理方案

《Python实现批量CSV转Excel的高性能处理方案》在日常办公中,我们经常需要将CSV格式的数据转换为Excel文件,本文将介绍一个基于Python的高性能解决方案,感兴趣的小伙伴可以跟随小编一... 目录一、场景需求二、技术方案三、核心代码四、批量处理方案五、性能优化六、使用示例完整代码七、小结一、

Python中 try / except / else / finally 异常处理方法详解

《Python中try/except/else/finally异常处理方法详解》:本文主要介绍Python中try/except/else/finally异常处理方法的相关资料,涵... 目录1. 基本结构2. 各部分的作用tryexceptelsefinally3. 执行流程总结4. 常见用法(1)多个e

Java实现将HTML文件与字符串转换为图片

《Java实现将HTML文件与字符串转换为图片》在Java开发中,我们经常会遇到将HTML内容转换为图片的需求,本文小编就来和大家详细讲讲如何使用FreeSpire.DocforJava库来实现这一功... 目录前言核心实现:html 转图片完整代码场景 1:转换本地 HTML 文件为图片场景 2:转换 H

Java使用jar命令配置服务器端口的完整指南

《Java使用jar命令配置服务器端口的完整指南》本文将详细介绍如何使用java-jar命令启动应用,并重点讲解如何配置服务器端口,同时提供一个实用的Web工具来简化这一过程,希望对大家有所帮助... 目录1. Java Jar文件简介1.1 什么是Jar文件1.2 创建可执行Jar文件2. 使用java