protobuf学习使用
1、概述
protobuf是Google开发的一种语言中立、平台无关、可扩展的序列化结构数据格式。允许定义一次数据结构,然后可以使用各种支持的语言来生成代码,以轻松地读写这些结构到一个二进制流中,如网络传输或文件,Protobuf支持多种编程语言,包括但不限于C++、Java、Python、Go、Ruby、JavaScript、C#等。
protobuf-getting-started:Protocol Buffer Basics: C++ | Protocol Buffers Documentation
2、使用步骤(以C++举例)
1、创建并编写xxx.proto文件
将要序列化的数据写到proto文件
2、将proto文件生成对应的.cc和.h文件
protoc -I=/路径1 --cpp_out=./路径2 /路径1/addressbook.proto
路径1为.proto所在的路径
protoc.exe -I=./ --cpp_out=./ *.proto
3、直接使用新生成的类
里边有对数据操作的api
3、使用说明
3.1、编写xxx.proto文件
举例login.proto
// 声明protobuf版本
syntax = "proto3";
// 定义请求消息
message LoginRequest {
// 语法格式:字段类型 字段名 = 字段编号;
// 编号从1开始,不能重复
bytes username = 1;
string password = 2;
int32 type = 3;
}
// 定义响应消息
message LoginResponse {
string token = 1;
bytes message = 2;
}
message相当于C++的class关键字,LoginRequest相当于类名,注释采用C/C++风格的双斜杠(//)
username、password、type相当于成员变量。编号从1开始,不能重复,序列化后的protobuf没有使用字段名称,而仅仅采用了字段编号区分字段,与json xml等相比,protobuf不是一种完全自描述的协议格式,即接收端在没有proto文件定义的前提下是无法解码一个protobuf消息体的,与此相对的,json xml等协议格式是完全自描述的,拿到了json消息体,便可以知道这段消息体中有哪些字段。
语法格式
字段类型 字段名 = 字段编号
如何知道有哪些字段类型呢,可以对照下面表查看
3.2、将proto文件生成对应的.cc和.h文件
protoc -I=./ --cpp_out=./ *.proto
其中login.pb.h和login.pb.cc为新生成的头文件和源文件。
下载链接比如:https://github.com/protocolbuffers/protobuf/releases/tag/v26.1
此时我们可以打开看看新生成的头文件,会发现其中有相关字段读写api,详见下图:
3.3、使用代码范例
范例代码演示了如何使用新生成的类,如何序列化和反序列化
代码编译:g++ -std=c++11 -o main login.pb.cc main.cc -lprotobuf -lpthread -L/usr/local/lib
#include "login.pb.h"
#include <iostream>
using namespace std;
int main(int argc, char *argv[]) {
// 创建一个 LoginRequest 对象
LoginRequest login_request;
login_request.set_username("张三");
login_request.set_password("123");
login_request.set_type(100);
// 将 LoginRequest 对象序列化为字符串
string request_data;
login_request.SerializeToString(&request_data);
// 将字符串发送到服务器....
// 服务器接收到字符串后,反序列化为 LoginRequest 对象
LoginRequest login_response;
login_response.ParseFromString(request_data);
// 使用 LoginRequest 对象进行业务逻辑处理...
cout << "接收 用户名: " << login_response.username() << endl;
cout << "接收 密码: " << login_response.password() << endl;
cout << "接收 类型: " << login_response.type() << endl;
return 0;
}
4、protobuf中使用数组
使用数组要使用repeated修饰相关字段,此时你可以理解为vector<string> name,如图。
#include "login.pb.h"
#include <iostream>
using namespace std;
int main(int argc, char *argv[]) {
// 创建一个 LoginRequest 对象
LoginRequest login_request;
// login_request.set_username("张三");
// username 是一个 repeated 字段,可以添加多个用户名
login_request.add_username("张三");
login_request.add_username("李四");
login_request.add_username("王五");
login_request.set_password("123");
login_request.set_type(100);
// 将 LoginRequest 对象序列化为字符串
string request_data;
login_request.SerializeToString(&request_data);
// 将字符串发送到服务器....
// 服务器接收到字符串后,反序列化为 LoginRequest 对象
LoginRequest login_response;
login_response.ParseFromString(request_data);
// 使用 LoginRequest 对象进行业务逻辑处理...
// cout << "接收 用户名: " << login_response.username() << endl;
cout << "接收 用户名: " << login_response.username(0) << endl;
cout << "接收 用户名: " << login_response.username(1) << endl;
cout << "接收 用户名: " << login_response.username(2) << endl;
cout << "接收 密码: " << login_response.password() << endl;
cout << "接收 类型: " << login_response.type() << endl;
return 0;
}
5、protobuf中使用枚举
在proto文件中添加枚举定义(如图),这里有一个注意事项,枚举的第一个元素值必须为0,为了兼容proto2语义。
#include "login.pb.h"
#include <iostream>
using namespace std;
int main(int argc, char *argv[]) {
// 创建一个 LoginRequest 对象
LoginRequest login_request;
// login_request.set_username("张三");
// username 是一个 repeated 字段,可以添加多个用户名
login_request.add_username("张三");
login_request.add_username("李四");
login_request.add_username("王五");
login_request.set_password("123");
login_request.set_type(100);
// 设置枚举值
login_request.set_logintype(ADMIN);
// 将 LoginRequest 对象序列化为字符串
string request_data;
login_request.SerializeToString(&request_data);
// 将字符串发送到服务器....
// 服务器接收到字符串后,反序列化为 LoginRequest 对象
LoginRequest login_response;
login_response.ParseFromString(request_data);
// 使用 LoginRequest 对象进行业务逻辑处理...
// cout << "接收 用户名: " << login_response.username() << endl;
cout << "接收 用户名: " << login_response.username(0) << endl;
cout << "接收 用户名: " << login_response.username(1) << endl;
cout << "接收 用户名: " << login_response.username(2) << endl;
cout << "接收 密码: " << login_response.password() << endl;
cout << "接收 类型: " << login_response.type() << endl;
// 打印 枚举登陆类型
cout << "接收 类型: " << login_response.logintype() << endl;
return 0;
}
6、protobuf中导入其他proto文件
新编写extrainfo.proto,使用import导入,然后使用protoc工具生成新头文件和源文件。
范例代码演示了如何操作Extrainfo中的成员。
备注:相关接口在生成的头文件中都能看到,可以打开看看
代码编译:g++ -std=c++11 -o main login.pb.cc main.cc extrainfo.pb.cc -lprotobuf -lpthread -L/usr/local/lib
// 声明protobuf版本
syntax = "proto3";
message Extrainfo {
string time = 1;
int32 level = 2;
}
// 声明protobuf版本
syntax = "proto3";
// 导入其他proto文件
import "extrainfo.proto";
enum LoginType {
USER = 0; // proto3中第一个枚举值必须为0
ADMIN = 1;
}
// 定义请求消息
message LoginRequest {
// 语法格式:字段类型 字段名 = 字段编号;
// 编号从1开始,不能重复
repeated bytes username = 1;
string password = 2;
int32 type = 3;
LoginType loginType = 4;
Extrainfo extra = 5;
}
// 定义响应消息
message LoginResponse {
string token = 1;
bytes message = 2;
}
#include "login.pb.h"
#include <iostream>
using namespace std;
int main(int argc, char *argv[]) {
// 创建一个 LoginRequest 对象
IMLogin::LoginRequest login_request;
// login_request.set_username("张三");
// username 是一个 repeated 字段,可以添加多个用户名
login_request.add_username("张三");
login_request.add_username("李四");
login_request.add_username("王五");
login_request.set_password("123");
login_request.set_type(100);
// 设置枚举值
login_request.set_logintype(ADMIN);
// 操作其他proto,extra字段
login_request.mutable_extra()->set_time("key");
login_request.mutable_extra()->set_level(6);
// 将 LoginRequest 对象序列化为字符串
string request_data;
login_request.SerializeToString(&request_data);
// 将字符串发送到服务器....
// 服务器接收到字符串后,反序列化为 LoginRequest 对象
LoginRequest login_response;
Extrainfo extra = login_response.extra();
login_response.ParseFromString(request_data);
// 使用 LoginRequest 对象进行业务逻辑处理...
// cout << "接收 用户名: " << login_response.username() << endl;
cout << "接收 用户名: " << login_response.username(0) << endl;
cout << "接收 用户名: " << login_response.username(1) << endl;
cout << "接收 用户名: " << login_response.username(2) << endl;
cout << "接收 密码: " << login_response.password() << endl;
cout << "接收 类型: " << login_response.type() << endl;
// 打印 枚举登陆类型
cout << "接收 类型: " << login_response.logintype() << endl;
// 访问其他proto,extra字段
cout << "接收 extra 字段: " << extra.time() << endl;
cout << "接收 extra 字段: " << extra.level() << endl;
return 0;
}
7、protobuf中添加命名空间
在proto文件中添加package IMLogin,表示当前的proto文件命名空间为IMLogin,打开生成的头文件一看就懂,C++代码再定义对象时也要添加上命名空间。
8、protobuf编译安装
9、问题:使用protobuf为什么节省字节
1、变量名不用传递,传编号(按编号区分字段)
2、采用可变长字段方案(可自行了解下)
学习链接:https://github.com/0voice