protobuf的基本用法

本文详细介绍了protobuf的安装步骤,包括下载、解压、编译和安装,并展示了如何编写protobuf配置文件,创建消息类型。通过示例展示了序列化和反序列化的过程,以及如何利用protobuf进行数据交换。此外,还分享了使用protobuf的经验,如列表创建、服务定义以及代码生成规则,是学习和使用protobuf的实用指南。
摘要由CSDN通过智能技术生成

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()方法)。

图示:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_索伦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值