安装、学习protobuf

Protobuf是什么?

类似于json的一种数据格式,独立于语言,而且是二进制方式,所以比json更快,而且还可以直接存储一些图、树

序列化和反序列化

持久化(存到磁盘硬盘)领域中,数据存到磁盘叫序列化,从磁盘读取出来叫反序列化

网络传输领域中,数据块转字符串叫序列化,对端把字符串解析为数据块叫反序列化

客户端和服务端如何通信

比如一个结构体三个数据依次发送出去,接收端可能一下接收了三个,是无法处理的(不知道边界)

第二种情况,一次性发送出去,添加标记,这样接收端可以一次性解封装,常采用TLV (Tag Length Value)

Protobuf到底是什么

类似于刚刚说的TLV结构,是一种组织数据的格式,可以把若干数据组织成一块---> 序列化为字符串 --> 发送 --> 字符串被反序列化 --> 解析数据

Protobuf怎么安装?

只演示Linux下的安装过程

  1. github下载tar.gz包

https://github.com/protocolbuffers/protobuf/releases/tag/v21.12

我这里下载的21.12版本,比较老了,但是基本功能都能用

  1. 登录Linux服务器安装

先上传到Linux上,一般的ssh工具如Mobaxterm都有这个功能

tar -zxvf your_protobuf_package.tar.gz -C /path/to/you_want

cd /path/to/you_want

  • 需要安装c++编译器,如果其他语言版本的也要安装对应编译器,c++我用的g++编译器!

./configure

make

sudo make install

protoc --version

Protobuf如何使用?

简单示例

  1. 确定要序列化的数据,手动改成protobuf格式
Protobuf类型 c++类型 备注
double double 64位浮点数
float float 32位浮点数
int32 int 32位整数
int64 long 64位整数
uint32 unsigned int 32位无符号整数
uint64 unsigned long 64位无符号整数
sint32 signed int 32位整数,处理负数效率高于int32
sint64 signed long 64位整数,处理负数效率高于int64
bool bool 布尔
string string 字符串必须是utf-8或7-bit ASCII编码
bytes string 多字节语言,比如中文字符,用这个更好
enum enum 枚举
message object of class 自定义消息类型
  1. 根据proto语法,写入后缀proto文件中

test.proto

[root@hcss-ecs-9452 test_proto]# cat test.proto 
/*
struct Person
{
    string name;
    string sex;
    int age;
}
*/
syntax = "proto3";

message Person
{
    string name = 1;
    bytes sex = 2;
    int32 age = 3;
}
  1. protoc命令把proto文件转成c++,包含一个xxx.pb.cc和xxx.pb.h

protoc ./test.proto --cpp_out=./

[root@hcss-ecs-9452 test_proto]# 
[root@hcss-ecs-9452 test_proto]# ls
test.proto
[root@hcss-ecs-9452 test_proto]# protoc ./test.proto --cpp_out=./
[root@hcss-ecs-9452 test_proto]# ls
test.pb.cc  test.pb.h  test.proto
[root@hcss-ecs-9452 test_proto]#
  1. 把产生的c++文件添加到项目中,根据文件中API实现序列化和反序列化

节选test.pb.h ,这部分就是protobuf给出的对name这个数据的处理方式,比如清空 设置 查询 根据地址修改等

enum : int {
    kNameFieldNumber = 1,
    kSexFieldNumber = 2,
    kAgeFieldNumber = 3,
  };
  // string name = 1;
  void clear_name();
  const std::string& name() const;
  template <typename ArgT0 = const std::string&, typename... ArgT>
  void set_name(ArgT0&& arg0, ArgT... args);
  std::string* mutable_name();
  PROTOBUF_NODISCARD std::string* release_name();
  void set_allocated_name(std::string* name);
  private:
  const std::string& _internal_name() const;
  inline PROTOBUF_ALWAYS_INLINE void _internal_set_name(const std::string& value);
  std::string* _internal_mutable_name();
  1. 编写makefile

这里需要注意,protobuf大量使用c++11特性,但是centOS默认安装的是g++4.8.5编译器,可能不支持,需要在编译选项中-std=gnu++11

# 目标文件
TARGET = run

# 源文件
SRC = run.cpp test.pb.cc

# 编译器
CC = g++

# 编译选项
CFLAGS = -I. -lprotobuf -std=gnu++11

# 默认规则
all: $(TARGET)

# 依赖关系
$(TARGET): $(SRC)
        $(CC) $(CFLAGS) $^ -o $@

