当前位置: 首页 > 工具软件 > Proto.Menu > 使用案例 >

ProtoBuf

彭骏
2023-12-01

ProtoBuf简述

protocol buffers 是一种语言无关、平台无关、可扩展的序列化结构数据的方法,可用于通信协议、数据存储等。

使用流程:

  1. 创建 .proto 文件,定义数据结构
  2. 编译 .proto文件生成接口
  3. 调用接口实现序列化、反序列化以及读写

安装

不同版本的protobuf无法互相兼容,ROS安装时会一并安装,路径为/usr/bin/protoc,若需要依赖其他版本可安装在不同路径下

  1. 安装依赖 sudo apt-get install autoconf automake libtool curl make g++

  2. 生成Makefile ./autogen.sh && ./configure

    如需多版本并存或修改安装路径,可设置目录/configure --prefix=/usr/protobuf

  3. 检查编译结果 make check

  4. 安装到系统路径下 sudo make install

  5. 设置系统环境

    单版本安装

    Ubuntu默认安装在/usr/local/lib目录下

    • 创建文件 sudo gedit /etc/ld.so.conf.d/libprotobuf.conf
    • 填入内容 /usr/local/lib(若修改了路径,使用修改过的)
    • 更新 sudo ldconfig

    多版本安装

    • 创建软链接 sudo ln -s /usr/protobuf/bin/protoc /usr/local/bin/protoc3.6
    • 更新sudo ldconfig
  6. 测试 protoc --version

ProtoBuf语法

  • 定义结构体

    • message <name> {}
    • 结构体语法 : 字段规则 类型 名称 = 字段编号
    message Person {
      string name = 1;
      int32 id = 2;
      string email = 3;
      }
    
  • 定义枚举

    • enum <name> {}
    • 枚举语法 : 名称 = 枚举编号
    • 枚举常量值必须在32位整数范围内,负值无效
      enum PhoneType {
        MOBILE = 0;
        HOME = 1;
        WORK = 2;
      }
    
  • 字段规则

    注意:proto3已舍弃required字段,optional字段也无法显示使用(因为缺省默认就设置为optional),同时弃用默认值设置,默认值大多为0、空、false。

    • proto2
      • [default = XXX] 设置默认值
      • required 字段只能也必须出现1次
      • optional 字段可出现0次或1次
      • repeated 字段可出现任意多次(包括 0),[packed = true]选项可提高编码效率
    • proto3
      • singular 字段可出现0个或1个该字段。这是 proto3 语法的默认字段规则,一般无需声明
      • repeated 字段可出现任意多次(包括 0),可视为动态大小的数组
    • reserved 字段设置为保留项,主要用于已经删除字段的禁用
  • 字段类型

    protobuf属性C++属性备注
    boolbool
    stringstring一个字符串必须是utf-8编码或者7-bit的ascii编码的文本,长度不能超过 2^32
    bytesstring可能包含任意顺序的字节数据,但长度不能超过 2^32
    doubledouble固定8个字节
    floatfloat固定4个字节
    int32int32使用变长编码,对于负数编码效率较低,如果经常使用负数,建议使用sint32
    int64int64使用变长编码,对于负数编码效率较低,如果经常使用负数,建议使用sint64
    uint32uint32使用变长编码
    uint64uint64使用变长编码
    sint32int32使用变长编码,符号整型。负值的编码效率高于常规的 int32 类型
    sint64int64使用变长编码,符号整型。负值的编码效率高于常规的 int64 类型
    fixed32uint32定长4字节,如果数据>2^28,编码效率高于unit32
    fixed64uint64定长8字节,如果数据>2^56,编码效率高于unit64
    sfixed32int32总是4字节
    sfixed64int64总是8字节
  • 字段编号

    • 0 ~ 536870911(除去 19000 到 19999 之间的数字)
    • 标签号1-15比起更大数字需要少一个字节进行编码,可将常用或重复的元素设置为此类标签

ProtoBuf生成

通过命令

protoc -I=<SRC_DIR> --cpp_out=<DST_DIR> <SRC_DIR>/xxx.proto

  • SRC_DIR: .proto所在的源目录
  • –cpp_out: 生成c++代码
  • –python_out:生成python代码,_pb2.py文件
  • DST_DIR: 生成代码的目标目录
  • xxx.proto: 要针对哪个proto文件生成接口代码
# 寻找 Protobuf 库
find_package(Protobuf REQUIRED)

# 生成protobuf的lib
file(GLOB PROTO_LIST "proto/*.cc")
add_library(proto
  STATIC
    ${PROTO_LIST})
target_include_directories(proto PUBLIC ${PROTOBUF_INCLUDE_DIRS})
target_link_libraries(proto PUBLIC ${PROTOBUF_LIBRARIES})

# 链接执行文件
add_executable(protobuf_test 
    src/protobuf_test.cpp)
target_include_directories(protobuf_test PUBLIC ${PROJECT_SOURCE_DIR}/proto)
target_link_libraries(protobuf_test proto)

通过CMake

自动生成对应的C++头文件和源文件

protobuf_generate_cpp (<SRCS> <HDRS>
    [DESCRIPTORS <DESC>] [EXPORT_MACRO <MACRO>] [<ARGN>...])
  • SRCS Variable to define with autogenerated source files
  • HDRS Variable to define with autogenerated header files
  • DESCRIPTORS Variable to define with autogenerated descriptor files, if requested.
  • EXPORT_MACRO is a macro which should expand to __declspec(dllexport) or __declspec(dllimport) depending on what is being compiled.
  • ARGN .proto files

