Protobuf快速入门

news2024/11/23 23:08:08

Protobuf入门

Protobuf介绍

Protobuf (Protocol Buffers) 是谷歌开发的一款无关平台,无关语言,可扩展,轻量级高效的序列化结构的数据格式,用于将自定义数据结构序列化成字节流,和将字节流反序列化为数据结构。所以很适合做数据存储和为不同语言,不同应用之间互相通信的数据交换格式,只要实现相同的协议格式,即后缀为proto文件被编译成不同的语言版本,加入各自的项目中,这样不同的语言可以解析其它语言通Protobuf序列化的数据。目前官方提供c++,java,go等语言支持。

Protobuf特点

  • 语言无关、平台无关:即Protobuf支持java、C++、Python等多种语言。
  • 高效:即比XML更小、更快、更简单。
  • 扩展性、兼容性好:可以更新数据结构,而不影响和破坏原有的旧程序。
  • 使用特定:Protobuf是需要依赖通过编译生成的头文件和源文件来使用。

工作流程:

image-20230521170256330

  1. 编写.proto⽂件,⽬的是为了定义结构对象(message)及属性内容。
  2. 使⽤protoc编译器编译.proto⽂件,⽣成⼀系列接⼝代码,存放在新⽣成头⽂件和源⽂件中。
  3. 依赖⽣成的接⼝,将编译⽣成的头⽂件包含进我们的代码中,实现对.proto⽂件中定义的字段进⾏设置和获取,和对message对象进⾏序列化和反序列化。

什么是序列化和反序列化

  • 序列化过程:是指把一个对象变成二进制内容。
  • 反序列化过程:把一个二进制内容变回对象。

Protobuf入门实战

实现目标如下:

• 对⼀个联系⼈的信息使⽤PB进⾏序列化,并将结果打印出来。
• 对序列化后的内容使⽤PB进⾏反序列,解析出联系⼈信息并打印出来。
• 联系⼈包含以下信息:姓名、年龄。

步骤如下:

  • 编写.proto文件,定义message
  • 编译.proto文件,查看生成的代码
  • 实现对一个联系人的序列化和反序列化操作

编写.proto文件

创建contacts.proto文件

Protocol Buffers语⾔版本3,简称proto3,是.proto⽂件最新的语法版本。proto3简化了Protocol Buffers语⾔,既易于使⽤,⼜可以在更⼴泛的编程语⾔中使⽤。它允许你使⽤Java,C++,Python等多种语⾔⽣成protocol buffer代码。在.proto⽂件中,要使⽤syntax = “proto3”; 来指定⽂件语法为proto3,并且必须写在除去注释内容的第⼀⾏。如果没有指定,编译器会使⽤proto2语法。

syntax="proto3"; //消息类型命名规范:使⽤驼峰命名法,⾸字⺟⼤写。
package contacts;

//定义联系人消息
message PeopleInfo{
    string name=1;
    string age=2;
}

Protobuf标量数据

protoTypeNotesC++ Type
doubledouble
floatfloat
int32使⽤变⻓编码[1]。负数的编码效率较低⸺若字段可 能为负值,应使⽤sint32代替。int32
int64使⽤变⻓编码[1]。负数的编码效率较低⸺若字段可 能为负值,应使⽤sint64代替。int64
uint32使⽤变⻓编码[1]。uint32
uint64使⽤变⻓编码[1]。uint64
sint32使⽤变⻓编码[1]。符号整型。负值的编码效率⾼于 常规的int32类型。int32
sint64使⽤变⻓编码[1]。符号整型。负值的编码效率⾼于 常规的int64类型。int64
fixed32定⻓4字节。若值常⼤于2^28则会⽐uint32更⾼ 效。uint32
fixed64定⻓8字节。若值常⼤于2^56则会⽐uint64更⾼ 效。uint64
sfixed32定⻓4字节。int32
sfixed64定⻓8字节。int64
boolbool
string包含UTF-8和ASCII编码的字符串,⻓度不能超过 2^32。string
bytes可包含任意的字节序列但⻓度不能超过2^32。string

[1]变⻓编码是指:经过protobuf编码后,原本4字节或8字节的数可能会被变为其他字节数。

字段的唯一编号

字段编号的范围为1536,870,911(2^29-1),其中1900019999不可⽤。

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

image-20230521234330107

编译.proto文件

protoc [--proto_path=IMPORT_PATH] --cpp_out=DST_DIR path/to/file.proto
protoc 是 Protocol Buffer 提供的命令⾏编译⼯具。

--proto_path 指定 被编译的.proto⽂件所在⽬录,可多次指定。可简写成 -IIMPORT_PATH 。如不指定该参数,则在当前⽬录进⾏搜索。当某个.proto ⽂件 import 其他.proto ⽂件时,或需要编译的 .proto ⽂件不在当前⽬录下,这时就要⽤-I来指定搜索⽬录。

--cpp_out= 指编译后的⽂件为 C++ ⽂件。

DST_DIR 编译后⽣成⽂件的⽬标路径。

path/to/file.proto 要编译的.proto⽂件。

执行命令:protoc --cpp_out=. ./contacts.proto

image-20230522001826482

编译contacts.proto⽂件后会⽣成什么

编译contacts.proto⽂件后,会⽣成所选择语⾔的代码,这里选择的是C++,所以编译后⽣成了两个⽂件: contacts.pb.h contacts.pb.cc。对于编译⽣成的C++代码,包含了以下内容:
• 对于每个message,都会⽣成⼀个对应的消息类。

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

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

image-20230522003039922

该类是继承于Message类。且对于每一个成员,都有set方法和get方法获取和设置对应的成员属性。

image-20230522003255850

而序列化和反序列化的方法,则是定义在其父类中:MessageLite

image-20230522003634880

其接口主要如下:

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成员函数,因为序列化不会改变类对象的内容,⽽是将序列化的结果保存到函数⼊参指定的地址中
• 详细messageAPI可以参⻅完整列表。 Protocol Buffers Documentation (protobuf.dev)

实现序列化和反序列化

  • 对⼀个联系⼈的信息使⽤protobuf进⾏序列化,并将结果打印出来。
  • 然后将二进制数据使用protobuf进行反序列,并获取其内容。
  • 创建文件use_contacts.cpp
#include <iostream>
#include "contacts.pb.h"
using namespace std;

int main()
{

    string people_str;
    { 
        // .proto⽂件声明的package,通过protoc编译后,会为编译⽣成的C++代码声明同名的命名空间
        // 其范围是在.proto ⽂件中定义的内容
        contacts::PeopleInfo people;
        people.set_age("20");
        people.set_name("张三");
        // 调⽤序列化⽅法,将序列化后的⼆进制序列存⼊string中
        if (!people.SerializeToString(&people_str))
        {
            cout << "序列化联系⼈失败." << endl;
        }
        // 打印序列化结果
        cout << "序列化后的 people_str: " << people_str << endl;
    }
    {
        contacts::PeopleInfo people;
        // 调⽤反序列化⽅法,读取string中存放的⼆进制序列,并反序列化出对象
        if (!people.ParseFromString(people_str))
        {
            cout << "反序列化出联系⼈失败." << endl;
        }
        // 打印结果
        cout << "Parse age: " << people.age() << endl;
        cout << "Parse name: " << people.name() << endl;
    }

    return 0;
}

编译:g++ -o contacts use_contacts.cpp contacts.pb.cc -lprotobuf -std=c++11

image-20230522005124152

注意:

  • 由于ProtoBuf是把对象序列化成了⼆进制序列,这⾥⽤string来作为接收⼆进制序列的容器。所以在终端打印的时候会有换⾏等⼀些乱码显⽰。
  • 所以相对于xml和JSON来说,因为被编码成⼆进制,破解成本增⼤,ProtoBuf编码是相对安全的。

protobuf语法详解

项目目标:升级的版本,最终将会升级如下内容:
• 不再打印联系⼈的序列化结果,⽽是将通讯录序列化后并写⼊⽂件中。
• 从⽂件中将通讯录解析出来,并进⾏打印。
• 新增联系⼈属性,共包括:姓名、年龄、电话信息、地址、其他联系⽅式、备注。

字段规则

  • singular:消息中可以包含该字段零次或⼀次(不超过⼀次)。proto3语法中,字段默认使⽤该
    规则。
  • repeated:消息中可以包含该字段任意多次(包括零次),其中重复值的顺序会被保留。可以理解为定义了⼀个数组。

比如下面的写法表示:在一个PeopleInfo信息里面,可以包含多个phone_num

//定义联系人消息
message PeopleInfo{
    string name=1;
    string age=2;
    repeated string phone_num=3;
}

消息类型的定义和使用

下面实现,在目前的contacts.proto文件中加入phone属性,并单独设置为一个message。

则一共有三种写法,包括嵌套,非嵌套写法,分开住两个文件中定义

嵌套写法

/--------------嵌套写法---------------/
message PeopleInfo{
    string name=1;
    string age=2;
    message Phone{
        string number=1;
    }
    repeated Phone phone=3;
}
//通讯录定义
message Contacts{
    repeated PeopleInfo people=1;
}

image-20230601000333667

当为嵌套定义时,Phone在头文件中的类名就变为了PeopleInfo_Phone。也是使用下划线将内部与外部名连接起来。

非嵌套写法

message Phone{
    string number=1;
}

//定义联系人消息
message PeopleInfo{
    string name=1;
    string age=2;
    repeated Phone phone=3;
}

message Contacts{
    repeated PeopleInfo people=1;
}

image-20230601001213138

分为两个文件

image-20230601002023965

如果contacts2.proto文件中并没有定义package contacts2,那么文件1在使用message phone时,也不需要带上contacts2.。

image-20230601002628614

实现通讯录v0.2

实现write.cc:向通讯录中写入内容

void AddPeopleInfo(contacts1::Contacts *contacts_ptr)
{
    for (int j = 0;; j++)
    {
        std::cout << "--------- 新增联系人(输入“退出”即可结束新增联系人) --------" << std::endl;
        std::cout << " 请输入联系人姓名: ";
        string name;
        getline(std::cin, name);
        if(name=="退出"){
            break;
        }
        contacts1::PeopleInfo *peopleinfo = contacts_ptr->add_people(); //添加一个people消息
        peopleinfo->set_name(name);
        std::cout << " 请输入联系人年龄: ";
        string age;
        getline(std::cin, age);
        peopleinfo->set_age(age);
        for (int i = 0;; i++)
        {
            std::cout << " 请输入联系人电话 " << i + 1 << "(只输入回车完成电话的新增): ";
            string number;
            getline(std::cin, number);
            if (number.empty())
            {
                break;
            }
            contacts1::Phone *phone = peopleinfo->add_phone();  //添加一个phone信息
            phone->set_number(number);
        }
    }

}

int main()
{
    contacts1::Contacts contacts;
    // 读取本地已经存在的联系人文件
    fstream input("contacts.bin", ios::in | ios::binary);
    if (!input)
    {
        std::cout << " contacts.bin not find,create new file! " << std::endl;
    }
    else if (!contacts.ParseFromIstream(&input))
    {
        std::cerr << " Failed to parse contacts.bin " << std::endl;
        input.close();
        return -1;
    }

    AddPeopleInfo(&contacts);
    // 向文件中覆盖写入新的内容
    fstream output("contacts.bin", ios::out | ios::binary | ios::trunc);
    if (!contacts.SerializeToOstream(&output))
    {
        std::cerr << " Failed to serialize ,write error! " << std::endl;
        output.close();
        input.close();
        return -1;
    }
    std::cout << " write success! " << std::endl;
    output.close();
    input.close();
    /*
        在程序结束时调⽤ ShutdownProtobufLibrary(),为了删除 Protocol Buffer 库分配的所
    有全局对象。对于⼤多数程序来说这是不必要的,因为该过程⽆论如何都要退出,并且操作系统将负责
    回收其所有内存。但是,如果你使⽤了内存泄漏检查程序,该程序需要释放每个最后对象,或者你正在
    编写可以由单个进程多次加载和卸载的库,那么你可能希望强制使⽤ Protocol Buffers 来清理所有
    内容。
    */
    google::protobuf::ShutdownProtobufLibrary();
    return 0;
}

image-20230601233015352

可以看到contacts.bin中以二进制的方式写入了内容:

image-20230601233113056

hexdump程序

hexdump:是Linux下的⼀个⼆进制⽂件查看⼯具,它可以将⼆进制⽂件转换为ASCII、⼋进制、⼗进制、⼗六进制格式进⾏查看。

参数:

  • -C: 表⽰每个字节显⽰为16进制和相应的ASCII字符

image-20230601233324028

实现read.cc:反序列化二进制文件的内容

void PrintContacts(contacts1::Contacts& contacts){
    for(int i=0;i<contacts.people_size();i++){
        contacts1::PeopleInfo peopleinfo=contacts.people(i);    //通过下标获取people
        std:cout<<"----------联系人"<<i+1<<"----------"<<std::endl;
        std::cout<<"联系人姓名: "<<peopleinfo.name()<<std::endl;
        std::cout<<"联系人年龄: "<<peopleinfo.age()<<std::endl;
        int j=1;
        for(;j<=peopleinfo.phone_size();j++){
            std::cout<<"电话: "<<peopleinfo.phone(j-1).number()<<std::endl;
        }
    }
}

int main(){
    contacts1::Contacts contacts;
    fstream input("contacts.bin",ios::binary | ios::in);
    if(!input){
        std::cout << " contacts.bin not find,create new file! " << std::endl;
    }
    else if(!contacts.ParseFromIstream(&input)){

        std::cerr<<" Failed to Parse contacts.bin !"<<std::endl;
        input.close();
        return -1;
    }
    PrintContacts(contacts);
    input.close();
    google::protobuf::ShutdownProtobufLibrary();
    return 0;
}