# 编译.proto文件生成C++源文件和头文件
test.pb.cc test.pb.h: test.proto
        protoc --cpp_out=. $<

# 清理规则
clean:
        rm -f $(TARGET) test.pb.cc test.pb.h

# 防止make删除中间文件
.PHONY: all clean
  1. 编译运行查看

可能要先设置一下环境变量

echo $LD_LIBRARY_PATH

# 如果已经设置过/usr/local/lib就不用往下走了
echo 'export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib' >> ~/.bashrc

source ~/.bashrc

echo $LD_LIBRARY_PATH

[root@hcss-ecs-9452 test_proto]# ls
Makefile  run.cpp  test.pb.cc  test.pb.h  test.proto
[root@hcss-ecs-9452 test_proto]# make
g++ -I. -L/user/local/lib -lprotobuf -std=gnu++11 run.cpp test.pb.cc -o run
[root@hcss-ecs-9452 test_proto]# ./run
name:Jack
sex:女
age:18

复杂应用

嵌套复合数据类型

新增一个proto文件,后续步骤和上面一致

  • 嵌套结构体直接加,嵌套数组前面加repeated就行,调用时需注意,数组要先add_xxx, set_xxx(0, xx)

    [root@hcss-ecs-9452 test_proto]# cat testComplex.proto
    syntax = "proto3";
    message Addr
    {
    int32 door_num = 1;
    bytes addr = 2;
    }

    message Person
    {
    repeated string name = 1;
    bytes sex = 2;
    int32 age = 3;
    Addr ad = 4;
    }

    [root@hcss-ecs-9452 test_proto]# cat run.cpp
    #include "testComplex.pb.h"

    int main(){

      Person p1;
      p1.add_name();
      p1.set_name(0, "Jack");
      p1.add_name("Tom");
      p1.add_name("sb");
     
      p1.set_sex("女");
      p1.set_age(18);
      p1.mutable_ad()->set_addr("陕西省西安市雁塔区");
      p1.mutable_ad()->set_door_num(999); 
      
      std::string output;
      p1.SerializeToString(&output);
      
      Person p2;
      p2.ParseFromString(output);
      //std::cout << "name:" << p2.name() << std::endl;
      std::cout << "sex:"  << p2.sex() << std::endl;
      std::cout << "age:"  << p2.age() << std::endl;
      std::cout << "addr:" << p2.ad().addr() << p2.ad().door_num() << "号" << std::endl;
      int size = p2.name_size();
      for(int i = 0; i < size; ++i)
      {
          std::cout << "第" << i << "个名字是" << p2.name(i) << std::endl;
      }
      return 0;
    

    }

使用枚举

[root@hcss-ecs-9452 test_proto]# cat testComplex.proto 
syntax = "proto3";
enum Color
{
    red = 0;    //第一个必须是0
    green = 5;  //后面的不必123
    blue = 6;
}
message Addr
{
    int32 door_num = 1;
    bytes addr     = 2;
}

message Person
{
    repeated string name = 1;
    bytes sex   = 2;
    int32 age   = 3;
    Addr ad     = 4;
    Color color = 9;  //这里也可以不写567
}
[root@hcss-ecs-9452 test_proto]# cat run.cpp
#include "testComplex.pb.h"

int main(){

    Person p1;
    p1.add_name();
    p1.set_name(0, "Jack");
    p1.add_name("Tom");
    p1.add_name("sb");
   
    p1.set_sex("女");
    p1.set_age(18);
    p1.mutable_ad()->set_addr("陕西省西安市雁塔区");
    p1.mutable_ad()->set_door_num(999); 
    p1.set_color(Color::red);    
 
    std::string output;
    p1.SerializeToString(&output);
    
    Person p2;
    p2.ParseFromString(output);
    //std::cout << "name:" << p2.name() << std::endl;
    std::cout << "sex:"  << p2.sex() << std::endl;
    std::cout << "age:"  << p2.age() << std::endl;
    std::cout << "addr:" << p2.ad().addr() << p2.ad().door_num() << "号" << std::endl;
    int size = p2.name_size();
    for(int i = 0; i < size; ++i)
    {
        std::cout << "第" << i << "个名字是" << p2.name(i) << std::endl;
    }
    std::cout << "Color:"  << p2.color() << std::endl;
    return 0;
}

proto中导入其他proto文件

import "/path/xxx.proto"

命名空间

package xxx

  • 当其他proto文件调用时:
    xxx.Person p

  • 上述命令会转换为cc文件的namespace