protobuf消息编译库proto,后续调用直接使用该库即可。

project(proto)

# 寻找 Protobuf 库
find_package(Protobuf REQUIRED)

# 检测是否找到 Protobuf
if(PROTOBUF_FOUND)
  message("** protobuf found")
else()
  message(FATAL_ERROR "Cannot find Protobuf")
endif()

# 寻找所有的proto文件
file(GLOB PROTO_FILES ${PROJECT_SOURCE_DIR}/*.proto)
message("** PROTO_FILES = ${PROTO_FILES}")
# Generate the .h and .cxx files 必须配合 add_executable() 或者 add_library() 才能正常使用
protobuf_generate_cpp(PROTOBUF_SRCS PROTOBUF_HDRS ${PROTO_FILES})

# 增加 proto 描述文件的静态库
add_library(${PROJECT_NAME} STATIC ${PROTOBUF_SRCS} ${PROTOBUF_HDRS})
target_include_directories(${PROJECT_NAME} PUBLIC ${PROTOBUF_INCLUDE_DIRS})
target_link_libraries(${PROJECT_NAME} PUBLIC ${PROTOBUF_LIBRARIES})

接口调用

构造protobuf数据

在生成的C++接口内,会根据设置数据类型生成对应的函数接口,singular(单组数据)和repeated(多组数据)定义的类型生成的api存在差异。

singular

  • double/int

    // double x = 1;
    void clear_x();
    static const int kXFieldNumber = 1;
    double x() const;
    void set_x(double value);
    
    // int32 id = 2;
    void clear_id();
    static const int kIdFieldNumber = 2;
    ::google::protobuf::int32 id() const;
    void set_id(::google::protobuf::int32 value);
    
  • bytes/string

    在C++中bytes和string的实现都是std::string类型,区别在于string类型序列化调用了VerifyUTF8StringNamedField函数检验string中是否有非法的UTF-8字符,而bytes类型没有检验。

    string类型不能直接通过set赋值,需设置string变量后,将string变量通过set进行赋值。

    // bytes data = 1;  
    void clear_data();
    static const int kDataFieldNumber = 1;
    const ::std::string& data() const;
    void set_data(const ::std::string& value);
    #if LANG_CXX11
    void set_data(::std::string&& value);
    #endif
    void set_data(const char* value);
    void set_data(const void* value, size_t size);
    ::std::string* mutable_data();
    ::std::string* release_data();
    void set_allocated_data(::std::string* data);
    
  • 嵌套message

    // .test.test_2_msg c = 3;
    bool has_c() const;
    void clear_c();
    static const int kCFieldNumber = 3;
    const ::test::test_2_msg& c() const;
    ::test::test_2_msg* release_c();
    ::test::test_2_msg* mutable_c();
    void set_allocated_c(::test::test_2_msg* c);
    

    通过set_allocated_mutable_传入已定义值,在关闭程序时会引起的double free core dump,参考c++ Protobuf中set_allocated引起的double free core dump

    正确定义:

    int good_case1(){
        a* aa = new a();
        b bb;
        aa->set_aa(1);
        bb.set_allocated_aaa(aa);
        return 0;
    }
    
    void good_case2(){
        a aa;
        b bb;
        aa.set_aa(1);
        bb.mutable_aaa()->MergeFrom(aa);
    }
    
  • descriptor

    #include <google/protobuf/message.h>
    namespace google::protobuf
    
    const Descriptor* descriptor = msg->GetDescriptor();
    

repeated

  • double/int

     // repeated int32 e = 5;
     int e_size() const;
     void clear_e();
     static const int kEFieldNumber = 5;
     ::google::protobuf::int32 e(int index) const;
     void set_e(int index, ::google::protobuf::int32 value);
     void add_e(::google::protobuf::int32 value);
     const ::google::protobuf::RepeatedField< ::google::protobuf::int32 >&
         e() const;
     ::google::protobuf::RepeatedField< ::google::protobuf::int32 >*
         mutable_e();
    
  • 嵌套message

      // repeated .test.test_2_msg d = 4;
      int d_size() const;
      void clear_d();
      static const int kDFieldNumber = 4;
      ::test::test_2_msg* mutable_d(int index);
      ::google::protobuf::RepeatedPtrField< ::test::test_2_msg >*
          mutable_d();
      const ::test::test_2_msg& d(int index) const;
      ::test::test_2_msg* add_d();
      const ::google::protobuf::RepeatedPtrField< ::test::test_2_msg >&
          d() const;
    

序列化和反序列化

调用接口实现序列化、反序列化以及读写

  • 序列化

    • bool SerializeToString(string* output) const

      序列化消息并将字节存储在给定的字符串中。请注意,字节是二进制的,而不是文本,只是使用 string 类作为容器。

      文件中显示为乱码,通过gedit修改会损坏数据,可以通过vim修改。

    • bool SerializeToOstream(ostream* output) const

      将message写入给定的C++的ostream

  • 反序列化

    • bool ParseFromString(const string& data)

      解析给定字符串到message

    • bool ParseFromIstream(istream* input)

      解析给定C++ istream到message

参考

[翻译] ProtoBuf 官方文档(一)- 开发者指南

语言指导(proto3)

[翻译] ProtoBuf 官方文档(九)- (C++开发)教程

Protocol Buffer Basics: C++

CMake 生成 ProtoBuf

[转]Protobuf3 语法指南

Ubuntu18.04同时安装两个版本的protobuf(protoc)

 类似资料: