1. PB存储单个数据
我们在test.proto文件中定义消息类型(message)该图以项目中函数名举例:
PB 中的类型(message、service)由谷歌定义,于C++ class类似;
我们用bytes定义字符串类型,多字节存储,处理的是字节,省时省消耗
如果写成string,还要进行多字节的转码,把字符转成字节存储,protobuf的二进制存储的
syntax ="proto3";//版本号
package fixbug;//代码所在的包
//定义生成service 的选项 生成service服务类和描述rpc的方法, 不设置默认不生成
//数据 列表 映射表
//定义的请求消息 类型
//定义执行结果的类型,避免重复代码
message ResultCode{
int32 errcode =1;
bytes errmsg=2;
}
message LoginRequest
{
bytes name =1;
bytes pwd =2;
}
//响应消息类型
message LoginResponse{
ResultCode result=1;
bool success=2;
}
- 我们定义了message类型的信息,以及类型所包含的成员
- 等号以及后面的数字是字段的标识符,用于在序列化/反序列化过程中唯一标识这些字段。
- 类型内依然可以嵌套其他的类型:如上的ResultCode 就是该类型内包含的其他类型,在此处是为了避免多类型内的重复代码
我们配置完test.proto之后,打开终端,进入文件所在位置时,
输入命令:protoc test.proto --cpp.out=./ 生成了teat.pb.cc和test.pb.h两个文件
这两个文件将test.proto 文件内容转换成C++ 形式的代码
在main.cc 中
#include "test.pb.h"
#include <iostream>
#include <string>
using namespace fixbug;
int main()
{
// 定义登录消息的对象
LoginRequest req;
// 向对象写值 .set_("XXX");
req.set_name("lulu");
req.set_pwd("123457");
// 序列化->char*
std::string send_str;
if (req.SerializePartialToString(&send_str))
{
std::cout << send_str.c_str() << std::endl; // 展示序列化的内容
}
// 反序列化
// 定义接收的登录消息对象
LoginRequest req3;
if (req3.ParseFromString(send_str)) // 返回值为 bool类型
{
std::cout << req3.name() << std::endl;
std::cout << req3.pwd() << std::endl;
}
return 1;
}
编译命令:g++ main.cc test.pb.cc -lprotobuf
.执行:/a.out
2. 存储列表类数据
当我们需要获取好友列表时,我们的message类型中需要存储的则为列表结构(repeated)
syntax ="proto3";//版本号
package fixbug;//代码所在的包
//定义生成service 的选项 生成service服务类和描述rpc的方法, 不设置默认不生成
//数据 列表 映射表
//定义的请求消息 类型
//定义执行结果的类型,避免重复代码
message ResultCode{
int32 errcode =1;
bytes errmsg=2;
}
}
//定义请求好友列表的消息类型
message GetFriendListsRequest{
int32 usrid =1;
}
message User{
int32 age=1;
bytes name=2;
enum Sex {
MAN=0;
WOMAN=1;
}
Sex sex=3;
}
//列表
//定义响应好友列表的消息类型
message GetFriendListsResponse{
ResultCode result=1;
repeated User friend_list=2;//repeted 可重复的-> 列表
}
- 在获取好友列表的结构中,我们定义了repeted(列表:表示可以重复的)User结构
- User为嵌套类型:在User结构中我们定义关于好友的结构
- 在User类型的性别的定义中,我们选择使用枚举,类型内的枚举变量并不占用字段标识,而定义性别字段,需要进行标识计数=3;
输入命令:protoc test.proto --cpp.out=./ 生成了teat.pb.cc和test.pb.h两个文件
我们继续在main.cc中写入:
int main()
{
GetFriendListsResponse rsp;
ResultCode*result= rsp.mutable_result();
result->set_errcode(0);
result->set_errmsg("");
// 类型内的类型嵌套处理:
// 获取嵌套类型的指针对象
// 对该对象进行赋值
User*friend1=rsp.add_friend_list();
friend1->set_age(20);
friend1->set_name("lulu");
friend1->set_sex(User::WOMAN);
User*friend2=rsp.add_friend_list();
friend2->set_age(22);
friend2->set_name("ksda");
friend2->set_sex(User::WOMAN);
int num=rsp.friend_list_size();
//std::cout<<num<<std::endl;
int i=0;
while(i<num)
{
const fixbug::User user=rsp.friend_list(i++);
std::cout<<user.age()<<std::endl;
std::cout<<user.name()<<std::endl;
std::cout<<user.sex()<<std::endl;
}
return 0;
}
在 GetFriendListsResponse方法中,我们使用了嵌套类型的ResultCode,以及队列性质的User类型
在使用嵌套类:ResultCode时
- 不能直接引用GetFriendListsResponse对象的result变量(此处区分与PB 存储单个数据时的直接调用赋值),我们发现GetFriendListsResponse对象的result变量此时是const常量限制,无法修改,所有我们必须定义嵌套类型的指针,接收获取的可修改的对象地址,ResultCode*result= rsp.mutable_result();再对result 进行赋值操作;
嵌套队列类型的User:
- 获取添加好友信息的指针:变量User*friend2=rsp.add_friend_list();
- 获取列表中每一位好友的成员对象user:const fixbug::User user=rsp.friend_list(i++);
- 输出好友列表的成员量:std::cout<<num<<std::endl;
编译命令:g++ main.cc test.pb.cc -lprotobuf
.执行:/a.out
3. 解析 message 类型
class LoginRequest 类 公有方式继承于 google::protobuf::Message 类
由原来的message 类型的成员转换为C++类型:
- 可以定义类型对象
- 获取成员变量的读写功能
- 获取数据对象的序列化和反序列化
4. 解析 service 类型
4.1. 实践3(定义RPC方法)
在protobuf里面怎么定义描述rpc方法的类型?
在test.proto配置 service 类型:
//定义描述rpc 方法的类型:service
service UserServiceRpc{
rpc Login (LoginRequest)returns(LoginResponse);
rpc GetFriendList(GetFriendListsRequest)returns(GetFriendListsResponse);
}
rpc Login相当于是定义一个login的rpc方法,login是rpc方法的名字,protobuf不支持rpc通信功能,只支持对于rpc方法的描述,通过这个描述它就可以去做rpc请求的携带的参数的序列化和反序列化,login的(LoginRequest)传输调用的方法名及参数,returns就是表示:这个rpc方法login(LoginResponse)执行以后返回的protobuf打包的类型。
我们进入生成的test.pb.h看到,服务service生成了UserServiceRpc类 、UserServiceRpc_stub类(桩,代理类)
相当于是说,本地调用一个RPC方法的时候,底层要做很多事情,这些事情都是由代理类来做的。
UserServiceRpc类解析:
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 GetFriendList(::PROTOBUF_NAMESPACE_ID::RpcController* controller,
const ::fixbug::GetFriendListsRequest* request,
::fixbug::GetFriendListsResponse* response,
::google::protobuf::Closure* done);
// implements Service ----------------------------------------------
const ::PROTOBUF_NAMESPACE_ID::ServiceDescriptor* GetDescriptor();
void CallMethod(const ::PROTOBUF_NAMESPACE_ID::MethodDescriptor* method,
::PROTOBUF_NAMESPACE_ID::RpcController* controller,
const ::PROTOBUF_NAMESPACE_ID::Message* request,
::PROTOBUF_NAMESPACE_ID::Message* response,
::google::protobuf::Closure* done);
const ::PROTOBUF_NAMESPACE_ID::Message& GetRequestPrototype(
const ::PROTOBUF_NAMESPACE_ID::MethodDescriptor* method) const;
const ::PROTOBUF_NAMESPACE_ID::Message& GetResponsePrototype(
const ::PROTOBUF_NAMESPACE_ID::MethodDescriptor* method) const;
private:
GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(UserServiceRpc);
};
UserServiceRpc类是无参数的构造函数:
是使用在rpc服务的提供者(callee,远程方法的执行端)
- 虚函数Login、GetFriendLists,都是由继承于Service的UserServiceRpc重写的,生成方法的名字就是我们定义rpc服务的方法的名字,参数是基于回调的操作,参数都是固定的4个。
- 我们得知道rpc方法的名字(属于哪一个类类型的方法)和调用函数方法的参数,都在ServiceDescriptor描述(服务的名字,服务里面的方法名字,参数,请求的是纯对象的方法)。
UserServiceRpc_stub类解析(代理类):
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 GetFriendList(::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);
};
Stub是用在RPC调用的发起端(caller端,rpc服务的消费者)
- Stub代理类公有继承于UserServiceRpc,由::PROTOBUF_NAMESPACE_ID::RpcChannel* 传递的指针channel实现类的构造
- 由于UserServiceRpc_Stub代理类公有继承于UserServiceRpc,该类中也有虚函数Login、GetFriendLists,但是该函数的执行都是由channel执行的
- 不管是调用什么方法,底层都是调用channel的CallMethod方法!method(0)和method1(1)肯定是区分方法的名字
-
4.2. RpcController是什么?
通过查看 RpcController
得知: RpcController是一个包含纯虚函数的抽象类!
- 所以 RpcController的调用,须得我们自己继承并实现其虚函数
- 继而通过父类的指针通过多态调用了我们实现的 MY_RpcController
- stub的login方法或者getfriendlist方法都是父类的指针指向了子类
- 最终都调用到我们的MyRpcChannel的CallMethod方法,我们自己实现的部分(message类型:LoginRequest、LoginResponse、 GetFriendListsResponse、GetFriendListsResponse)就可以进行rpc方法的序列化和反序列化,然后发起远程的rpc调用请求。