ProtoBuf 详解

news2024/11/25 5:56:24

1、初识ProtoBuf

序列化的概念:

举个例子:

我们在打电话的过程中,两个人可以通过电话听到对方的声音,这个过程就涉及到了序列化和反序列化。

相同的例子还有:网络通信、数据持久化等

常见的实现方式:JSON、ProtoBuf、XML

什么是ProtoBuf

将结构化的数据进行序列化的一种方式

ProtoBuf 特点

语言无关、平台无关:即ProtoBuf支持Java、C++、Python等多种语言,支持多平台。

高效:即比XML更小、更快、更简单。

扩展性、兼容性好:你可以更新数据结构,而不影响和破坏原有的旧程序。

2、ProtoBuf Ubuntu下安装

下载 ProtoBuf 前⼀定要安装依赖库:autoconf automake libtool curl make g++ unzip 如未安装,安装命令如下:
Ubuntu 用户选择:
 

sudo apt-get install autoconf automake libtool curl make g++ unzip -y

CentOS 用户选择:
 

sudo yum install autoconf automake libtool curl make gcc-c++ unzip

ProtoBuf 下载地址:https://github.com/protocolbuffers/protobuf/releases
在这⾥我们希望⽀持全部语⾔,所以选择 protobuf-all-21.11.zip,右键将下载链接复制出来。
下载命令:
 

wget https://github.com/protocolbuffers/protobuf/releases/download/v21.11/protobuf-all-
21.11.zip 

下载完成后,解压zip包:
 

unzip protobuf-all-21.11.zip

解压完成后,会⽣成 protobuf-21.11 ⽂件,进⼊⽂件:
 

cd protobuf-21.11

进⼊解压好的⽂件,执⾏以下命令:
 

# 第⼀步执⾏autogen.sh,但如果下载的是具体的某⼀⻔语⾔,不需要执⾏这⼀步。
./autogen.sh
# 第⼆步执⾏configure,有两种执⾏⽅式,任选其⼀即可,如下:
# 1、protobuf默认安装在 /usr/local ⽬录,lib、bin都是分散的
./configure
# 2、修改安装⽬录,统⼀安装在/usr/local/protobuf下
./configure --prefix=/usr/local/protobuf

再依次执⾏
 

make // 执⾏15分钟左右
make check // 执⾏15分钟左右
sudo make install

到此,需要你回忆⼀下在执行configure时,如果当时选择了第⼀种执行方式,也就是 ./configure ,那么到这就可以正常使⽤protobuf了。如果选择了第⼆种执行方式,即修改了安装目录,那么还需要在/etc/profile 中添加⼀些内容:
 

sudo vim /etc/profile
# 添加内容如下:
#(动态库搜索路径) 程序加载运⾏期间查找动态链接库时指定除了系统默认路径之外的其他路径
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/protobuf/lib/
#(静态库搜索路径) 程序编译期间查找动态链接库时指定查找共享库的路径
export LIBRARY_PATH=$LIBRARY_PATH:/usr/local/protobuf/lib/
#执⾏程序搜索路径
export PATH=$PATH:/usr/local/protobuf/bin/
#c程序头⽂件搜索路径
export C_INCLUDE_PATH=$C_INCLUDE_PATH:/usr/local/protobuf/include/
#c++程序头⽂件搜索路径
export CPLUS_INCLUDE_PATH=$CPLUS_INCLUDE_PATH:/usr/local/protobuf/include/
#pkg-config 路径
export PKG_CONFIG_PATH=/usr/local/protobuf/lib/pkgconfig/

最后⼀步,重新执⾏ /etc/profile 文件:
 

source /etc/profile

输⼊ protoc --version 查看版本,有显⽰说明安装成功。

到这里说明安装成功了。

3、快速上手

在快速上⼿中,会编写第⼀版本的通讯录 1.0。在通讯录 1.0 版本中,将实现:

• 对⼀个联系人的信息使⽤ PB 进行序列化,并将结果打印出来。

• 对序列化后的内容使用 PB 进⾏反序列,解析出联系人信息并打印出来。

• 联系人包含以下信息: 姓名、年龄。

