protobuf 入门

article/2025/8/30 6:43:09

参考自
https://juejin.cn/post/7029961388411846664

介绍了protobuf基本概念、优缺点、与protobuf在C++上的基本使用

1. 什么是protobuf

它是一个灵活、高效、结构化的序列化数据结构,它与传统的XML、JSON等相比,它更小、更快、更简单。

ProtoBuf是由Google开发的一种数据序列化协议(类似于XML、JSON、hessian)。ProtoBuf能够将数据进行序列化,并广泛应用在数据存储、通信协议等方面。protobuf压缩和传输效率高,语法简单,表达力强。

我们说的 protobuf 通常包括下面三点:

  • 一种二进制数据交换格式。可以将 C++ 中定义的存储类的内容 与 二进制序列串 相互转换,主要用于数据传输或保存
  • 定义了一种源文件,扩展名为 .proto(类比.cpp文件),使用这种源文件,可以定义存储类的内容
  • protobuf有自己的编译器 protoc,可以将 .proto 编译成.cc文件,使之成为一个可以在 C++ 工程中直接使用的类

序列化:将数据结构或对象转换成二进制串的过程。反序列化:将在序列化过程中所产生的二进制串转换成数据结构或对象的过程。

1.1 protobuf优缺点

protobuf / XML / JSON这三种序列化方式优缺点

优点:

  • json

    较XML格式更加小巧,传输效率较xml提高了很多,可读性还不错。

  • xml

    可读性强,解析方便。

  • protobuf

    就是传输效率快(据说在数据量大的时候,传输效率比xml和json快10-20倍),序列化后体积相比Json和XML很小,支持跨平台多语言,消息格式升级和兼容性还不错,序列化反序列化速度很快。

    并且protobuf有代码生成机制。序列化反序列化直接对应程序中的数据类,不需要解析后在进行映射(XML/JSON都是这种方式)

    比如你你写个一下类似结构体的内容

    message testA  
    {  int32 m_testA = 1;  
    }  
    

    像写一个这样的结构,protobuf可以自动生成它的.h 文件和点.cpp文件。protobuf将对结构体testA的操作封装成一个类。

    protobuf中定义一个消息类型是通过关键字message字段指定的,这个关键字类似于C++/Java中的class关键字。使用protobuf编译器将proto编译成C++代码之后,每个message都会生成一个名字与之对应的C++类,该类公开继承自google::protobuf::Message

    性能方面

    • 序列化后,数据大小可缩小3倍
    • 序列化速度快
    • 传输速度快

    使用方面

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

    使用范围

    • 跨平台、跨语言、可扩展性强

缺点:

  • json缺点就是传输效率也不是特别高(比xml快,但比protobuf要慢很多)。

  • xml缺点就是效率不高,资源消耗过大。

  • protobuf缺点就是使用不太方便。

    为了提高性能,protobuf采用了二进制格式进行编码。这直接导致了可读性差。但是同时也很安全

在一个需要大量的数据传输的场景中,如果数据量很大,那么选择protobuf可以明显的减少数据量,减少网络IO,从而减少网络传输所消耗的时间。考虑到作为一个主打社交的产品,消息数据量会非常大,同时为了节约流量,所以采用protobuf是一个不错的选择。

在这里插入图片描述

2. 定义.proto文件

2.1 message 介绍

messageprotobuf中定义一个消息类型是通过关键字message字段指定的,这个关键字类似于C++/Java中的class关键字。使用protobuf编译器将proto编译成C++代码之后,每个message都会生成一个名字与之对应的C++类,该类公开继承自google::protobuf::Message

2.2 message 消息定义

创建tutorial.person.proto文件,文件内容如下:

// FileName: tutorial.person.proto 
// 通常文件名建议命名格式为 包名.消息名.proto // 表示正在使用proto3
syntax = "proto3";//包声明,tutorial 也可以声明为二级类型。在C++中则是命名空间tutorial::
//例如a.b,表示a类别下b子类别。在C++中则是命名空间a::b::
package tutorial;//编译器将生成一个名为person的类
//类的字段信息包括姓名name,编号id,邮箱email,以及电话号码phones
message Person
{string name = 1;int32 id =2;string email = 3; enum PhoneType {  //电话类型枚举值 MOBILE = 0;  //手机号  HOME = 1;    //家庭联系电话WORK = 2;    //工作联系电话} //电话号码PhoneNumber消息体//组成包括号码number、电话类型typemessage PhoneNumber {string number = 1;    PhoneType type = 2;}  //重复字段,保存多个PhoneNumberrepeated PhoneNumber phones = 4; 
}// 通讯录消息体,包括一个重复字段,保存多个Person
message AddressBook { repeated Person people = 1; 
}

2.3 字段解释

2.3.1 包声明

proto 文件以package声明开头,这有助于防止不同项目之间命名冲突。在C++中,以package声明的文件内容生成的类将放在与包名匹配的namespace,上面的.proto文件中所有的声明都属于tutorial,即在命名空间tutorial::(如果是package a.b,则对应命名空间a::b:: )。

2.3.2 字段规则
  • repeated: 消息体中重复字段,可以保存多个相同类型的字段值。其中,proto3默认使用packed方式存储,这样编码方式比较节省内存。
2.3.3 标识号

在消息定义中,**每个字段都有唯一的一个数字标识符。这些标识符是用来在消息的二进制格式中识别各个字段的,一旦开始使用就不能够再改变。**注:[1,15]之内的标识号在编码的时候会占用一个字节。[16,2047]之内的标识号则占用2个字节。所以应该为那些频繁出现的消息元素保留 [1,15]之内的标识号。切记:要为将来有可能添加的、频繁出现的标识号预留一些标识号。

最小的标识号可以从1开始,最大到2^29 - 1。不可以使用其中的[19000-19999]的标识号, Protobuf协议实现中对这些进行了预留。如果非要在.proto文件中使用这些预留标识号,编译时就会报警。

2.3.4 数据定义

许多标准的简单数据类型都可以用作message字段类型,包括bool,int32,float,doublestring。还可以使用其他message类型作为字段类型在消息体中添加更多结构。比如上面的例子中,AddressBook消息内包含Person类型的字段;Person内还包含了PhoneNumber类型的字段

protobuf中简单数据类型说明

.proto TypeNotesC++ TypeJava TypePython Type[2]Go TypeRuby TypeC# TypePHP Type
doubledoubledoublefloatfloat64Floatdoublefloat
floatfloatfloatfloatfloat32Floatfloatfloat
int32使用变长编码,对于负值的效率很低,如果你的域有可能有负值,请使用sint64替代int32intintint32Fixnum 或者 Bignum(根据需要)intinteger
uint32使用变长编码uint32intint/longuint32Fixnum 或者 Bignum(根据需要)uintinteger
uint64使用变长编码uint64longint/longuint64Bignumulonginteger/string
sint32使用变长编码,这些编码在负值时比int32高效的多int32intintint32Fixnum 或者 Bignum(根据需要)intinteger
sint64使用变长编码,有符号的整型值。编码时比通常的int64高效。int64longint/longint64Bignumlonginteger/string
fixed32总是4个字节,如果数值总是比总是比228大的话,这个类型会比uint32高效。uint32intintuint32Fixnum 或者 Bignum(根据需要)uintinteger
fixed64总是8个字节,如果数值总是比总是比256大的话,这个类型会比uint64高效。uint64longint/longuint64Bignumulonginteger/string
sfixed32总是4个字节int32intintint32Fixnum 或者 Bignum(根据需要)intinteger
sfixed64总是8个字节int64longint/longint64Bignumlonginteger/string
boolboolbooleanboolboolTrueClass/FalseClassboolboolean
string一个字符串必须是UTF-8编码或者7-bit ASCII编码的文本。stringStringstr/unicodestringString (UTF-8)stringstring
bytes可能包含任意顺序的字节数据。stringByteStringstr[]byteString (ASCII-8BIT)ByteStringstring
2.3.5 函数方法

protocol buffer 编译器为.proto文件中定义的每个message定义一个class,message的字段即为class中的成员变量

//继承自google::protobuf::Message
class Person : public ::google::protobuf::Message{
...
};

protocol buffer 编译器为.proto文件中定义的每个枚举类型也有相应定义

//message Person中的PhoneType枚举
enum Person_PhoneType {Person_PhoneType_MOBILE = 0,Person_PhoneType_HOME = 1,Person_PhoneType_WORK = 2
};

protocol buffer 编译器为.proto文件中定义的消息的每个字段生成一套存取器方法

  • 对于 message Person 中的 int32 id = 2,编译器将生成下列方法:

    • ::google::protobuf::int32 id() const;: 返回字段id的值
    • void set_id(::google::protobuf::int32 value); : 设置字段id的值
    • void clear_id():清除字段的值。
  • 对于message Person中的string name = 1;,编译器将生成下列方法(只显示部分):

    • void clear_name();清除字段的值

    • const ::std::string& name() const;返回字段name的值

    • void set_name(const ::std::string& value);设置字段name的值

    • void set_name(const char* value);同上

    • void set_name(const char* value, size_t size);同上

    • ::std::string* mutable_name();

      对于原型为const std::string &name() const的get函数而言,返回的是常量字段,不能对其值进行修改。但是在有一些情况下,对字段进行修改是必要的,所以提供了一个mutable版的get函数,通过获取字段变量的指针,从而达到改变其值的目的。若字段值存在,则直接返回该对象,若不存在则新new 一个。

  • 对于message AddressBook中的repeated Person people = 1; ,编译器将生成下列方法(只显示部分):

    • int people_size() const;返回重复字段中元素个数

    • void clear_people();清空重复字段

    • const ::tutorial::Person& people(int index) const;获取重复字段中指定索引的值

    • ::tutorial::Person* mutable_people(int index);获取重复字段中指定索引处元素值的指针,从而可以改变其值。

    • ::tutorial::Person* add_people();向重复字段中增加一个元素并返回其指针

message关键字声明的的消息体,允许你检查、操作、读、或写该消息内容,还可以序列化生成二进制字符串,以及反序列化二进制字符串。

  • bool SerializeToString(string* output) const;:序列化消息并将字节存储在给定的字符串中。请注意,字节是二进制的,而不是文本;我们只使用string类作为一个方便的容器。
  • bool ParseFromString(const string& data);: 从给定的字符串解析消息。
  • bool SerializeToOstream(ostream* output) const;: 将消息写入给定的 C++ ostream
  • bool ParseFromIstream(istream* input);: 从istream解析消息

每个消息类还包含许多其他方法,可让您检查或操作整个消息,包括:

  • bool IsInitialized() const;: 检查是否所有必填字段都已设置。
  • string DebugString() const;:返回消息的可读字符串表示,对于调试特别有用。
  • void CopyFrom(const Person& from);: 用给定消息的值覆盖消息。
  • void Clear();: 将所有元素清除回空状态。

3. 编译.proto文件

通过protoc编译器根据.proto文件生成C++对应的.h和.cpp文件

protoc -I=$SRC_DIR --cpp_out=$DST_DIR  xxx.proto 
  • $SRC_DIR.proto文件所在的源目录
  • --cpp_out 生成C++代码
  • $DST_DIR 生成代码的目标目录
  • xxx.proto:要针对哪个proto文件生成接口。如tutorial.person.proto

编译完成后,将生成2个文件 tutorial.person.pb.htutorial.person.pb.cc 其中pb是protobuf的缩写。

4. 测试

#include <iostream>
#include <string>
#include "tutorial.person.pb.h"using namespace std;int main() {//构造一个tutorial::AddressBook消息对象tutorial::AddressBook book;//在重复字段people中添加一个Person元素auto p = book.add_people();//设置这个Person的字段值p->set_name("gailun");p->set_id(1);p->set_email("demaxiya@qq.com");for(int i = 0 ; i < 3 ; ++i) {//在Person消息的可重复字段phones中添加一个PhoneNumber元素auto phone_p = p->add_phones();//设置这个PhoneNumber的字段值phone_p->set_number("123456789"+to_string(i));phone_p->set_type(static_cast<tutorial::Person_PhoneType>(i));}string str;//序列化为二进制串并保存在string中if(book.SerializeToString(&str)){cout << "序列化成功!" << endl;//打印消息的可读字符串表示cout << book.DebugString();}//根据二进制字符串反序列化为消息tutorial::AddressBook book1;if(book1.ParseFromString(str)){cout << "反序列化成功" << endl;auto phone_ptr = book1.people(0);//获取可重复字段的第0个元素cout << "name: " << phone_ptr.name() << endl;cout << "id: " << phone_ptr.id() << endl;cout << "email: " << phone_ptr.email() << endl;for(int i = 0 ; i < 3 ; ++i) {cout << "phone: " << phone_ptr.phones(i).number() << endl;;}}
}

