为了账号安全,请及时绑定邮箱和手机立即绑定

Protobuf原理详解及入门教程

概述

Protobuf原理详解介绍了Google开发的一种高性能、可扩展的序列化结构化数据方式,通过简单的定义文件可以生成多种语言的代码。文章深入探讨了Protobuf的优点、应用场景、文件定义及编译过程,并展示了消息序列化与反序列化的示例。

Protobuf原理详解及入门教程
Protobuf简介

Protobuf是什么

Protocol Buffers(简称 Protobuf)是由 Google 开发的一种语言中立、平台中立、可扩展的序列化结构化数据的方式。它是一种二进制的序列化格式,用于在不同平台之间传输和保存结构化数据。通过使用简单的定义文件(通常以 .proto 为扩展名),可以生成各种语言的代码,这些代码可以在序列化和反序列化数据时使用。

Protobuf的优点

  1. 高效性:Protobuf 的序列化格式非常紧凑,相比于 XML 和 JSON,它占用更少的空间且序列化速度快。
  2. 易用性:通过定义文件可以非常方便地定义消息结构,并且生成相应的语言绑定代码,无需手动编写序列化和反序列化代码。
  3. 平台无关性:生成的代码可以在多种平台和语言上运行,包括 C++、Java、Python、Go 等。
  4. 可扩展性:如果需要修改消息结构,只需在 .proto 文件中添加新的字段,而不需要对已有的代码进行修改。
  5. 版本控制:Protobuf 支持版本控制,可以在不影响旧版本代码的情况下添加新字段或修改字段。

Protobuf的应用场景

  1. 跨平台数据交换:在不同的系统之间传输数据时,Protobuf 的紧凑格式和可移植性使其成为理想的选择。
    2.. 持久化存储:将数据持久化到磁盘或其他存储介质时,Protobuf 提供了一种紧凑且高效的存储方式。
  2. 网络通信:在客户端与服务器之间的通信中,Protobuf 可以替代 XML 和 JSON,以减少带宽消耗和提高传输速度。
  3. 构建微服务:在微服务架构中,Protobuf 可以用于定义服务间通信的数据格式,简化服务间通信的实现。
Protobuf文件定义

定义消息类型

消息类型是 Protobuf 序列化的基础。通过 .proto 文件定义数据结构,生成相应的序列化和反序列化代码。定义一个简单的消息类型如下:

message Address {
  required string street = 1;
  optional string city = 2;
  optional string state = 3;
  optional string zip = 4;
}

该定义表示一个 Address 消息类型,包含四个字段:street(必填),citystatezip(可选)。

定义字段类型

.proto 文件中,可以定义不同类型的字段。常用的字段类型包括:

  1. 基本类型
    • int32, int64, uint32, uint64, sint32, sint64, fixed32, fixed64, sfixed32, sfixed64
    • float, double
    • bool
    • string
  2. 复杂类型
    • message: 自定义的消息类型
    • enum: 枚举类型
  3. 特殊类型
    • bytes: 用于表示二进制数据

每个字段都有一个唯一标号,用于在序列化时标识字段的位置。字段可以有以下修饰符:

  • required: 字段是必需的,序列化时必须包含该字段。
  • optional: 字段是可选的,序列化时可以不包含该字段。
  • repeated: 字段可以重复多次,序列化时可以包含多个值。

示例代码解析

定义一个简单的用户信息结构:

message UserInfo {
  required string name = 1;
  optional string email = 2;
  repeated string phone = 3;
}

该定义表示一个 UserInfo 消息类型,包含三个字段:name(必填),email(可选),phone(可重复)。

Protobuf编译过程

编译工具protobuf编译器介绍

protobuf 编译器 protoc 是用于处理 .proto 文件的工具,可以生成各种语言的代码。安装 protoc 的步骤如下:

  1. 下载对应语言的 protoc 编译器并解压。
  2. 将解压后的 protoc 二进制文件添加到系统的 PATH 环境变量中。

例如,安装 C++ 版本的 protoc 编译器:

wget https://github.com/protocolbuffers/protobuf/releases/download/v3.21.1/protoc-3.21.1-linux-x86_64.zip
unzip protoc-3.21.1-linux-x86_64.zip
export PATH=<path_to_protoc>/bin:$PATH

编译过程详解

使用 protoc 编译器将 .proto 文件编译生成相应的代码。编译命令如下:

protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/addressbook.proto

该命令将 $SRC_DIR 下的 addressbook.proto 文件编译生成 C++ 代码,并输出到 $DST_DIR 目录下。

编译后的文件结构

编译完成后,会在指定目录下生成相应的代码文件。例如,对于 addressbook.proto 文件,生成的 C++ 文件结构如下:

src/
  addressbook.pb.h
  addressbook.pb.cc

这些文件分别包含头文件和源文件,可以直接在 C++ 项目中使用。

Protobuf消息序列化与反序列化

消息序列化

消息序列化是将对象序列化为二进制数据的过程。通过使用生成的代码,可以很方便地实现序列化操作。以下是一个简单的序列化示例:

#include "addressbook.pb.h"

int main() {
  // 创建一个 AddressBook 对象
  addressbook::AddressBook address_book;

  // 添加一个地址
  addressbook::Address* address = address_book.add_address();
  address->set_street("123 Main St");
  address->set_city("Anytown");
  address->set_state("CA");
  address->set_zip("12345");

  // 序列化为二进制数据
  std::string output;
  address_book.SerializeToString(&output);

  // 输出序列化后的数据长度
  std::cout << "Serialized data size: " << output.size() << std::endl;

  return 0;
}

消息反序列化

消息反序列化是将二进制数据反序列化为对象的过程。同样,通过生成的代码可以很方便地实现反序列化操作。以下是一个简单的反序列化示例:

#include "addressbook.pb.h"

int main() {
  // 从二进制数据中反序列化
  addressbook::AddressBook address_book;
  address_book.ParseFromString(serialized_data);

  // 输出第一个地址的信息
  if (!address_book.addresses().empty()) {
    const addressbook::Address& address = address_book.addresses(0);
    std::cout << "Street: " << address.street() << std::endl;
    std::cout << "City: " << address.city() << std::endl;
    std::cout << "State: " << address.state() << std::endl;
    std::cout << "Zip: " << address.zip() << std::endl;
  }

  return 0;
}

示例代码演示

定义一个简单的 .proto 文件:

message Person {
  required string name = 1;
  optional string email = 2;
  repeated string phone = 3;
}

编译生成 C++ 代码,并编写序列化与反序列化的示例:

#include "person.pb.h"

int main() {
  // 创建一个 Person 对象
  person::Person person;
  person.set_name("John Doe");
  person.add_phone("123-456-7890");

  // 序列化为二进制数据
  std::string serialized_data;
  person.SerializeToString(&serialized_data);

  // 反序列化为 Person 对象
  person::Person deserialized_person;
  deserialized_person.ParseFromString(serialized_data);

  // 输出反序列化后的数据
  std::cout << "Name: " << deserialized_person.name() << std::endl;
  for (int i = 0; i < deserialized_person.phone_size(); ++i) {
    std::cout << "Phone: " << deserialized_person.phone(i) << std::endl;
  }

  return 0;
}
Protobuf高级特性

可选字段

可选字段表示该字段在序列化时可以提供也可以不提供。在 .proto 文件中使用 optional 修饰符定义。

例如:

message Person {
  required string name = 1;
  optional string email = 2;
}

email 字段是可选的,序列化时可以不包含该字段。

重复字段

重复字段表示该字段可以出现多次。在 .proto 文件中使用 repeated 修饰符定义。

例如:

message Person {
  required string name = 1;
  repeated string phone = 2;
}

phone 字段可以重复多次,序列化时可以包含多个值。

重复消息类型

重复消息类型表示消息中可以包含多个子消息。通过在消息中定义 repeated message 类型,可以实现重复消息。

例如:

message Person {
  required string name = 1;
  repeated Address addresses = 2;
}

message Address {
  required string street = 1;
  optional string city = 2;
  optional string state = 3;
  optional string zip = 4;
}

Person 消息类型中定义了一个 addresses 字段,可以包含多个 Address 子消息。

实际项目中的Protobuf应用

选择Protobuf的理由

在实际项目中选择使用 Protobuf 通常有以下几个原因:

  1. 高效的数据序列化:Protobuf 提供了高效的二进制序列化格式,比 XML 和 JSON 占用更少的空间且序列化速度快。
  2. 跨语言支持:生成的代码可以在多种语言上运行,方便不同平台间的通信。
  3. 版本控制:Protobuf 支持版本控制,可以在不影响旧版本代码的情况下添加新字段或修改字段。
  4. 易用性:通过简单的定义文件可以方便地定义和修改消息结构,自动生成序列化和反序列化代码。
  5. 可扩展性:在定义文件中添加新字段不会影响现有的代码实现,增加了系统的灵活性。

实际项目案例分享

在某社交应用中,用户信息和好友信息需要在客户端和服务器之间进行传输。使用 Protobuf 定义消息格式,可以方便地实现高效的数据交换。例如:

message User {
  required int32 id = 1;
  required string name = 2;
  repeated string email = 3;
  repeated string phone = 4;
}

message FriendRequest {
  required int32 from_id = 1;
  required int32 to_id = 2;
}

通过定义 UserFriendRequest 消息类型,可以实现用户信息和好友请求的高效传输。

实践中遇到的问题及解决方法

在实际项目中使用 Protobuf 时,可能会遇到以下问题及解决方法:

  1. 字段版本控制问题
    • 问题:添加新字段后,旧版本客户端无法解析新版本的数据格式。
    • 解决方法:使用 optionalrepeated 字段修饰符,并在客户端和服务器中进行版本兼容处理。
// 示例代码
message User {
  required int32 id = 1;
  required string name = 2;
  optional string email = 3;
  repeated string phone = 4;
}

// 在旧版本客户端中忽略新字段
if (has_email) {
  std::cout << "Email: " << user.email() << std::endl;
} else {
  std::cout << "Email not provided" << std::endl;
}
  1. 性能优化问题

    • 问题:序列化和反序列化速度慢,特别是在大量数据传输时。
    • 解决方法:使用更高效的序列化库或优化数据结构。
  2. 字段类型冲突问题
    • 问题:字段类型定义不一致,导致序列化或反序列化失败。
    • 解决方法:严格定义字段类型,并在代码中进行类型检查。
// 示例代码
// 基于定义文件定义正确的类型
message User {
  required int32 id = 1;
  required string name = 2;
  required bool active = 3;
}

// 在代码中进行类型检查
if (user.active()) {
  std::cout << "User is active" << std::endl;
} else {
  std::cout << "User is inactive" << std::endl;
}

综上所述,通过使用 Protobuf,可以高效地实现跨平台的数据交换,简化开发过程,提高系统灵活性。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消