protobuf安装、使用

发布时间 2023-04-16 10:32:08作者: WuYunTaXue

介绍

protobuf是用来对数据进行序列化和反序列化的灵活,高效,自动化的解决方案。

序列化:将数据结构转换成二进制的字节串
反序列化:将二进制串还原成数据结构

Ubuntu下编译安装

尝试安装最新版本-v3.22.1(没成功)

参照文档的安装过程
github-protocol-readme

这里在Linux下使用cmake构建,找到readm中指示的cmake的readme.md
protocol-cmake-readme

#下载源码和子模块
$ git clone https://github.com/protocolbuffers/protobuf.git
$ cd protobuf
$ git submodule update --init --recursive

#在源码路径下开始编译
$ cmake .
$ cmake --build . --parallel 10

#安装
$ sudo make install

使用:

编译时一直报错:asbl::相关的undefined,没有找到原因。
也许不应该编译submodule中的abseil-cpp模块

换版本安装-v3.20.1

网友们使用的版本大都是带初始化脚本然后再编译,我也尝试下载之前的版本进行编译

去proto git的release界面下载了v3.20.1版本源码
将之前版本残留的文件删除,默认在/usr/local/的include\lib\bin目录下

$ ./autogen.sh
报错
./autogen.sh: 41: autoreconf: not found
安装前置
$ sudo apt-get install autoconf automake libtool

##
$ ./autogen.sh
$ ./configure --prefix=/usr/local/comenv/protobuf

$ make -j4
$ sudo make install
#可以看到在指定路径下生成了lib\bin\include三个目录

#配置系统环境 or 配置工程使用环境
$ sudo vi /etc/profile

#添加:
export PATH=$PATH:/usr/local/comenv/protobuf/bin/
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/comenv/protobuf/lib
export LIBRARY_PATH=$LIBRARY_PATH:/usr/local/comenv/protobuf/lib
export CPLUS_INCLUDE_PATH=$CPLUS_INCLUDE_PATH:/usr/local/comenv/protobuf/include
export PKG_CONFIG_PATH=/usr/local/comenv/protobuf/lib/pkgconfig

#PATH                 可执行文件查找目录
#LD_LIBRARY_PATH      动态链接器(ld)查找动态库的路径,即执行期间的库文件查找路径
#LIBRARY_PATH         编译期间库查找路径
#CPLUS_INCLUDE_PATH   g++头文件查找路径
#PKG_CONFIG_PATH      pc文件查找路径

# 或者不修改环境,只修改自己工程的查找路径
# 使用时也可以只修改工程编译选项的库、头文件路径,只是在代码IDE里就没有提示、跳转识别等

$ source /etc/comenv

参考
csdn - wangicter

or

命令行安装-没试过

sudo apt insatll protobuf-compiler libprotobuf-dev

验证

$ protoc --version
libprotoc 3.20.1

使用

一般步骤

以C++为例

  • 创建proto文件,定义数据格式等
  • 使用protoc工具,将proto文件编译生成一个.h和.cc文件,将源\头文件拷贝至工程使用
  • 使用protobuf的接口,实现序列号、反序列化

protoc编译工具

$ protoc --cpp_out=./xxx/ ./xxx.proto
#--cpp_out表示将proto文件编译出c++使用的.cc和.h,并指定文件输出的路径
#最后一个参数是proto文件路径
#编译之后,程序包含生成的cc和h文件即可使用

proto文件

syntax = "proto3";

package SCH; //整个文件的包名,对应到C++里是这个文件所有的message类都在这个命名空间里

message Student{
  //选项 类型 名称 = 标号
  uint64 id = 1;
  string name =2;
  optional string email = 3;

  enum PhoneType {
      MOBILE = 0;
      HOME = 1;
  }

  message PhoneNumber {
      string number = 1;
      optional PhoneType type = 2;
  }
  repeated PhoneNumber phone = 4;

  message Remark { 
    string describe = 1;
    int32  rank = 2;
  }

  Remark remark = 5;
}
  • syntax 使用的proto的版本,2和3有一些语法差别,不写默认为版本2

  • package 防止不同项目之间的命名冲突。对应到C++中,这个文件生成的类将被放置在一个与package名相同的命名空间中。

  • message 消息结构定义,一个message就是一些字段的集合,字段可以是bool\int32\double\string等基础类型,也可以是其他message类型。这里Student和PhoneNumber都会在程序中作为类名。

    • message中,字段的格式为:修饰符 类型 名称 = 标识;
  • 修饰符 message中的每个字段都有一个修饰符(可以缺省)

    • optional 可选的,加入这个标识,会生成has_xx的接口
    • repeated 可重复的(可以理解为不定长的数组)
  • 标识 该字段在二进制编码中使用的唯一“标识(tag)”。标识号1-15编码所需的字节数比更大的标识号使用的字节数要少1个,所以,如果你想寻求优化,可以为经常使用或者重复的项采用1-15的标识(tag),其他经常使用的optional项采用≥16的标识(tag)。在重复的字段中,每一项都要求重编码标识号(tag number),所以重复的字段特别适用于这种优化情况。