运行该程序

# g++ test.cpp tutorial.person.pb.cc -lprotobuf
# ./a.out 
序列化成功!
people {name: "gailun"id: 1email: "demaxiya@qq.com"phones {number: "1234567890"}phones {number: "1234567891"type: HOME}phones {number: "1234567892"type: WORK}
}
反序列化成功
name: gailun
id: 1
email: demaxiya@qq.com
phone: 1234567890
phone: 1234567891
phone: 1234567892

http://chatgpt.dhexx.cn/article/dtYQfidB.shtml

相关文章

protobuf语法详解

文章目录 一、包&#xff08;package&#xff09;二、选项&#xff08;option&#xff09;三、消息类型&#xff08;message&#xff09;3.1、常规消息类型3.1.1、字段修饰符3.1.2、字段类型3.1.2.1、标量类型3.1.2.2、枚举类型3.1.2.3、Any类型3.1.2.4、oneof类型3.1.2.5、map…

ProtoBuf在中C++使用介绍

ProtoBuf 我们先来看看官方文档给出的定义和描述&#xff1a; protocol buffers 是一种语言无关、平台无关、可扩展的序列化结构数据的方法&#xff0c;它可用于&#xff08;数据&#xff09;通信协议、数据存储等。 Protocol Buffers 是一种灵活&#xff0c;高效&#xff0c;自…

