Protobuf
Protocol Buffers(又名 protobuf)是 Google 的语言中立、平台中立、可扩展的机制,用于序列化结构化数据
- 类似于 XML,json,但更小、更快、更简单。
- 只需定义一次数据的结构化方式,然后就可以使用特殊生成的源代码,使用各种语言轻松地在各种数据流中写入和读取结构化数据。
protobuf使用步骤
- 第一步:定义proto文件,文件的内容就是顶我们需要存储的或者传输的数据结构们也就是定义我们自己的数据存储和传输协议
- 第二步:使用protobuf编译器protoc来编译自定义的proto文件,用于生成pb.h文件(proto文件中自定义类的头文件)和pb.cc(proto文件中自定义类的实现文件)
- 第三步:使用protobuf的C++ API来进行消息的读写
定义proto文件
syntax = "proto3";
package monitor.proto;
message CpuLoad {
float load_avg_1 = 1;
float load_avg_3 = 2;
float load_avg_15 = 3;
map<int32, string> = 4;
}
message NetInfo {
string name = 1;
float send_rate = 2;
}
message MonitorInfo{
CpuLoad cpu_load = 1; #std::string
repeated NetInfo net_info = 2; # std::vector
}
protoc -I$SRC_DIR --cpp_out=$DST_DIR test.proto
# 参数解释
# -I$SRC_DIR 指定test.proto所在目录
# --cpp_out=$DST_DIR 指定生成cpp相关文件,并指定生成路径
# test.proto 需要编译的proto文件
以上protobuf的数据类型均为message,通过命令行protoc -I$SRC_DIR --cpp_out=$DST_DIR test.proto 生成xx.pb.h和xx.pb.cc文件 。
message CpuLoad 生成的是一个class CpuLoad 这个类继承了 ::google::protobuf::message ,这个类提供了许多C++ API 用于调用序列化与反序列化的功能,以及访问protobuf中设置的属性。
bool SerializeToString(std::string* output) const;
// 将消息序列化并储存在指定的string中。
// 注意里面的内容是二进制的,而不是文本;
// 我们只是使用string作为一个很方便的容器。
// “1001001000111” std::string 序列化消息存储为string
bool ParseFromString(const std::string& data);
// 从给定的string解析消息。
bool SerializeToArray(void * data, int size) const
// 将消息序列化至数组
uint8 * data
bool ParseFromArray(const void * data, int size)
// 从数组解析消息
bool SerializeToOstream(ostream* output) const;
// 将消息写入到给定的C++ ostream中。
bool ParseFromIstream(istream* input);
// 从给定的C++ istream解析消息。
以下为个人测试示例
syntax = "proto3";
package fixbug; // 声明代码所在包 类似namespace
// 定义下面的选项 表示生成service类对应的rpc
option cc_generic_services = true;
message ResultCode{
int32 errcode = 1;
bytes errmsg = 2;
}
// 定义登录消息类型 name pwd
// 一般string都用bytes表示
message LoginRequest{
string name = 1;
string pwd = 2;
}
// 定义登录响应消息类型
message LoginResponse{
ResultCode result = 1;
bool success = 2;
}
message User{
bytes name = 1;
uint32 age = 2;
enum Sex{
MAN = 0;
WOMEN = 1;
}
Sex sex = 3;
}
message GetFriendListRequest{
uint32 userid = 1;
}
message GetFriendListResponse{
ResultCode resultr = 1;
repeated User friendList = 2; // 定义了一个列表类型
}
service UserServiceRpc{
rpc Login(LoginRequest) returns(LoginResponse);
rpc GetFriendList(GetFriendListRequest) returns(GetFriendListResponse);
}
#include "test.pb.h"
#include <string>
#include <iostream>
// 获取在protobuf文件中封装好的数据
fixbug::LoginRequest req;
req.set_name("liu yi");
req.set_pwd("0823");
/**
* @brief 序列化:将数据序列化成一个字符串,然后将序列化后的字符串进行发送 --send_str
*/
std::string send_str;
bool ret = req.SerializeToString(&send_str);
if(ret){
std::cout << send_str.c_str() << std::endl;
}
/**
* @brief 反序列化:将一个protobuf对象存储的数据
* 反序列化成一个可以用于己端使用的string
* @brief ps:传输过来的数据是一个序列化的字符串
* 需要反序列化解出来 但都需要通过protobuf提供出来的对象的接口来进行
*/
fixbug::LoginRequest req2;
ret = req2.ParseFromString(send_str);
if(ret){
std::cout << req2.name() << req2.pwd() << std::endl;
}
在分布式中最关键的一个环节就是通信的双方制定正确的通信的协议,这个通信协议是通过Rpc服务接口来定义的,protoc生成的类 class UserServiceRpc 继承的类是::PROTOBUF_NAMESPACE_ID::Service, class UserServiceRpc_Stub这个类继承UserServiceRpc Stub这个类主要是调用方用来Call来调用的。
UserServiceRpc protoc生成的这个类主要Rpc服务的提供者用来调用
UserServiceRpc_Stub proto生成的这个类主要是Rpc服务的消费者来调用
Rpc服务的消费者,也就是Rpc服务的调用方,主要的调用与实现逻辑:UserServiceRpc_Stub 继承了 UserServiceRpc 其中在proto文件中定义好的接口由protoc生成,如下是生成的Rpc服务接口参数列表,一个是RpcController* controller(先挖个坑后续研究) 然后是我们在proto文件中定义的需要的接口参数 即是 定义好的 ::fixbug::LoginRequest* request ::fixbug::LoginResponse* response ,然后是一个回调函数Closure这个后面再填坑。
void UserServiceRpc_Stub::Login(::PROTOBUF_NAMESPACE_ID::RpcController* controller,
const ::fixbug::LoginRequest* request,
::fixbug::LoginResponse* response,
::google::protobuf::Closure* done)
/*
* @prief 具体实现
*/
void UserServiceRpc_Stub::Login(::PROTOBUF_NAMESPACE_ID::RpcController* controller,
const ::fixbug::LoginRequest* request,
::fixbug::LoginResponse* response,
::google::protobuf::Closure* done) {
channel_->CallMethod(descriptor()->method(0),
controller, request, response, done);
}
RpcChannel 一个抽象类,纯虚函数,只有一个函数接口就是method这个函数,需要我们去具体实现,通过继承RpcChannel基类来实现一个自己RpcChannel 来进行rpc接口的调用,其中这个descriptor()->method(0) descriptor是UserServiceRpc的用来描述的一个函数 可以获取接口的名称以及所需的参数列表等等 后面填坑。