调用接口

查看头文件中字段生成的接口:

//这里省略了很多
// uint64 id = 1;
void Student::clear_id();
uint64_t Student::id();
void Student::set_id(uint64_t value);

// string name = 2;
inline void Student::clear_name();
inline const std::string& Student::name();
template <typename ArgT0, typename... ArgT>
inline void Student::set_name(ArgT0&& arg0, ArgT... args);
inline std::string* Student::mutable_name();
inline std::string* Student::release_name();

// repeated .SCH.Student.PhoneNumber phone = 4;
inline int Student::phone_size()
inline void Student::clear_phone()
inline ::SCH::Student_PhoneNumber* Student::mutable_phone(int index)
inline ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::SCH::Student_PhoneNumber >* Student::mutable_phone()
inline const ::SCH::Student_PhoneNumber& Student::phone(int index)
inline ::SCH::Student_PhoneNumber* Student::add_phone()
inline const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::SCH::Student_PhoneNumber >& Student::phone()
  • 普通字段都有set_xxx、clear_xxx和xxx的接口,xxx是定义的字段名称,set_id()就是给id字段赋值,id()就是获取id字段的值
  • string字段比普通字段多了mutable_xxx的接口,nutable_name()返回name字段buf的指针,为空时调用这个接口会初始化一个空字符串
  • 可重复字段,多了xxx_size和add_xxx接口,取消了set_xxx接口。并且xxx()和mutable_xxx接口有了重载,可以根据index索引来获取/设置指定位置的内容。add_phone接口返回一个初始化后的字段指针,并添加到phone buf中,利用这个指针进行赋值。

标准接口

string DebugString() const; //将消息内容以可读的方式输出

bool SerializeToString(string* output) const; //将消息序列化并储存在指定的string中。注意里面的内容是二进制的,而不是文本;我们只是使用string作为一个很方便的容器。

bool ParseFromString(const string& data); //从给定的string解析消息。

bool SerializeToArray(void * data, int size) const  //将消息序列化至数组

bool ParseFromArray(const void * data, int size)    //从数组解析消息

bool SerializeToOstream(ostream* output) const; //将消息写入到给定的C++ ostream中。

bool ParseFromIstream(istream* input); //从给定的C++ istream解析消息。

序列号、反序列化示例

#include <iostream>
#include <string>
#include "hello.pb.h"

//序列化
int serialize(std::string &resStr) {
    SCH::Student stu;

    //向对象中填值
    stu.set_id(23456);   //普通成员会有set函数来赋值
    stu.set_name("stu1");
    *stu.mutable_email() = "hello@world.com";  //string成员会多一些接口,mutable会返回指针

    //嵌套的重复成员:
    SCH::Student::PhoneNumber* pNum1 = stu.add_phone();
    pNum1->set_number("1812435454");
    pNum1->set_type( SCH::Student::MOBILE);

    SCH::Student::PhoneNumber* pNum2 = stu.add_phone();
    pNum2->set_number("1234567");
    pNum2->set_type( SCH::Student::HOME);

    //嵌套的单次成员
    stu.mutable_remark()->set_describe("ok");
    stu.mutable_remark()->set_rank(1);

    //将stu序列化到string中,string中数据已经是二进制的格式,string只是它的容器
    stu.SerializePartialToString(&resStr);

    std::cout << "Serialize, debug string: \n" << stu.DebugString() << std::endl;  //按照结构输出
    return 0;
}

//反序列化
int deserialize(std::string &str) {
    SCH::Student stu;
    if ( false == stu.ParseFromString(str) ) {
        std::cout << "parse failed" << std::endl;
        return -1;
    }

    std::cout << "Deserialize, debug string: \n" << stu.DebugString() << std::endl;
    std::cout << "-------------" << std::endl;


    if (stu.has_email()) {  //设置为optional的字段才会有这个接口
        std::cout << "email: " << stu.email() << std::endl;
    }
    std::cout << "id: " << stu.id() << std::endl;

    for (int i=0; i<stu.phone_size(); i++) {
        SCH::Student::PhoneNumber pnum = stu.phone(i);
        pnum.has_type();

        std::cout << "phone: " << pnum.type() << "-" << pnum.number() << std::endl;
    }
    return 0;
}

int main() {
    std::string SerializeStr;

    serialize( SerializeStr);
    std::cout << "----------" << std::endl;
    deserialize(SerializeStr);

    return 0;
}

编译选项需要链接:
-lprotobuf -lpthread

参考
腾讯云 - Dabelv
csdn - freshman94