linux 安装protobuf

从github&#xff1a;https://github.com/protocolbuffers/protobuf 下载源代码 1、根据protobuf GitHub的README.md安装protoBuf &#xff08;1&#xff09;安装依赖工具 sudo apt-get install autoconf automake libtool curl make g unzip&#xff08;2&#xff09;在prot…

protobuf-master :编译篇

protobuf的编译其实相对简单&#xff0c;这里搬一下protobuf的ReadMe就很清楚了~ 如果是平时接触开源项目无从下手的小伙伴&#xff0c;也建议从阅读ReadMe开始哦! This directory contains CMake files that can be used to build protobuf with MSVC on Windows. You can bui…

ProtoBuf编码原理

背景 Protobuf是我们在网络传输中经常会用到的协议&#xff0c;优点是版本间兼容性强&#xff0c;对数据序列化时的极致压缩使得Protobuf包体积比xml、json等格式要小很多&#xff0c;节约流量。对于pb协议的具体使用方法&#xff0c;其官网有比较详细的说明&#xff0c;本文不…

windows protobuf编译

protobuf编译 Protobuf下载地址&#xff1a;https://github.com/protocolbuffers/protobuf/releases 1、配置cmake: 2、点击生成&#xff0c;打开工程文件&#xff1a;略 3、编译protobuf: 4、安装完成展示&#xff1a; 测试&#xff1a; 1、创建在bin目录下创建build.bat…

【Protobuf】Protobuf协议

Protobuf协议 什么是Protobuf一、编写proto文件二、生成协议类三、编码解码3.1 编码方法3.2 解码方法 什么是Protobuf Protobuf是谷歌发布的一套协议格式&#xff0c;它规定了一系列编码和解码方法。 目前&#xff0c;网上已经有不少实现Protobuf编码解码的库&#xff0c;可以…

protobuf简介

文章目录 一、protobuf的定义二、protobuf的优缺点2.1、优点2.2、缺点 三、protobuf的使用流程3.1、protobuf在Linux下的安装过程3.2、定义proto文件3.3、protoc编译器3.4、调用接口进行序列化、反序列化 四、protobuf的应用场景五、protobuf与json和XML的对比 一、protobuf的定…

java中使用protobuf总结

基本没怎么接触过java编程&#xff0c;别的团队发过来一个用java编写的存储pb的文件&#xff0c;让拆分和解析&#xff0c;硬着头皮做一下&#xff0c;在此将步骤做个记录&#xff1a; 下载安装protobuf https://github.com/protocolbuffers/protobuf/tags?afterv3.6.1.2 编译…

protobuf 详解

protobuf简介 Protobuf是Protocol Buffers的简称&#xff0c;它是Google公司开发的一种数据描述语言&#xff0c;是一种轻便高效的结构化数据存储格式&#xff0c;可以用于结构化数据串行化&#xff0c;或者说序列化 。它很适合做数据存储或 RPC 数据交换格式。可用于通讯协议…

protobuf介绍和语法

