protobuf的安装配置
protobuf(protocol buffer)是google 的一种数据交换的格式,它独立于平台语言。
google 提供了protobuf多种语言的实现:java、c#、c++、go 和 python,每一种实现都包含了相应语言的编译器以及库文件。
由于它是一种二进制的格式,比使用 xml(20倍) 、json(10倍)进行数据交换快许多。可以把它用于分布式应用之间的数据通信或者异构环境下的数据交换。作为一种效率和兼容性都很优秀的二进制数据传输格式,可以用于诸如网络传输、配置文件、数据存储等诸多领域。
在github源代码下载地址:https://github.com/google/protobuf
1、解压压缩包:unzip protobuf-master.zip
2、进入解压后的文件夹:cd protobuf-master
3、安装所需工具:sudo apt-get install autoconf automake libtool curl make g++ unzip
4、自动生成configure配置文件:./autogen.sh
5、配置环境:./configure
6、编译源代码(时间比较长):make
7、安装:sudo make install
8、刷新动态库:sudo ldconfig
protobuf配置文件的编写
示例:
配置文件的后缀名为 .proto,按照如下格式定义消息类型。
syntax = "proto3"; // 声明了protobuf的版本
package fixbug; // 声明了代码所在的包(对于C++来说是namespace)
// 登录消息类型
message LoginRequest
{
string name = 1; // 1表示第一个字段
string pwd = 2;
}
// 定义登录响应消息类型
message LoginResponse
{
int32 errcode = 1;
string errmsg = 2;
bool success = 3;
}
编译命令:
首先使用命令 protoc 可以查看提示命令
这里可以看到,想要生成哪个语言的头文件和源文件,有对应的命令
使用命令:./表示在当前目录下生成
protoc test.proto --cpp_out=./
使用示例
序列化
使用刚才编译生成的test.pb.h里的类型
#include <iostream>
#include <string>
#include "test.pb.h"
using namespace fixbug;
int main(void)
{
// 封装了Login请求对象的信息
LoginRequest req;
req.set_name("zhang san");
req.set_pwd("123456");
// 对象数据序列化
std::string send_str;
if (req.SerializeToString(&send_str))
{
std::cout << send_str.c_str() << std::endl;
}
return 0;
}
反序列化
#include <iostream>
#include <string>
#include "test.pb.h"
using namespace fixbug;
int main(void)
{
// 封装了Login请求对象的信息
LoginRequest req;
req.set_name("zhang san");
req.set_pwd("123456");
// 对象数据序列化
std::string send_str;
if (req.SerializeToString(&send_str))
{
std::cout << send_str.c_str() << std::endl;
}
// 从send_str里反序列化一个login对象
LoginRequest req2;
if (req2.ParseFromString(send_str))
{
std::cout << req2.name() << std::endl;
std::cout << req2.pwd() << std::endl;
}
return 0;
}
使用经验
一般而言,在protobuf的配置文件里会把字符串类型设置为bytes类型
// 登录消息类型
message LoginRequest
{
bytes name = 1; // 1表示第一个字段
bytes pwd = 2;
}
在编写过程中如果有重复代码,可以把重复的拿出来写成一个message。
示例,在LoginResponse 和GetFriendListsResponse都有 errcode字段和errmsg字段,可以把这两个重复的字段写成一个message,在其他两个地方使用该类型即可。
message ResoultCode
{
int32 errcode = 1;
bytes errmsg = 2;
}
// 登录消息类型
message LoginRequest
{
bytes name = 1; // 1表示第一个字段
bytes pwd = 2;
}
// 定义登录响应消息类型
message LoginResponse
{
ResoultCode resoult = 1;
bool success = 3;
}
message GetFriendListsRequest
{
uint32 userid = 1;
}
message GetFriendListsResponse
{
ResoultCode resoult = 1;
}
protobuf列表的创建
在上面的示例中,所编写的是普通的数据类型,protobuf除了数据对象,还有列表和映射表。
如下,想要得到一个用户的好友信息,定义完好友信息后,再GetFriendListsResponse里要定义好友列表字段,就需要用到关键字 repeated。
syntax = "proto3"; // 声明了protobuf的版本
package fixbug; // 声明了代码所在的包(对于C++来说是namespace)
message ResoultCode
{
int32 errcode = 1;
bytes errmsg = 2;
}
// 登录消息类型
message LoginRequest
{
bytes name = 1; // 1表示第一个字段
bytes pwd = 2;
}
// 定义登录响应消息类型
message LoginResponse
{
ResoultCode resoult = 1;
bool success = 3;
}
message GetFriendListsRequest
{
uint32 userid = 1;
}
message User
{
bytes name = 1;
uint32 age = 2;
enum Sex
{
MAN = 0;
WOMAN = 1;
}
Sex sex = 3;
}
message GetFriendListsResponse
{
ResoultCode resoult = 1;
repeated User friend_list = 2; // 列表类型
}
使用示例:
#include <iostream>
#include <string>
#include "test.pb.h"
using namespace fixbug;
int main(void)
{
GetFriendListsResponse rsp;
ResultCode* rc = rsp.mutable_result();
rc->set_errcode(0);
User* user1 = rsp.add_friend_list();
user1->set_name("zhang san");
user1->set_age(20);
user1->set_sex(User::MAN);
User* user2 = rsp.add_friend_list();
user2->set_name("li si");
user2->set_age(21);
user2->set_sex(User::MAN);
std::cout << rsp.friend_list_size() << std::endl;
return 0;
}
如果要输出好友的信息,也很简单,friend_list()返回的是一个User类型,所以用该类型接收即可。
代码:
#include <iostream>
#include <string>
#include "test.pb.h"
using namespace fixbug;
int main(void)
{
GetFriendListsResponse rsp;
ResultCode* rc = rsp.mutable_result();
rc->set_errcode(0);
User* user1 = rsp.add_friend_list();
user1->set_name("zhang san");
user1->set_age(20);
user1->set_sex(User::MAN);
User* user2 = rsp.add_friend_list();
user2->set_name("li si");
user2->set_age(21);
user2->set_sex(User::MAN);
std::cout << rsp.friend_list_size() << std::endl;
User friend1 = rsp.friend_list(0);
User friend2 = rsp.friend_list(1);
std::cout << friend1.name() << std::endl;
std::cout << friend1.age() << std::endl;
std::cout << friend1.sex() << std::endl;
std::cout << friend2.name() << std::endl;
std::cout << friend2.age() << std::endl;
std::cout << friend2.sex() << std::endl;
return 0;
}
如果一个数据对象的某个成员还是对象的话,需要用指针接收一个mutable的地址。
定义描述RPC方法的类型-service
在protobuf中,默认不生成service服务类型,需要加上一个option选项。
// 定义该选项,表示生成service服务类和RPC方法描述,默认不生成
option cc_generic_services = true;
根据上面所编写的配置文件,可以得知service服务类可以这样写:
// 在protobuf里怎么定义描述rpc方法的类型 - service
service UserServiceRpc
{
rpc Login(LoginRequest) returns(LoginResponse);
rpc GetFriendLists(GetFriendListsRequest) returns(GetFriendListsResponse);
}
生成代码规则
那么它生成的C++代码是什么样子的?
以LoginRequest为例:它是一个class类,继承于 google::protobuf::Message
同样,request也是class类,继承Message
图示:
那么编写的service类型所生成的代码如下:
可以发现,生成的类和所编写的rpc方法一致。而生成的这个类就是
Callee ServiceProvider rpc服务提供者
class UserServiceRpc_Stub;
class UserServiceRpc : public ::PROTOBUF_NAMESPACE_ID::Service {
protected:
// This class should be treated as an abstract interface.
inline UserServiceRpc() {};
public:
virtual ~UserServiceRpc();
typedef UserServiceRpc_Stub Stub;
static const ::PROTOBUF_NAMESPACE_ID::ServiceDescriptor* descriptor();
virtual void Login(::PROTOBUF_NAMESPACE_ID::RpcController* controller,
const ::fixbug::LoginRequest* request,
::fixbug::LoginResponse* response,
::google::protobuf::Closure* done);
virtual void GetFriendLists(::PROTOBUF_NAMESPACE_ID::RpcController* controller,
const ::fixbug::GetFriendListsRequest* request,
::fixbug::GetFriendListsResponse* response,
::google::protobuf::Closure* done);
// implements Service ----------------------------------------------
const ::PROTOBUF_NAMESPACE_ID::ServiceDescriptor* GetDescriptor();
/*
其他内容
*/
};
第二个生成的是stub
caller ServiceConsumer rpc服务消费者
class UserServiceRpc_Stub : public UserServiceRpc {
public:
UserServiceRpc_Stub(::PROTOBUF_NAMESPACE_ID::RpcChannel* channel);
UserServiceRpc_Stub(::PROTOBUF_NAMESPACE_ID::RpcChannel* channel,
::PROTOBUF_NAMESPACE_ID::Service::ChannelOwnership ownership);
~UserServiceRpc_Stub();
inline ::PROTOBUF_NAMESPACE_ID::RpcChannel* channel() { return channel_; }
// implements UserServiceRpc ------------------------------------------
void Login(::PROTOBUF_NAMESPACE_ID::RpcController* controller,
const ::fixbug::LoginRequest* request,
::fixbug::LoginResponse* response,
::google::protobuf::Closure* done);
void GetFriendLists(::PROTOBUF_NAMESPACE_ID::RpcController* controller,
const ::fixbug::GetFriendListsRequest* request,
::fixbug::GetFriendListsResponse* response,
::google::protobuf::Closure* done);
private:
::PROTOBUF_NAMESPACE_ID::RpcChannel* channel_;
bool owns_channel_;
GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(UserServiceRpc_Stub);
};
注意该类的构造函数需要传入一个Channel类型,该类型是一个抽象基类:
class PROTOBUF_EXPORT RpcChannel {
public:
inline RpcChannel() {}
virtual ~RpcChannel();
// Call the given method of the remote service. The signature of this
// procedure looks the same as Service::CallMethod(), but the requirements
// are less strict in one important way: the request and response objects
// need not be of any specific class as long as their descriptors are
// method->input_type() and method->output_type().
virtual void CallMethod(const MethodDescriptor* method,
RpcController* controller, const Message* request,
Message* response, Closure* done) = 0;
private:
GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(RpcChannel);
};
那么对于上面的示例,在调用Login()方法或GetFriendLists()方法时,会在底层转为调用
channel_->CallMethod()
该方法是个纯虚函数,需要派生类继承RpcChannel类后重写该方法(因为基类指针可以指向派生类对象,所以调用的方法会是派生类实现的CallMethod()方法)。
图示: