Protobuf 使用和原理

news2025/7/14 10:24:20

文章目录

  • 1. protobuf 简介
    • 1.1. 发展背景
    • 1.2. 优缺点
      • 1.2.1. 优点
      • 1.2.2. 缺点
  • 2. 使用
    • 2.1. 消息类型
      • 2.1.1. 字段限制
      • 2.1.2. 数据类型
      • 2.1.3. 分配字段编号
      • 2.1.4. 保留字段
      • 2.1.5. 默认字段规则
      • 2.1.6. 枚举
    • 2.2. Protobuf 工作流程
      • 2.2.1 编译proto文件
    • 2.3. 使用建议
  • 3. 原理
    • 3.1. 编码格式
      • 3.1.1. 字段唯一标识——tag
      • 3.1.2. 补充 packed 编码
    • 3.2. 编码算法
      • 3.2.1. 补码概念回顾
      • 3.2.2. Varints
        • 3.2.2.1. Varints 编码
        • 3.2.2.2. Varints 解码
      • 3.2.3. ZigZag
        • 3.2.3.1. ZigZag 编码
        • 3.2.3.2. ZigZag 解码
        • 3.2.3.3. C 实现
    • 3.3. 总结

1. protobuf 简介

命名:Protocol Buffers— 协议缓冲区

1.1. 发展背景

Protobuf 的诞生之初是为了解决服务端新旧协议(高低版本)兼容性问题,同时被寄予2 个特点:

  1. 可以很容易地引入新字段
  2. 数据格式可以用各种语言来处理(Java,C++ 等各种语言)

发展历程:

  1. 2001年在谷歌诞生
  2. 2008年2.0版本对外开源
  3. 2016年3.0版本发布
  4. 2022年更新至3.20

Protobuf 对外开源是从Protobuf2 开始

1.2. 优缺点

1.2.1. 优点

性能:

  • 体积小,序列化后,数据大小可缩小3-10倍
  • 序列化速度快,比XML和JSON快20-100倍
  • 传输速度快,因为体积小,传输起来带宽和速度会有优化

使用:

  • 使用简单,proto编译器自动进行序列化和反序列化
  • 维护成本低,多平台仅需维护一套对象协议文件(.proto)
  • 向后兼容性(扩展性)好,不必破坏旧数据格式就可以直接对数据结构进行更新
  • 加密性好,Http传输内容抓包只能看到字节

使用范围:跨平台、跨语言(支持Java, Python, Objective-C, C+, Dart, Go, Ruby, and C#等),可扩展性好

1.2.2. 缺点

  • 功能,不适合用于对基于文本的标记文档(如HTML)建模,因为文本不适合描述数据结构
  • 通用性较差:json、xml已成为多种行业标准的编写工具,而Protobuf只是Google公司内部的工具
  • 自解耦性差:以二进制数据流方式存储(不可读),需要通过.proto文件才能了解到数据结构

2. 使用

2.1. 消息类型

在 proto 中,所有结构化的数据都被称为 message。

syntax = "proto3";
package hello;

message helloworld 
{ 
   required int32     id = 1;
   required string    name = 2;
   optional int32     age = 3;
}

如果开头第一行不声明 syntax = “proto3”;,则默认使用 proto2 进行解析。
声明package,来防止命名冲突。 Packages是可选的。

2.1.1. 字段限制

  • required:消息体中必填字段,不设置会导致编解码异常;
  • optional:消息体中可选字段;
  • repeated:可重复字段(变长字段);

由于一些历史原因,repeated字段并没有想象中那么高效,新版本中允许使用特殊的选项来获得更高效的编码:

repeated int32 samples = 4 [packed=true];

2.1.2. 数据类型

完整数据类型映射—>《支持的全部数据类型》

.proto 类型C++类型Go 类型Java 类型
doubledoublefloat64double
int32int32int32int
int64int64int64long
sint32int32int32int
sint64int64int64long
boolboolboolboolean

2.1.3. 分配字段编号

每个消息定义中的每个字段都有唯一的编号
注意:

  • 范围 1 到 15 中的字段编号需要一个字节进行编码;
  • 范围 16 至 2047 中的字段编号需要两个字节;
  • 最小字段编号为1,最大字段编号为229-1 或 536,870,911;
  • 不能使用保留编号 19000 到 19999;

2.1.4. 保留字段

通过 reserved 确保删除的字段不会重复使用。

message Foo {
  reserved 2, 15, 9 to 11;
  reserved "foo", "bar";
}

注意,不能在同一个 reserved 语句中混合字段名称和字段编号。如有需要需要像上面这个例子这样写。

通过完全删除某个字段或将其注释掉来更新消息类型,那么未来的用户可以在对该类型进行自己的更新时重新使用该字段号。如果稍后加载到了的旧版本 .proto 文件,则会导致服务器出现严重问题,例如数据混乱,隐私错误等等。

2.1.5. 默认字段规则

  • 字段名不能重复,必须唯一。
  • repeated 字段:可以在一个 message 中重复任何数字多次(包括 0 ),不过这些重复值的顺序被保留。

在 proto3 中,纯数字类型的 repeated 字段编码时候默认采用 packed 编码。

2.1.6. 枚举

在 message 中可以嵌入枚举类型。

message MyMessage1 {
  enum EnumAllowingAlias {
    option allow_alias = true;
    UNKNOWN = 0;
    STARTED = 1;
    RUNNING = 1;
  }
}

枚举类型需要注意的是,一定要有 0 值。

  • 枚举为 0 的是作为零值,当不赋值的时候,就会是零值。
  • 为了和 proto2 兼容。在 proto2 中,零值必须是第一个值。

通过设置 allow_alias 为 true,允许将不同的枚举常量指定为相同的值。

2.2. Protobuf 工作流程

Protobuf 使用有2 个前置:

  • .proto 文件
  • protoc 编译器

《编译工具下载》

工作流程:

graph LR
.proto文件-->protoc编译器
protoc编译器-->C++/Python/Jave等平台目标文件
C++/Python/Jave等平台目标文件-->文件导入项目
文件导入项目-->引入Google提供的相应库
引入Google提供的相应库-->开始序列化/反序列化

2.2.1 编译proto文件

执行protoc命令对.proto文件进行编译。Linux系统通过 help protoc 查看protoc命令的使用详解。

protoc --proto_path=$SRC_DIR --cpp_out=$DST_DIR  xxx.proto 
  • –proto_path= S R C D I R 表示从 SRC_DIR 表示从 SRCDIR表示从SRC_DIR目录下读取proto文件。
  • –cpp_out=$DST_DIR 表示生成的C++代码保存路径
  • xxx.proto:要针对哪个proto文件生成接口,例如 hello.proto

–proto_path 有一个别名 -I 。

2.3. 使用建议

  1. 字段标识号,尽量控制在1-15;
  2. 若使用字段出现负数,考虑 sint32/sint64 类型;
  3. 若使用字段出现比较大的正数,考虑使用 fixed32/fixed64 类型;
  4. 对于 repeated 字段,尽量增加 packed=true 修饰;

3. 原理

《google官方原理介绍》

3.1. 编码格式

protobuf采用TLV(tag-length-value)编码格式。

  • tag:字段的唯一标识;
  • length:表示value数据的长度,length不是必须的,固定长度的value,没有length;
  • value:数据本身的内容
    在这里插入图片描述

3.1.1. 字段唯一标识——tag

tag值: 由field_number和wire_type两部分组成。
在这里插入图片描述

  • field_number: message 定义字段时指定的字段编号;
  • wire_type: 根据这个类型选择不同的 Value 编码方案;
wrie_type编码方案编码长度存储方式对应的数据类型
0Varint
(负数为ZigZag)
变长(1-10个字节)T-Vint32,int64,uint32,uint64,bool
enum,int32,int64(负数使用)
164-bit固定8个字节T-Vfixed64,sfixed64,double
2Lenght-deliml变长T-L-Vstring,bytes,repeated
532-bit固定4个字节T-Vfixed32,sfixed32,float

字段标识号(Field_Number),尽量控制在1-15。超过了则需要2个字节或更多。

3.1.2. 补充 packed 编码

在 proto2 中为我们提供了可选的设置 [packed = true],而这一可选项在 proto3 中已成默认设置。

  • [packed = false] 时的结构:Tag-Length-Value-Tag-Length-Value-Tag-Length-Value…
  • [packed = true] 时的结构:Tag-Length-Value-Value-Value…

3.2. 编码算法

Protobuf 中编码有两种:Varints 和 ZigZag。

ZigZag用于解决varint对负数编码效率低的问题。负数推荐使用 sint32 或 sint64。

3.2.1. 补码概念回顾

  • 原码:最高位为符号位,剩余位表示绝对值;
  • 反码:除符号位外,对原码剩余位依次取反;
  • 补码:对于正数,补码为其自身;对于负数,除符号位外对原码剩余位依次取反然后+1。

3.2.2. Varints

Varint 是一种使用一个或多个字节序列化整数的方法,也可以说是一种压缩算法,值越小的数字使用越少的字节数。压缩的依据是:越小的数字,越经常使用。

3.2.2.1. Varints 编码

Varints 的编码规则如下【注:大端字节序下】:

  1. 将数值转换为二进制,从最低位开始,自右至左每 7 位作为一组进行分割
  2. 翻转组。
  3. 在每一组最前面插入一位最高有效位(msb),凑成一个字节(8 位)。最后一组插入 0,表示后面没有字节出现;其他组插入 1 ,表示后面还有字节出现。
  4. 此时每一组都有 8 位,即一组就是一个字节,将结果转换为十六进制输出。

以 150 为例,首先转换为二进制:

1001 0110

7 位一组进行分割:

000 0001, 001 0110

翻转组:

001 0110, 000 0001

每一组最前面插入 msb,除最后一组插入 0 外,其余组插入 1:

1001 0110, 0000 0001

转换为十六进制表示:

96, 01

3.2.2.2. Varints 解码

Varints 的解码就是对编码的逆操作,以 150 的编码结果进行解码为例:

  1. 将编码后数据(十六进制)转换为二进制
  2. 去除每个字节最高位的 msb
  3. 翻转,然后转换为 10 进制输出

以 96, 01 为例,首先转换为二进制:

1001 0110, 0000 0001

去除每个字节最高位的 msb:

001 0110, 000 0001

翻转:

000 0001, 001 0110

转换回十进制:

128 + 16 +4 + 2 = 150

3.2.3. ZigZag

3.2.3.1. ZigZag 编码

Zigzag 编码规则

  • 有符号整数映射成无符号整数,再使用 varint 编码
    Zigzag 映射函数

Zigzag 映射函数

  • h(n) = (n << 1) ^ (n >> 31), n为sint32时
  • h(n) = (n << 1) ^ (n >> 63), n为sint64时

整数的补码(十六进制)与hash函数的对应关系如下:

nhexh(n)ZigZag (hex)
000 00 00 0000 00 00 0000
-1ff ff ff ff00 00 00 0101
100 00 00 0100 00 00 0202
-2ff ff ff fe00 00 00 0303
200 00 00 0200 00 00 0404
-64ff ff ff c000 00 00 7f7f
6400 00 00 4000 00 00 8080 01

下面以int32类型的数-2为例,分析它的编码过程。如下图所示:
在这里插入图片描述

3.2.3.2. ZigZag 解码

解码:

  • h(n) = (n >>> 1) ^ -(n & 1)

3.2.3.3. C 实现

#include <iostream>
using namespace std;

// zigzag 编码
unsigned int zigzag_encode_32(int val)
{
    return (unsigned int)((val<<1)^(val>>31));
}

// zigzag解码
int zigzag_decode_32(unsigned int val)
{
    return (int)((val>>1) ^ -(val&1));
}
 
int main()
{
    int n;
    while(1)
    {
        cout <<"\n请输入原码:";
        cin >> n;
        unsigned int zn = zigzag_encode_32(n);
        int uzn = zigzag_decode_32(zn);
        cout << "ZigZag编码:" << zn << ", 解码:" << uzn << endl;
    }
    
    return 0;
}

执行:

请输入原码:0
ZigZag编码:0, 解码:0

请输入原码:-1
ZigZag编码:1, 解码:-1

请输入原码:1
ZigZag编码:2, 解码:1

请输入原码:-2
ZigZag编码:3, 解码:-2

请输入原码:2
ZigZag编码:4, 解码:2

请输入原码:-3
ZigZag编码:5, 解码:-3

请输入原码:3
ZigZag编码:6, 解码:3

请输入原码:-64
ZigZag编码:127, 解码:-64

请输入原码:64
ZigZag编码:128, 解码:64

请输入原码:-65
ZigZag编码:129, 解码:-65

请输入原码:65
ZigZag编码:130, 解码:65

3.3. 总结

  • Varint编码:有效降低了数据量,但对大的正数和负数并不友好;
  • Zigzag编码:解决了负数编码过长问题;
  • Protobuf围绕着T-L-V 存储方式,不同的数据类型采用不同的编码方式。

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

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

相关文章

抓包工具fiddler详细使用教程

各位做测试的同学想必对抓包工具fiddler并不陌生&#xff0c;但是很多同学可能没有总结过它的用法&#xff0c;下面我总结了fiddler一些常用的用法。 Web端抓包配置 打开Fiddler&#xff0c;Tools -> Fiddler Options -> HTTPS 配置完后记得要重启Fiddler 选中Decrpt …

第2章 线程安全与共享资源竞争

第2章 线程安全与共享资源竞争 2.1 synchronized同步介绍 synchronized要解决的是共享资源冲突的问题。当共享资源被任务使用时&#xff0c;要对资源提前加锁。所有任务都采用抢占模式&#xff0c;即某个任务会抢先对共享资源加上第一把锁。如果这是一个排他锁&#xff0c;…

汇编指令学习(LOOP)

一、xor异或操作&#xff0c;相同为0&#xff0c;不同为1xor eax,eaxeax异或eax&#xff0c;相同为0&#xff0c;并把结果存放到eax&#xff0c;简单说该语句就是想eax寄存器清零。二、ECX&#xff0c;计数器mov ecx,0x3将ecx寄存器设置为3三、DEC减一操作dec ecxecx寄存器的值…

扬帆优配|昔日白马股濒临退市,却6天5涨停!ST股突然集体爆发

尽管再度重申“公司股票将被停止上市”&#xff0c;但3月8日早间&#xff0c;*ST辅仁股价仍是在开盘后快速封住涨停板。这已是该公司近6个买卖日来&#xff0c;第5次呈现涨停。 无独有偶&#xff0c;8日早间ST东瀛也在此前多次涨停后&#xff0c;再度呈现近4%的涨幅。而就在7日…

图像的读取与保存

图像是由一个个像素点组成&#xff0c;像素点就是颜色点&#xff0c;而颜色最简单的方式就是用RGB或RGBA表示图像保存图像将像素信息按照 一定格式&#xff0c;一定顺序&#xff08;即编码&#xff09; 存在硬盘上的 二进制文件 中保存图像需要以下必要信息&#xff1a;1. 文件…

ChatGPT的N种用法(持续更新中。。。)

目录前言一、语法更正二、文本翻译三、语言转换3-1、Python-->JAVA四、代码解释-1五、代码解释-2六、修复代码错误六、作为百科全书七、信息提取七、好友聊天八、创意生成器8-1、VR和密室结合8-2、再结合AR九、采访问题9-1、采访问题清单9-2、采访问题清单并给出相应答案十、…

优思学院|六西格玛管理的核心理念是什么?

六西格玛管理是一种基于数据分析的质量管理方法&#xff0c;旨在通过降低过程的变异性来达到质量稳定和优化的目的。该方法以希腊字母“σ”为名&#xff0c;代表标准差&#xff0c;是衡量过程变异性的重要指标。 六西格玛管理的核心理念是“以客户为中心、以数据为基础、追求…

【JAVA程序设计】【C00114】基于SSM+微信小程序的食堂订餐点餐管理系统——有文档

基于微信小程序的食堂订餐点餐管理系统项目简介项目获取开发环境项目技术运行截图项目简介 基于ssm框架管理前端使用vue&#xff0c;用户使用微信小程序的食堂订餐点餐小程序共分为三个角色&#xff1a;系统管理员、商家、用户 管理员角色包含以下功能&#xff1a; 首页展示、…

提升数字品牌的5个技巧

“品牌”或“品牌推广”的概念通常用于营销。因为建立您的企业品牌对于产品来说极其重要&#xff0c;品牌代表了您与客户互动的身份和声音。今天&#xff0c;让我们来看看在数字领域提升品牌的一些有用的技巧。如何在数字领域提升您的品牌&#xff1f;在了解这些技巧之前&#…

Android特别的数据结构(二)ArrayMap源码解析

1. 数据结构 public final class ArrayMap<K,V> implements Map<K,V> 由两个数组组成&#xff0c;一个int[] mHashes用来存放Key的hash值&#xff0c;一个Object[] mArrays用来连续存放成对的Key和ValuemHashes数组按非严格升序排列初始默认容量为0减容&#xff…

Hbase 映射到Hive

目录 一、环境配置修改 关闭掉hbase&#xff0c;zookeeper和hive服务 进入hive312/conf 修改hive-site.xml配置&#xff0c; 在代码最后添加配置 将hbase235的jar包全部拷贝到hive312的lib目录&#xff0c;并且所有的是否覆盖信息全部输入n&#xff0c;不覆盖 查看hive312下…

详解抓包原理以及抓包工具whistle的用法

什么是抓包? 分析网络问题业务分析分析网络信息流通量网络大数据金融风险控制探测企图入侵网络的攻击探测由内部和外部的用户滥用网络资源探测网络入侵后的影响监测链接互联网宽频流量监测网络使用流量(包括内部用户&#xff0c;外部用户和系统)监测互联网和用户电脑的安全状…

CI流水线的理解

一、概念 单元测试&#xff1a;针对软件的基本单元&#xff08;如&#xff1a;类、函数&#xff09;所做的测试。 集成测试&#xff1a;将软件代码单元集成起来后&#xff0c;以组件、模块和子系统为单位进行的测试&#xff0c;主要测试接口间的交互关系。也称组件测试&#xf…

嵌入式Linux从入门到精通之第十五节:嵌入式系统简介

嵌入式系统的层级结构如下图所示: 开发环境 这里以三星s5pv210为开发环境: 系统资源 s5pv210是三星公司推出的32位RISC微处理器,其CPU采用的是ARM Cortex-A8内核,基于ARMv7架构 丰富的片内资源,为手持设备和其它移动领域应用,提供了低价格、低功耗、高性能的微处理器解…

nodejs学习巩固笔记-nodejs基础,Node.js 高级编程(核心模块、模块加载机制)

目录Nodejs 基础大前端开发过程中的必备技能nodejs 的架构为什么是 NodejsNodejs 异步 IONodejs 事件驱动架构全局对象全局变量之 process核心模块核心模块 - path全局变量之 Buffer创建 bufferBuffer 实例方法Buffer 静态方法Buffer-split 实现核心模块之FS模块文件操作 APImd…

Chromium HTML Video 媒体播放代码梳理

经过一番探索&#xff08;参见Android 10 WebView 踩坑实录&#xff09;&#xff0c;终于搞定 Chromium WebView 的代码下载和编译问题&#xff0c;加下来就要向 H265 8K 高清播放发起冲锋。不过在打开 Chromium 源码后&#xff0c;眼前一黑。这还是熟悉的 Chromium 代码吗&…

KEIL5中头文件路劲包含问题

方式1&#xff1a;1.Keil中添加头文件相对路劲的方法在c/c配置中添加路劲&#xff0c;最终是将添加的绝对路径转化为相对路径&#xff1b;注意&#xff1a;相对路径的当前位置指.uvproj文件所在位置在C/C配置中的include paths”中添加工程所用的所有头文件的路径&#xff1b;2…

45岁VP:1000页PPT 10节实战课程,江湖再见!

与时舒卷&#xff0c;与光同尘 知识星球3年已满&#xff0c;受兄弟的盛请于是有了这2年10节课程 10节课程1000页自写的PPT20年的经验&#xff0c;N个企业不同行业的切身实际案例&#xff0c;王者课程&#xff0c;分享后江湖再见&#xff0c;华丽转身。 第1课 百万年薪CIO成长&…

线程、进程、协程的总结详细

线程、进程、协程的总结详细1 、进程1.1 进程是什么呢&#xff1f;1.2 生命周期1.3 进程同步机制1.4 进程通信机制1.4.1 管道1.4.2 消息队列1.4.3 共享内存1.4.4 信号量和PV操作1.4.5 信号1.4.6 socket1.4.7 总结 Linux 内核提供的进程通信机制2、线程2.1 线程是什么呢&#xf…

王道计算机组成原理课代表 - 考研计算机 第四章 指令系统 究极精华总结笔记

本篇博客是考研期间学习王道课程 传送门 的笔记&#xff0c;以及一整年里对 计算机组成 知识点的理解的总结。希望对新一届的计算机考研人提供帮助&#xff01;&#xff01;&#xff01; 关于对 “指令系统” 章节知识点总结的十分全面&#xff0c;涵括了《计算机组成原理》课程…