目录 前言 语法 标识符 字段 字段类型 proto2和proto3区别 前言 Protobuf即Protocol Buffers&#xff0c;是Google公司开发的一种跨语言和平台的序列化数据结构的方式&#xff0c;是一个灵活的、高效的用于序列化数据的协议。 与XML和JSON格式相比&#xff0c;pr…

Protobuf:一种更小、更快、更高效的协议

C/CLinux服务器开发/后台架构师知识体系 Protobuf介绍 Protobuf (Protocol Buffers) 是谷歌开发的一款无关平台&#xff0c;无关语言&#xff0c;可扩展&#xff0c;轻量级高效的序列化结构的数据格式&#xff0c;用于将自定义数据结构序列化成字节流&#xff0c;和将字节流反…

win10商店打不开_win10应用商店闪退是咋回事呢

win10虽然具有闪电般的开机速度&#xff0c;并且还新增了很多功能。但比较是全新的操作系统&#xff0c;所以难免会存在一些故障&#xff0c;这里小编就给大家讲讲win10应用商店闪退打不开怎么解决。 方法一 1&#xff0c;首先&#xff0c;打开开始菜单&#xff0c;进入设置&am…

电脑安装Linux闪退,win10系统运行内置Linux系统闪退如何处理

我们在win10系统电脑的使用中&#xff0c;有小伙伴在Linux系统的使用中出现了问题&#xff0c; win10系统运行内置Linux系统闪退的情况出现了&#xff0c;这是什么原因导致的呢&#xff0c;我们在win10系统运行内置Linux系统闪退如何处理&#xff0c;今天小编就来跟大家分享一下…

Java版mc闪退_本文传授win10运行mc闪退的具体操作对策

我们在使用电脑的时候遇到了win10运行mc闪退问题确实比较难受&#xff0c;要是你的电脑技术没有达到一定的水平&#xff0c;可能就不能解决这个win10运行mc闪退的情况。我们应当如何处理这个问题呢&#xff1f;小编先给大伙说说简单的措施&#xff1a;1、确保电脑中安装了 .NET…

(2022.5.27)【Win10】Windows10重置后微软商店闪退打不开、图片闪退打不开、UWP应用闪退打不开——可能的解决方案

更新日志 20220609 增加注意事项 注意事项 经过多为网友的反馈&#xff0c;目前这个方法是无法直接解决微软商店打不开的问题。因此&#xff0c;基于我目前的了解&#xff08;6月9日&#xff09;&#xff0c;如果大家遇到这个问题&#xff0c;真的只能重新 U 盘安装系统了。…

win10内置计算机和天气闪退,win10系统中天气闪退怎么办?Win10天气应用闪退问题解决方法...

win10系统中天气闪退怎么办&#xff1f;最近有部分用户在安装了win10系统后发现自带的天气应用出现闪退的情况&#xff0c;点击天气应用&#xff0c;发现它启动了很久&#xff0c;然后就自动关闭了。之后再点击天气应用就闪退&#xff0c;打不开。而尝试打开别的应用却可以正常…

解决WIN10下应用商店不能用,闪退的情况

解决WIN10下应用商店不能用,闪退的情况 先说下我的情况,也是博主手贱,经常看PC上的某个文件或者程序不顺眼的话就会想办法把它干掉,为此重装过几次系统… 这一次是装了win10的周年更新后,烦人的cortana,onedrive等一些我不想要的APP又回来了,在暴力清理这些APP的时候,需要特殊…

win10java闪退怎么办_Win10应用打不开或闪退怎么办?解决方案在此

可能有一些用户升级Win10之后遇到了应用商店、应用打不开或闪退的问题&#xff0c;此时可尝试通过下面的一些方法来解决。 1、点击任务栏的搜索(Cortana小娜)图标&#xff0c;输入Powershell&#xff0c;在搜索结果中右键单击Powershell&#xff0c;选择“以管理员身份运行”。…

win10的c语言程序闪退,Win10专业版软件打不开闪退怎么办?

现在用到最多的Win10系统是Win10专业版&#xff0c;用户重装Win10专业版系统的目的就是为了解决电脑遇到的问题&#xff0c;然而重装系统后还是会出现许许多多的问题&#xff0c;比如说部分软件打不开了&#xff0c;闪退的问题。如果您也遇到了相同的问题&#xff0c;下面就是小…