输出结果:

image-20230602000102383

decode命令

我们可以⽤protoc -h命令来查看ProtoBuf为我们提供的所有命令option。

image-20230602000242002

其中ProtoBuf提供⼀个命令选项 --decode ,表⽰从标准输⼊中读取给定类型的⼆进制消息,并将其以⽂本格式写⼊标准输出。消息类型必须在.proto⽂件或导⼊的⽂件中定义。

protoc --decode=MESSAGE_TYPE TYPE_PATH < STD_IN
可以指定解码一种消息类型,后面需要指明消息类型的路径 已经输入文件

protoc --decode=contacts.Contacts ./contacts.proto < contacts.bin

image-20230602001217377

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

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

相关文章

chatgpt赋能python:Python关联规则——挖掘数据中的隐藏关系

Python关联规则——挖掘数据中的隐藏关系 在数据分析和挖掘中&#xff0c;我们经常需要找到数据集中的关联规则&#xff0c;以便更好地理解数据背后的隐藏关系和趋势。Python关联规则是一种经典的关联规则挖掘算法&#xff0c;它能够识别和发现数据中的有意义的关联性&#xf…

延迟队列的三种实现方案

延迟任务 定时任务&#xff1a;有固定周期的&#xff0c;有明确的触发时间延迟队列&#xff1a;没有固定的开始时间&#xff0c;它常常是由一个事件触发的&#xff0c;而在这个事件触发之后的一段时间内触发另一个事件&#xff0c;任务可以立即执行&#xff0c;也可以延迟 应用…

chatgpt赋能python:Python关联性分析:介绍及应用案例

Python 关联性分析&#xff1a;介绍及应用案例 在数据分析和机器学习领域中&#xff0c;关联性分析是一种经常被使用的工具。通过分析不同特征之间的相关性&#xff0c;可以获取大量有价值的信息&#xff0c;如客户行为模式、产品关联性等等。Python作为一种高效而简洁的编程语…

SpringCloud(2)

服务拆分和远程调用 任何分布式架构都离不开服务的拆分&#xff0c;微服务也是一样。 2.1.服务拆分原则 这里我总结了微服务拆分时的几个原则&#xff1a; 不同微服务&#xff0c;不要重复开发相同业务微服务数据独立&#xff0c;不要访问其它微服务的数据库微服务可以将自…

【生物力学】《人体骨肌系统生物力学》- 王成焘老师 - 第1章 - 总论

目录回到目录第2章 文章目录 1.1 人体骨肌系统1.1.1 人体骨骼系统1. 骨骼的功能2. 骨骼的构造3. 骨组织的构成1. 骨细胞与骨基质2. 成骨细胞3. 破骨细胞4. 骨原细胞 4. 皮质骨与松质骨的构造1. 皮质骨2. 松质骨 1.1.2 关节与骨连接1. 运动关节2. 局部活动关节3. 微动关节与固定…

chatgpt赋能python:Python中点的SEO

Python中点的SEO 在Python编程中&#xff0c;点&#xff08;.&#xff09;是一个非常重要的语法符号&#xff0c;可以用于访问对象属性、调用方法、导入模块等。但是&#xff0c;在SEO优化中&#xff0c;点也有着特殊的含义。 什么是点的SEO含义&#xff1f; 在URL地址中&am…

chatgpt赋能python:Python关联规则代码:优化你的数据分析策略

Python关联规则代码&#xff1a;优化你的数据分析策略 数据分析是当今商业竞争中的重要环节之一。随着数据量的不断增长&#xff0c;为了更好地发现数据之间的关系和规律&#xff0c;数据分析师需要采用一些有效的工具和技术。Python是一种强大的编程语言&#xff0c;可以用于…

白天过节,晚上了解了下 Threejs3D

文章目录 I. 介绍Three.js 3DA. 什么是Three.jsB. Three.js的历史C. Three.js的应用领域 II. 开始使用Three.js 3DA. 安装和引入Three.jsB. 创建Three.js场景C. 添加3D对象到场景D. 控制相机和灯光E. 渲染场景 III. Three.js 3D中的基本概念A. 坐标系统和变换B. 材质和纹理C. 几…

分布式存储Ceph的部署及应用(创建MDS、RBD、RGW 接口)

