PB (protobuf)学习以及项目应用梳理:

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.cctest.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.cctest.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++类型:

  1. 可以定义类型对象
  2. 获取成员变量的读写功能

  1. 获取数据对象的序列化和反序列化

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调用请求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值