【3rd Party】Cpp 中使用 Protobuf

发布时间 2023-09-08 13:27:00作者: Koshkaaa

前置条件:

ProtoBuf 的定义和描述

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

Protocol Buffers 是一种灵活,高效,自动化机制的结构数据序列化方法-可类比 XML,但是比 XML 更小(3 ~ 10倍)、更快(20 ~ 100倍)、更为简单。

怎么感觉到处都在对比 XML 的速度慢啊...

你可以定义数据的结构,然后使用特殊生成的源代码轻松的在各种数据流中使用各种语言进行编写和读取结构数据。你甚至可以更新数据结构,而不破坏由旧数据结构编译的已部署程序。

使用 ProtoBuf教程:

对 ProtoBuf 的基本概念有了一定了解之后,我们来看看具体该如何使用 ProtoBuf。

第一步,创建 .proto 文件,定义数据结构,如下所示:

//person.proto
package test;

message Person {
	required string name = 1;
	required int32 id = 2;
	required string email=3;
}

我们在上例中定义了一个名为 Person的消息,语法很简单,message 关键字后跟上消息名称。之后我们在其中定义了 message 具有的字段,形式为:

message xxx {
  // 字段规则:required -> 字段只能也必须出现 1 次
  // 字段规则:optional -> 字段可出现 0 次或1次
  // 字段规则:repeated -> 字段可出现任意多次(包括 0)
  // 类型:int32、int64、sint32、sint64、string、32-bit ....
  // 字段编号:0 ~ 536870911(除去 19000 到 19999 之间的数字)
  字段规则 类型 名称 = 字段编号;
}

第二步,protoc 编译 .proto 文件生成读写接口:

.proto 文件中定义了数据结构,这些数据结构是面向开发者和业务程序的,并不面向存储和传输。当需要把这些数据进行存储或传输时,就需要将这些结构数据进行序列化、反序列化以及读写。ProtoBuf 提供相应的接口代码,可以通过 protoc 这个编译器来生成相应的接口代码,命令如下:

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

生成的.h,.cpp文件为person.pb.h,person.pb.cpp,且.h的定义与proto文件的内容相关联:

namespace test { // 对应 package test;
 
class Person : public ::google::protobuf::Message { //对应 message Person 且继承自::google::protobuf::Message
public:
  
  inline void set_name(const ::std::string& value); //对应message的字段内容
  inline void set_email(const ::std::string& value);
  inline void set_id(::google::protobuf::int32 value);
  ...
}

第三步,编写C++业务代码:

// Person_Test.cpp
#include <iostream>
#include <fstream>
#include <google/protobuf/io/zero_copy_stream_impl.h>
#include <google/protobuf/text_format.h>
#include <fmt/core.h>
#include "person.pb.h"

using namespace test;

int main() {
    // 构造 Person 并填充信息
    Person p;
    p.set_name("Koshkaaa");
    p.set_id(100);
    p.set_email("kannajiahe@gmail.com");

    // 将 pb 二进制信息保存到字符串, 序列化
    std::string str;
    p.SerializeToString(&str);
    fmt::print("str: [ {} ]\n", str);

    // 将 pb 文件信息写入文件
    std::ofstream  fw;
    fw.open("../Person.txt", std::ios::out | std::ios::binary);
    auto *out = new google::protobuf::io::OstreamOutputStream(&fw);
    google::protobuf::TextFormat::Print(p, out);

    delete out;
    fw.close();

    // 将 pb 文本信息保存到字符串
    std::string str1;
    google::protobuf::TextFormat::PrintToString(p, &str1);
    fmt::print("str1: [ {} ]\n", str1);

    // 反序列化
    Person p1;
    p1.ParseFromString(str);
    fmt::print("Person1: name {0}, email {1}, id {2}", p1.name(), p1.email(), p1.id());

    return 0;
}

Cpp 业务代码对应的 CMakeList.txt :

# 使用 VcPkg
# ./vcpkg install protobuf:[special-version]
cmake_minimum_required(VERSION 3.24)
project(protobuf_tutorial)

set(CMAKE_CXX_STANDARD 17)

find_package(protobuf CONFIG REQUIRED)
find_package(fmt CONFIG REQUIRED)

add_executable(protobuf_tutorial main.cpp
        person.pb.cc)

target_link_libraries(
        protobuf_tutorial
        PRIVATE
        protobuf::libprotoc protobuf::libprotobuf protobuf::libprotobuf-lite
        fmt::fmt
)

另一种可供的参考

# 未使用 VcPkg
cmake_minimum_required(VERSION 3.24)
project(protobuf_tutorial)
set(CMAKE_CXX_FLAGS "-std=c++11  ${CMAKE_CXX_FLAGS}")

find_package(Protobuf REQUIRED)
include_directories(
  include
  ${PROTOBUF_INCLUDE_DIRS}
)
add_library(addressbook_protobuf person.pb.cc)
add_executable(test_person test_person.cpp)
target_link_libraries(
    test_person 
    addressbook_protobuf
    ${PROTOBUF_LIBRARIES}
)