通过通讯录 1.0,我们便能了解使用 ProtoBuf 初步要掌握的内容,以及体验到 ProtoBuf 的完整使⽤流程

步骤一:创建proto文件

格式建议:

• 创建 .proto 文件时,文件命名应该使用全小写字母命名,多个字母之间用 _ 连接。 例如:

lower_snake_case.proto 。

• 书写 .proto 文件代码时,应使用 2 个空格的缩进

如图创建后缀为 .proto 的文件

其中 syntax 为语言版本,简称 proto3,是 .proto 文件最新的语法版本。

package 为命名空间,package 是⼀个可选的声明符,能表示 .proto ⽂件的命名空间,在项目中要有唯⼀性。它的作⽤是为了避免我们定义的消息出现冲突。

message 是定义的消息

消息(message): 要定义的结构化对象,我们可以给这个结构化对象中定义其对应的属性内容。

这里再提⼀下为什么要定义消息?

在网络传输中,我们需要为传输双方定制协议。定制协议说白了就是定义结构体或者结构化数据,比如,tcp,udp 报文就是结构化的。

再比如将数据持久化存储到数据库时,会将⼀系列元数据统⼀用对象组织起来,再进行存储。

所以 ProtoBuf 就是以 message 的方式来支持我们定制协议字段,后期帮助我们形成类和方法来使用。在通讯录 1.0 中我们就需要为 联系人 定义⼀个 message。

定义消息字段:

在 message 中我们可以定义其属性字段,字段定义格式为:字段类型 字段名 = 字段唯⼀编号;

• 字段名称命名规范:全小写字母,多个字母之间用 _ 连接。

• 字段类型分为:标量数据类型 和 特殊类型(包括枚举、其他消息类型等)。

• 字段唯⼀编号:⽤来标识字段,⼀旦开始使⽤就不能够再改变

在这里还要特别讲解⼀下字段唯⼀编号的范围:

1 ~ 536,870,911 (2^29 - 1) ,其中 19000 ~ 19999 不可用。

19000 ~ 19999 不可用是因为:在 Protobuf 协议的实现中,对这些数进行了预留。如果非要在.proto文件中使⽤这些预留标识号,例如将 name 字段的编号设置为19000,编译时就会报警:

值得⼀提的是,范围为 1 ~ 15 的字段编号需要⼀个字节进行编码, 16 ~ 2047 内的数字需要两个字节进行编码。编码后的字节不仅只包含了编号,还包含了字段类型。所以 1 ~ 15 要⽤来标记出现非常频繁的字段,要为将来有可能添加的、频繁出现的字段预留⼀些出来。

步骤2:编译 contacts.proto 文件,生成 C++ 文件

编译命令:

protoc [--proto_path=IMPORT_PATH] --cpp_out=DST_DIR path/to/file.proto
protoc 是 Protocol Buffer      提供的命令⾏编译⼯具。
--proto_path                  指定被编译的.proto⽂件所在⽬录,可多次指定。可简写成 -I
IMPORT_PATH 。           如不指定该参数,则在当前⽬录进⾏搜索。当某个.proto ⽂件 import 其他
.proto              ⽂件时,或需要编译的 .proto ⽂件不在当前⽬录下,这时就要⽤-I来指定搜索⽬录。
--cpp_out=             指编译后的⽂件为 C++ ⽂件。
OUT_DIR                编译后⽣成⽂件的⽬标路径。
path/to/file.proto     要编译的.proto⽂件。

编译 contacts.proto ⽂件命令如下:
 

protoc --cpp_out=. contacts.proto

编译 contacts.proto 文件后会⽣成什么
编译 contacts.proto 文件后,会⽣成所选择语言的代码,我们选择的是C++,所以编译后生成了两个文件: contacts.pb.h contacts.pb.cc 。

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

• 对于每个 message ,都会生成⼀个对应的消息类。

• 在消息类中,编译器为每个字段提供了获取和设置⽅法,以及⼀下其他能够操作字段的⽅法。

• 编辑器会针对于每个 .proto ⽂件⽣成 .h 和 .cc ⽂件,分别⽤来存放类的声明与类的实现。

contact.pb.h 部分代码展示

class PeopleInfo final :
    public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:contact.PeopleInfo) */ {
 public:
  inline PeopleInfo() : PeopleInfo(nullptr) {}
  ~PeopleInfo() override;
  explicit PROTOBUF_CONSTEXPR PeopleInfo(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized);

  PeopleInfo(const PeopleInfo& from);
  PeopleInfo(PeopleInfo&& from) noexcept
    : PeopleInfo() {
    *this = ::std::move(from);
  }

  inline PeopleInfo& operator=(const PeopleInfo& from) {
    CopyFrom(from);
    return *this;
  }
  inline PeopleInfo& operator=(PeopleInfo&& from) noexcept {
    if (this == &from) return *this;
    if (GetOwningArena() == from.GetOwningArena()
  #ifdef PROTOBUF_FORCE_COPY_IN_MOVE
        && GetOwningArena() != nullptr
  #endif  // !PROTOBUF_FORCE_COPY_IN_MOVE
    ) {
      InternalSwap(&from);
    } else {
      CopyFrom(from);
    }
    return *this;
  }

上述的例子中:

• 每个字段都有设置和获取的方法, getter 的名称与小写字段完全相同,setter 方法以 set_ 开头。

• 每个字段都有⼀个 clear_ 方法,可以将字段重新设置回 empty 状态

到这里有同学可能就有疑惑了,那之前提到的序列化和反序列化方法在哪⾥呢?在消息类的父类 MessageLite 中,提供了读写消息实例的方法,包括序列化方法和反序列化方法。

class MessageLite {
public:
//序列化:
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);
}

注意:

• 序列化的结果为⼆进制字节序列,而非文本格式。

• 以上三种序列化的方法没有本质上的区别,只是序列化后输出的格式不同,可以供不同的应用场景使用。

• 序列化的 API 函数均为const成员函数,因为序列化不会改变类对象的内容, 而是将序列化的结果保存到函数入参指定的地址中。

• 详细 message API 可以参见 完整列表。

步骤3:序列化和反序列化的使用

创建⼀个测试文件 main.cc,方法中我们实现:

• 对⼀个联系人的信息使⽤ PB 进行序列化,并将结果打印出来。

• 对序列化后的内容使⽤ PB 进行反序列,解析出联系人信息并打印出来

main.cc

#include <iostream>
#include "contact.pb.h"

int main()
{
    std::string people_str;
    {
        // 对一个联系人的信息使用pb序列化,并将结果打印出来
        contact::PeopleInfo people;
        people.set_name("张三");
        people.set_age(20);
        if (!people.SerializeToString(&people_str))
        {
            std::cout << "序列化失败!" << std::endl;
            return -1;
        }

        std::cout << "序列化成功! 结果为:" << people_str << std::endl;
    }

    {
        // 将序列化后的结果反序列化,并将结果打印
        contact::PeopleInfo people;
        if (!people.ParseFromString(people_str))
        {
            std::cout << "反序列化失败!" << std::endl;
            return -1;
        }

        std::cout << "反序列化成功! 结果为:" << std::endl;
        std::cout << people.name() << std::endl;
        std::cout << people.age() << std::endl;
    }
    

        return 0;
}

编译命令:

g++ -o TestPb main.cc contact.pb.cc -std=c++11 -lprotobuf

由于 ProtoBuf 是把联系⼈对象序列化成了二进制序列,这里用 string 来作为接收二进制序列的容器。所以在终端打印的时候会有换⾏等⼀些乱码显⽰。
 

5、小结

1. 编写 .proto 文件,目的是为了定义结构对象(message)及属性内容。

2. 使用 protoc 编译器编译 .proto 文件,生成⼀系列接口代码,存放在新生成头文件和源文件中。

3. 依赖生成的接⼝,将编译生成的头⽂件包含进我们的代码中,实现对 .proto 文件中定义的字段进⾏设置和获取,和对 message 对象进⾏序列化和反序列化。

总的来说:ProtoBuf 是需要依赖通过编译生成的头⽂件和源⽂件来使用的。有了这种代码生成机制,开发人员再也不用吭哧吭哧地编写那些协议解析的代码了(干这种活是典型的吃力不讨好)

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

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

相关文章

upload-labs靶场通关攻略

一。 1.将一个php文件重命名为jpg文件 2.将这个jpg文件上传然后抓包吧jpg改成php接着放行 3.鼠标右键图片在新建标签页打开图片后去蚁剑连接 二 1.上传jpg文件然后抓包把jpg改成php放行 2.图片右键新建标签页打开图片&#xff0c;复制图片地址去蚁剑连接 三 1.文件重命名为1…

四款AI工具写完毕业论文,这才是真正的写作神器!(含教程)

在当今信息爆炸的时代&#xff0c;AI写作工具的出现极大地提高了写作效率和质量。特别是在学术论文的撰写过程中&#xff0c;AI工具不仅能够帮助快速生成论文草稿&#xff0c;还能优化内容质量、进行查重和排版&#xff0c;提供写作建议和创意灵感&#xff0c;极大地减轻了学者…

ChatGPT的全面写作革命:我们迎来效率飞跃还是创造力危机?

近年来&#xff0c;随着人工智能技术的持续发展&#xff0c;类似于ChatGPT的AI工具开始变革我们的工作模式。从撰写访谈大纲到文章框架&#xff0c;再到设计标题&#xff0c;许多人已逐渐依赖AI的辅助功能。不过&#xff0c;最近一家媒体采取了更加大胆的尝试&#xff1a;完全交…

Python基础 3 - 函数及数据容器

文章目录 一、函数概念1、函数介绍2、函数的定义3、函数的调用4、函数说明文档5、函数嵌套调用6、变量作用域1&#xff09;局部变量2&#xff09;全局变量3&#xff09;声明全局变量 二、数据容器入门1、列表 (list)1) 列表的定义2) 调用列表元素3) 列表的方法4) 列表的特点5) …

债务重组的费用不菲,为什么现在越来越多的人还是愿意做债重?

债务重组&#xff0c;起初是专为优质企业客户量身打造的大额融资解决方案&#xff0c;周期紧凑&#xff0c;一个月见成效。但随着时代变迁&#xff0c;它悄然转型&#xff0c;成了负债繁重、网贷缠身者的“救星”。这类朋友通过债务重组&#xff0c;先是由专业机构垫付月供&…

论文阅读:VideoMamba: State Space Model for Efficient Video Understanding

论文地址&#xff1a;arxiv 摘要 为了解决视频理解中的局部冗余与全局依赖性的双重挑战。作者将 Mamba 模型应用于视频领域。所提出的 VideoMamba 克服了现有的 3D 卷积神经网络与视频 Transformer 的局限性。 经过广泛的评估提示了 VideoMamba 的能力&#xff1a; 在视觉领…

基于 Householder 变换的 qr 分解 算法与源码实现

1&#xff0c;算法描述 1.1 算法1 反射向量 计算 Householder 向量 给定 算法计算满足 v(1) 1.0 的 和 , 使得 是正交矩阵且 , 即&#xff0c;将m维向量 通过反射变换 反射至 轴上去。 1.2 算法2 QR 分解 Householder QR 分解 未完待补。。。。 2&#xff0c;源码…

什么是RS485总线?

1.什么是RS485总线&#xff1f; RS485 是一种通用的通信标准&#xff0c;广泛用于数据采集和控制应用中。 它的主要优点之一是它允许将多个 RS485 设备放在同一条总线上&#xff0c;这使得多个节点可以相互连接。 RS-485是美国电子工业协会&#xff08;EIA&#xff09;在1983年…

2024HarmonyOS应用开发者高级认证最新整理题库和答案(已收录182道 )

更新截止2024-08-27,完整题库一共182道题,足够覆盖90%考题,如有新题和遗漏我会持续补充 所有题目的选项都是打乱顺序的,记答案不要记序号 完整题库请在我的网盘下载或查看在线文档 完整题库在线文档预览 单选(已收录102道) 1 . 以下哪个装饰器用来表示并发共享对象。(B) A. @…

Windows服务器应急响应(下)

目录 介绍步骤 介绍 进程&#xff08;Process&#xff09;是计算机中的程序关于某数据集合上的一次运行活动&#xff0c;是系统进行资源分配和调度的基本单位&#xff0c;是操作系统结构的基础。在早期面向进程设计的计算机结构中&#xff0c;进程是程序的基本执行实体&#x…