目录 一、存储基础1.1 单机存储设备1.2 单机存储的问题 二、分布式存储&#xff08;软件定义的存储 SDS&#xff09;2.1 分布式存储的类型 三、Ceph 简介3.1 Ceph 优势3.2 Ceph 架构3.3 Ceph 核心组件3.4 Pool、PG 和 OSD 的关系3.5 OSD 存储后端3.6 Ceph 数据的存储过程3.7 Ce…

音视频基础 及 海思sample_venc解读

1、sample的整体架构 (1)sample其实是很多个例程&#xff0c;所以有很多个main (2)每一个例程面向一个典型应用&#xff0c;common是通用性主体函数&#xff0c;我们只分析venc (3)基本的架构是&#xff1a;venc中的main调用venc中的功能函数&#xff0c;再调用common中的功…

chatgpt赋能python:关于Python关联数组

关于Python关联数组 Python是一种高级编程语言&#xff0c;具有简单、易懂的语法和丰富的功能。其中一项强大的特性是Python关联数组&#xff0c;也称为字典。在本文中&#xff0c;我们将探讨什么是Python关联数组、为什么它们很有用、如何使用它们以及应该使用哪种类型的关联…

了解FFmpeg音频通道布局结构:AVChannelLayout结构体解析

1. 引言&#xff08;Introduction&#xff09; 1.1 FFmpeg简介&#xff08;Brief Introduction to FFmpeg&#xff09; FFmpeg是一套可以用来记录、转换数字音频、视频&#xff0c;并能将其转化为流的开源计算机程序。它提供了录制、转换以及流化音视频的完整解决方案。它包含…

计算机网络,期末大题整理part1

1.要发送的数据1010001&#xff0c;采用CRC生成多项式是P(X)X4X2X1&#xff0c;试求应添加在数据后面的余数。 接收端接收到的PPP帧的数据部分是0001110111110111110110&#xff0c;问删除发送端加的零比特后变成怎样的比特串 答&#xff1a;00011101111111111110 &#xff0…

vue3 antd项目实战——修改和增加公用一个弹窗(页面组件传值)

vue3 antd项目实战——修改和增加公用一个弹窗&#xff08;页面组件传值&#xff09; 往期知识调用&#xff08;步骤不懂就看这儿&#xff09;场景复现实战演示基础modal框的搭建现在我们就可以实现modal框及内部表单的动态绑定了&#xff01;&#xff01;&#xff01; ![在这里…

国内可用 ChatGPT 网页版

前言 ChatGPT持续火热&#xff0c;然鹅国内很多人还是不会使用。 2023年6月1日消息&#xff0c;ChatGPT 聊天机器人可以根据用户的输入生成各种各样的文本&#xff0c;包括代码。但是&#xff0c;加拿大魁北克大学的四位研究人员发现&#xff0c;ChatGPT 生成的代码往往存在严…

读改变未来的九大算法笔记01_数据压缩

1. 起源 1.1. 香农–法诺编码&#xff08;Shannon-Fano Coding&#xff09; 1.1.1. 克劳德香农 1.1.1.1. 1948年论文创建信息理论领域的贝尔实验室科学家 1.1.2. 麻省理工学院教授罗伯特法诺&#xff08;Robert Fano&#xff09; 1.2. 霍夫曼编码 1.2.1. 大卫霍夫曼 1.2…

chatgpt赋能python:Python关联图简介

Python关联图简介 Python语言是一种非常流行和广泛使用的语言&#xff0c;可用于各种用途&#xff0c;包括数据分析和数据可视化。在数据可视化方面&#xff0c;Python有许多强大的库和工具&#xff0c;可以用来创建各种类型的图表和图形。其中之一是Python关联图。 在本篇文…

LNMP应用

安装 Nginx 服务 systemctl stop firewalld systemctl disable firewalld setenforce 0 1、安装依赖包 yum -y install pcre-devel zlib-devel gcc gcc-c make2、创建运行用户 useradd -M -s /sbin/nologin nginx3、编译安装 cd /opt tar zxvf nginx-1.12.0.tar.gz -C /op…

chatgpt赋能Python-python关联分析算法

介绍 Python是一种高级、通用、直译式编程语言。在数据科学和机器学习领域&#xff0c;它已成为首选语言之一。它有很多强大的库和框架&#xff0c;可以帮助数据分析师、科学家和工程师处理大量数据。Python关联分析算法就是其中之一。 Python关联分析算法可以帮助我们在数据…

MobileNetv1、MobileNetv2、MobileNetv3网络讲解

MobileNetv1、MobileNetv2、MobileNetv3网络讲解_mobilenetv3和mobilenetv2_I松风水月的博客-CSDN博客MobileNetv1、MobileNetv2、MobileNetv3网络讲解https://blog.csdn.net/qq_38683460/article/details/127409816