引言
在分布式系统和微服务架构中,高效的数据序列化和反序列化是关键。虽然 JSON 和 XML 是广泛使用的数据格式,但它们并不总是最优的选择。Protocol Buffers(protobuf)是由 Google 开发的一种高效的序列化机制,它提供了一种更快速、更小的数据交换格式,并且通常用于rpc服务的通信中。
特点:
-
跨平台:支持多种编程语言,包括但不限于 C++, Java, Python, Go。
-
高效性:比 JSON 和 XML 更小的二进制格式,占用更少的网络带宽和存储空间。
-
可扩展性:支持向后和向前兼容,方便数据结构的升级。
protobuf 的基本使用
安装 protobuf
首先,我们需要安装 protobuf 编译器 protoc
。以下是安装步骤:
-
macOS
brew install protobuf
-
Ubuntu
sudo apt-get install protobuf-compiler
定义 .proto 文件
使用 protobuf 的第一步是定义你的数据结构。我们通过 .proto
文件来描述这些结构。例如,我们定义一个简单的用户信息结构:
syntax = "proto3";//声明版本号
message User {
string name = 1;//字段1
int32 id = 2;
string email = 3;
}
在这个定义中,syntax = "proto3"
表示我们使用的是 protobuf 的第三版语法。message则是类似于c++中的class声明,定义一个数据体。
编译 .proto 文件
接下来,我们需要使用 protoc
编译器将 .proto
文件编译成目标编程语言的代码。这里我们使用c++,可以使用以下命令:
protoc --cpp_out=. user.proto
这将生成两个文件:user.pb.h
和 user.pb.cc
,我们可以在 C++ 代码中直接使用。当我们编译proto文件后,里面的message声明的代码端,会自动被声明为同名的类,并且对其中的变量,采用默认的成员函数来进行操作。比如有一个string类型的name变量,则会默认生成获取name值的string name()成员函数和设置name值的 void set_name(string name)的成员函数。
在代码中使用
以下是一个简单的示例,展示如何在 C++ 中使用生成的 protobuf 代码:
#include <iostream>
#include <fstream>
#include "user.pb.h"
using namespace std;
int main() {
// 创建一个 User 对象,这里的User对象来自user.pb.h文件,也就是编译生成后的文件
User user;
user.set_name("张三");
user.set_id(123);
user.set_email("2813348758@qq.com");
// 序列化为二进制格式并写入文件
fstream output("user_data.bin", ios::out | ios::trunc | ios::binary);
if (!user.SerializeToOstream(&output)) //SerializeToOstream通过改函数将数据序列化,并将序列化后的数据传给传出参数,也就是这里的output
{
cerr << "Failed to write user data." << endl;
return -1;
}
output.close();
// 从文件中读取并反序列化
User user_new;
fstream input("user_data.bin", ios::in | ios::binary);
if (!user_new.ParseFromIstream(&input)) //ParseFromIstream通过该函数进行反序列化,将传入参数进行反序列化赋值给User对象
{
cerr << "Failed to read user data." << endl;
return -1;
}
input.close();
// 输出反序列化后的数据
cout << "Name: " << user_new.name() << ", ID: " << user_new.id() << ", Email: " << user_new.email() << endl;
return 0;
}
在这个示例中,我们创建了一个 User
对象,并将其序列化为二进制数据,然后又将其反序列化为新的 User
对象。
两个字段声明:
repeated:
repeated
字段用于表示一个字段可以出现零次或多次,相当于一个数组或列表。这对于表示多个值的集合非常有用,例如一个人的多个电话号码。
optional:
在 proto3 中,所有字段默认都是可选的,即使不显式声明 optional
。这意味着你可以不设置某个字段,它的默认值会被使用。
枚举enum的使用:
enum枚举类型允许你为一组常量值定义一个名称,提供了一种便捷的方式来表示一组预定义的可能值。枚举类型的使用可以提高代码的可读性和可维护性。使用enum关键字来声明,使用类似于c++中枚举类型的使用,不做过多介绍
定义rpc服务方法:
使用service
关键字用于定义远程过程调用 (RPC) 服务。一个 service
包含一组 RPC 方法,每个方法定义了请求消息和响应消息的类型。
举个栗子:
假设我们要定义一个用户管理服务,其中包括添加用户和获取用户信息的方法:
syntax = "proto3";
package user;
message Person {
string name = 1;
int32 id = 2;
string email = 3;
}
message AddPersonRequest //添加用户功能的请求,添加一个person类
{
Person person = 1;
}
message AddPersonResponse //添加用户功能的响应,根据返回的success来判断请求是否成功
{
bool success = 1;
}
message GetPersonRequest //获取用户功能的请求,根据id获取用户信息
{
int32 id = 1;
}
message GetPersonResponse //获取用户功能的响应,对应请求,返回相应的person信息
{
Person person = 1;
}
service UserService {
rpc AddPerson(AddPersonRequest) returns (AddPersonResponse);
rpc GetPerson(GetPersonRequest) returns (GetPersonResponse);
}
注意:这里使用rpc服务,则需要安装相应的grpc插件
sudo apt-get install -y protobuf-compiler
sudo apt-get install -y grpc
编译 .proto 文件生成 C++ 源文件:
protoc --grpc_out=. --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` user.proto
protoc --cpp_out=. user.proto
这将生成以下文件:
-
user.pb.h
和user.pb.cc
:包含消息类的定义和实现。 -
user.grpc.pb.h
和user.grpc.pb.cc
:包含服务类的定义和实现。
protobuf 的优缺点
优点
-
高效:比 JSON 和 XML 更紧凑,占用更少的带宽和存储空间。
-
跨语言支持:支持多种编程语言,方便在不同系统之间进行数据交换。
-
版本兼容性:protobuf 的消息结构可以轻松地向后兼容和向前兼容。
缺点
-
可读性差:二进制格式不如 JSON 和 XML 可读,调试时需要借助工具。
结论
Protocol Buffers 是一种高效、跨平台的序列化工具,适用于需要高效数据传输的场景。虽然它的二进制格式在可读性上不如 JSON 和 XML,但它在性能和灵活性上具有明显优势。无论是大型分布式系统还是微服务架构,protobuf 都是一个值得考虑的选择。