基于FPGA的lz4解压缩仿真调试

1、简介 对于任意长度顺序呈现的输入数据流&#xff0c;通过对冗余byte的数据编码&#xff0c;完成数据压缩的问题。数据包格式 从数据包长度可知&#xff0c;最少需要5个字节才能压缩&#xff0c;否则压缩无意义&#xff0c;对于lz其他的介绍可以百度&#xff0c;本文只介绍…

JobScheduler 开发自测调试

1. 目标 例如以下模拟数据 相同时间内灭屏待机情况 有Job优化版本 无Job优化版本 数据展示 剩余电量 50 45 续航提升5% 时间延迟次数 100 0 N/A,体现数据优化原因 拦截Job次数 132 0 N/A,体现数据优化原因 第三方App的Job 执行总次数(越大越耗电) 20 200 优化后,减少(1-20/…

C++ 变量、输入输出、表达式和顺序语句 ac-wing

输入两个整数&#xff0c;求这两个整数的和是多少。 #include <iostream> using namespace std; int main () {int a, b;cin >> a >> b;cout << a b << endl;return 0; }差 #include<iostream> using namespace std; int main() {int A…

easy_fastapi 后端开发框架

GitHub easy_fastapi by one-ccs 遵循 MIT 开源协议 Easy FastAPI 基于 FastAPI 开发的后端框架&#xff0c;集成 SQLAlchemy、Pydantic、Alembic、PyJWT 等插件。 一、目录结构说明 project-root/ │ ├─ backend/ # 后端项目目录&#xff08;python 3.12.4&#xff09; │…

微信小程序背景图无法显示

文章目录 不知道有没有人跟我一样&#xff0c;刚接触微信小程序&#xff0c;在写代码的时候&#xff0c;背景图莫名奇妙不显示。 网上有很多解决方法&#xff0c;比如转 base64 &#xff0c;网络图片地址等等&#xff0c;但我觉得都太麻烦了&#xff0c;这里直接给出我的解决方…

Unity实战案例全解析 之 背包/贩卖/锻造系统(左侧类图实现)

物品类 using System.Collections; using System.Collections.Generic; using UnityEngine; public class Item {#region 物品类的基础属性public int ID { get; set; }public string Name { get; set; }public Typeitem typeitem { get; set; }//物品类型public Qualityitem…

VMware17 虚拟机使用NAT模式上网配置

1、. 确认网络适配器选择NAT模式 2、 查看所需要配置的网络信息 在NAT设置里面找到网关ip 在DHCP设置中查看可用ip的范围 后面设置虚拟机的etc/sysconfig/network-scripts/下面的ens文件会用到 查看网卡名称 ip addr 我这里的网卡名称是ens33 配置网关、ip地址、DNS地址 vi…

[线程]单例模式 及 指令重排序

文章目录 一. 单例模式饿汉模式懒汉模式单例模式中涉及到的线程安全问题 二. 指令重排序引起线程安全问题 一. 单例模式 单例模式, 是一种经典的设计模式 设计模式: 类似于棋谱, 把编程中各种经典的问题场景给你盘一盘, 并给出一下解决方案 遇到这种场景, 代码就这样写, 绝对不…

Linux和Unix的区别及为什么鸿蒙系统不用Unix的原因

目录 Linux是什么? Unix是什么&#xff1f; 他们的区别&#xff1a; 鸿蒙系统介绍及鸿蒙系统不用Unix的原因 Linux是什么? Linux的历史可以追溯到1991年&#xff0c;由芬兰的计算机科学家林纳斯托瓦兹&#xff08;Linus Torvalds&#xff09;为了学习操作系统的工作原理而…

【 OpenHarmony 系统应用源码魔改 】-- Launcher 之「桌面布局定制」

前言 阅读本篇文章之前&#xff0c;有几个需要说明一下&#xff1a; 调试设备&#xff1a;平板&#xff0c;如果你是开发者手机&#xff0c;一样可以加 Log 调试&#xff0c;源码仍然是手机和平板一起分析&#xff1b;文章中的 Log 信息所显示的数值可能跟你的设备不一样&…