Protobuf

Protobuf序列化的步骤

protobuf中的数据类型 和 C++ 数据类型对照:

image-20250313100438695

使用protobuf进行序列化的步骤:

  • 定义.proto文件: .proto文件来描述要序列化的数据结构
1
2
3
4
5
6
7
8
//声明所使用的protobuf版本号
syntax = "proto3";

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

注:等号后面的编号要从1开始,每个成员都有一个唯一的编号,不能重复,一般连续编号即可。

  • 编译.proto文件:使用protoc编译器将user_info.proto文件编译成C++代码
1
protoc -I path <user_info.proto> --cpp_out=输出路径(存储生成的c++文件)

注1:-I: 参数后面可以跟随一个或多个路径,用于告诉编译器在哪些路径下查找导入的文件或依赖的文件,如protoc -I path1 -I path2protoc -I path1:path2

注2:这会在输出路径下生成user_info.pb.huser_info.pb.cc两个文件。

  • 在C++项目中集成:

在项目的源文件中包含生成的头文件:#include "user_info.pb.h"

  • 序列化和反序列化:

    • 序列化:创建一个UserInfo对象并设置其字段值,然后调用SerializeToArraySerializeToString方法将其序列化为字节流。

      1
      2
      3
      4
      5
      6
      7
      8
      UserInfo user;
      user.set_id(1);
      user.set_name("John Doe");
      user.set_email("johndoe@example.com");

      std::string data;
      //接受一个std::string类型的引用作为输出参数
      user.SerializeToString(&data);
    • 反序列化:创建一个UserInfo对象,并通过调用ParseFromArrayParseFromString方法从字节流中恢复数据。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      UserInfo user;
      if (user.ParseFromString(data)) {
      // 成功解析后,可以访问user的字段了
      std::cout << "ID: " << user.id() << std::endl;
      std::cout << "Name: " << user.name() << std::endl;
      std::cout << "Email: " << user.email() << std::endl;
      } else {
      // 解析失败
      }

repeated 限定修饰符

repeated 用于定义可重复字段的关键字,与数组或列表概念相似。

eg:

  • 定义 .proto 文件:
1
2
3
4
5
6
7
syntax = "proto3";

message Book {
string title = 1;
repeated string authors = 2;
int32 year_published = 3;
}
  • 使用生成的类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
#include "book.pb.h" // 假设生成的文件名为book.pb.h

void AddBookInfo(Book &book) {
book.set_title("Example Book Title");
book.add_authors("Author One");
book.add_authors("Author Two");
book.set_year_published(2025);
}

int main() {
Book book;

// 添加书籍信息
AddBookInfo(book);

// 访问并打印书籍信息
std::cout << "Title: " << book.title() << std::endl;
std::cout << "Year Published: " << book.year_published() << std::endl;
std::cout << "Authors:" << std::endl;
for (int i = 0; i < book.authors_size(); ++i) {
std::cout << " - " << book.authors(i) << std::endl;
}
return 0;
}

枚举

c++中的枚举类型:

1
2
3
4
5
6
7
enum Color
{
Red = 5, // 可以不给初始值, 默认为0
Green,
Yellow,
Blue
};

.proto 文件中:

1
2
3
4
5
6
7
enum Color
{
Red = 0;
Green = 3; // 第一个元素以外的元素值可以随意指定
Yellow = 6;
Blue = 9;
}

注:第一个元素的元素值必须为0,元素之间使用;

Protobuf中包的概念

Protobuf使用包的概念避免消息类型之间的命名冲突

eg:

定义 .proto 文件:

  • user_info.proto:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    syntax = "proto3";

    package user.profile;

    message UserInfo {
    string name = 1;
    int32 id = 2;
    string email = 3;
    }
  • order_info.proto:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    syntax = "proto3";

    package order.details;

    message OrderInfo {
    string product_name = 1;
    int32 order_id = 2;
    string customer_email = 3;
    }

在C++中使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#include "user_info.pb.h"
#include "order_info.pb.h"

int main() {
// 使用user.profile.UserInfo(需要指定命名空间)
user::profile::UserInfo user;
user.set_name("John Doe");
user.set_id(1234);
user.set_email("johndoe@example.com");

std::cout << "User Info:" << std::endl;
std::cout << "Name: " << user.name() << std::endl;
std::cout << "ID: " << user.id() << std::endl;
std::cout << "Email: " << user.email() << std::endl;

// 使用order.details.OrderInfo
order::details::OrderInfo order;
order.set_product_name("Example Product");
order.set_order_id(5678);
order.set_customer_email("customer@example.com");

std::cout << "\nOrder Info:" << std::endl;
std::cout << "Product Name: " << order.product_name() << std::endl;
std::cout << "Order ID: " << order.order_id() << std::endl;
std::cout << "Customer Email: " << order.customer_email() << std::endl;

return 0;
}