Linux(程序设计):33---protobuf库(C++操作Protobuf通信协议)

一、Protobuf通信协议概述

二、protobuf库的安装

  • 通过下方链接下载,此处我们下载3.12.1版本的。下载完解压,进入目录
# 如果下载失败,那就进入网页进行下载
wget https://distfiles.macports.org/protobuf3-cpp/protobuf-cpp-3.12.1.tar.gz

tar zxf protobuf-cpp-3.12.1.tar.gz

cd protobuf-3.12.1

  • 进行配置、编译、安装
./configure

make 

sudo make install

 

  • 默认情况下:
    • 头文件默认安装在/usr/local/include/google/protobuf目录下
    • 库文件默认安装在/usr/local/lib/目录下
    • 可执行文件protoc默认安装在/usr/local/bin/目录下

  • 重新加载动态库
sudo ldconfig

 

  • 查看一下protobuf库的版本
protoc --version

 

编写带有protobuf库的C++程序

  • 编译时要带上-lprotobuf选项,并且程序下要带有与protobuf相关的.h和.cc文件

三、演示案例

编写.protobuf文件

  • 我们现在一份代码,主目录名为person,建立完成之后进入目录

  • 现在我们在建立一个目录proto,用来存储.proto文件

  • 现在我们编写两个.proto文件,分别名为IM.BaseDefine.proto、IM.Login.proto,文件内容分别如下所示

syntax = "proto3";     //声明protobuf版本, 此处为protobuf3, 如果不写默认为2
package IM.BaseDefine; //导致命名空间, 生成对应的.h和.cpp中会有一个IM命名空间和一个BaseDefine命名空间, 其中BaseDefine命名空间包含在IM命名空间内

option optimize_for = LITE_RUNTIME; //设置选项

enum PhoneType{
	PHONE_DEFAULT			= 0x0;		
    PHONE_HOME              = 0x0001;   // 家庭电话
    PHONE_WORK              = 0x0002;   // 工作电话     
}
syntax = "proto3";
package IM.Login;
import "IM.BaseDefine.proto";	//import引入IM.BaseDefine.proto文件
option optimize_for = LITE_RUNTIME;

// message关键字 代表一个对象
message Phone{
    string number = 1;	// = 1是什么?默认值
    IM.BaseDefine.PhoneType phone_type = 2;
}

message Book{
    string name = 1;
    float price = 2;
}

message Person{
    string  name    = 1;
    int32   age     = 2;
    repeated string languages = 3; // repeated 重复, 可以嵌套对象, 类似数组, 会提供对应的add_languages()接口
    Phone   phone   = 4;
    repeated Book   books   = 5;	
    bool            vip     = 6;
    string          address = 7;
}

//使用T开头测试
message TInt32{
    int32   int1     = 1;
}

message TString{
    string   str1     = 1;
}
  • 然后建立一个脚本create.sh并赋予其可执行权限,用来编译.proto文件生成C++代码文件,脚本内容如下:

#!/bin/sh
# proto文件在哪里
SRC_DIR=./
# .h .cc输出到哪里
DST_DIR=../

#C++
protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/*.proto
  • 现在.proto目录下有如下三个文件:

生成.h和.cc文件

  • 指向上面的create.sh,就会在proto的上一级目录自动生成对应的.h和.cc文件,如下所示:
    • IM.BaseDefine.proto:会生成IM.BaseDefine.pb.h和IM.BaseDefine.pb.cc
    • IM.Login.proto:会生成IM.Login.pb.h和IM.Login.pb.cc

  • .h文件中会根据.proto文件生成对应的接口,.cc文件则是.h文件的实现。下面截取IM.BaseDefine.pb.h的部分代码,关于具体实现就不详细介绍了

编写测试代码①

  • 有了上面对应的.h和.cc代码之后,我们就可以自己编写C++程序来测试上面的接口了
  • 例如下面是一个code_test.cpp的文件,用来测试相关接口的使用

//code_test.cpp
#include <iostream>
#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/wait.h>

#include "IM.BaseDefine.pb.h"
#include "IM.Login.pb.h"

static uint64_t getNowTime()
{
    struct timeval tval;
    uint64_t nowTime;

    gettimeofday(&tval, NULL);

    nowTime = tval.tv_sec * 1000L + tval.tv_usec / 1000L;
    return nowTime;
}

/*
如果proto结构体的变量是基础变量,比如int、string等等,那么set的时候直接调用set_xxx即可。

如果变量是自定义类型(也就是message嵌套),那么C++的生成代码中,就没有set_xxx函数名,取而代之的是三个函数名:
set_allocated_xxx()
release_xxx()
mutable_xxx()

使用set_allocated_xxx()来设置变量的时候,变量不能是普通栈内存数据,
必须是手动new出来的指针,至于何时delete,就不需要调用者关心了,
protobuf内部会自动delete掉通过set_allocated_设置的内存;

release_xxx()是用来取消之前由set_allocated_xxx设置的数据,
调用release_xxx以后,protobuf内部就不会自动去delete这个数据;

mutable_xxx()返回分配内存后的对象,如果已经分配过则直接返回,如果没有分配则在内部分配,建议使用mutable_xxx
*/
bool ProtobufEncode(std::string &strPb)
{
    IM::Login::Person person;

    person.set_name("dongshao");      // 设置以set_为前缀
    person.set_age(80);

    person.add_languages("C++");    // 数组add
    person.add_languages("Java");

    // 电话号码
    // mutable_ 嵌套对象时使用,并且是单个对象时使用,比如对应的Person里面的Phone  phone = 4;
    // 比如mutable_phone如果phone已经存在则直接返回,如果不存在则new 一个返回
    IM::Login::Phone *phone = person.mutable_phone();
    if(!phone)
    {
        std::cout << "mutable_phone failed." << std::endl;
        return false;
    }
    phone->set_number("18888888888");
    phone->set_phone_type(IM::BaseDefine::PHONE_HOME);

    // 书籍
    // add_则是针对repeated的嵌套对象,每次调用都返回一个新的对象,注意和mutable_的区别。
    // 比如Person里面的repeated Book   books   = 5;
    IM::Login::Book *book = person.add_books();
    book->set_name("Linux kernel development");
    book->set_price(7.7);
    book = person.add_books();
    book->set_name("Linux server development");
    book->set_price(8.0);

    // vip
    person.set_vip(true);
    // 地址
    person.set_address("anhui");

    uint32_t pbSize = person.ByteSize();        // 序列化后的大小
    
    strPb.clear();
    strPb.resize(pbSize);
    uint8_t *szData = (uint8_t *)strPb.c_str();

    if (!person.SerializeToArray(szData, pbSize))   // 拷贝序列化后的数据
    {
        std::cout << "person pb msg SerializeToArray failed." << std::endl;
        return false;
    }
    return true;
}


bool ProtobufDecode(std::string &strPb)
{
    IM::Login::Person person;
    person.ParseFromArray(strPb.c_str(), strPb.size()); // 反序列化

    // printPerson(person); 这个函数在本文件被删除了, 实现可以参阅pb_speed.cpp

    return true;
}

void printHex(uint8_t *data, uint32_t len)
{
    for(uint32_t i = 0; i < len; i++)
    {
        printf("%02x ", data[i]);
    }
    printf("\n\n");
}

void TInt()
{
    std::string strPb;
    uint8_t *szData;
    IM::Login::TInt32 int1;

    uint32_t int1Size = int1.ByteSize();        // 序列化后的大小

    std::cout << "null int1Size = " << int1Size << std::endl;

    int1.set_int1(0x12);
    int1Size = int1.ByteSize();        // 序列化后的大小
    std::cout << "0x12 int1Size = " << int1Size << std::endl;
    strPb.clear();
    strPb.resize(int1Size);
    szData = (uint8_t *)strPb.c_str();
    int1.SerializeToArray(szData, int1Size);   // 拷贝序列化后的数据
    printHex(szData, int1Size);
    
	int1.set_int1(-11);
    int1Size = int1.ByteSize();        // 序列化后的大小
    std::cout << "-11 int1Size = " << int1Size << std::endl;
    strPb.clear();
    strPb.resize(int1Size);
    szData = (uint8_t *)strPb.c_str();
    int1.SerializeToArray(szData, int1Size);   // 拷贝序列化后的数据
    printHex(szData, int1Size);
	

    int1.set_int1(0x7f);
    int1Size = int1.ByteSize();        // 序列化后的大小
    std::cout << "0xff int1Size = " << int1Size << std::endl;
    strPb.clear();
    strPb.resize(int1Size);
    szData = (uint8_t *)strPb.c_str();
    int1.SerializeToArray(szData, int1Size);   // 拷贝序列化后的数据
    printHex(szData, int1Size);
    

    int1.set_int1(0xff);
    int1Size = int1.ByteSize();        // 序列化后的大小
    std::cout << "0xff int1Size = " << int1Size << std::endl;
    strPb.clear();
    strPb.resize(int1Size);
    szData = (uint8_t *)strPb.c_str();
    int1.SerializeToArray(szData, int1Size);   // 拷贝序列化后的数据
    printHex(szData, int1Size);

    int1.set_int1(0x1234);
    int1Size = int1.ByteSize();        // 序列化后的大小
    std::cout << "0x1234 int1Size = " << int1Size << std::endl;
    strPb.clear();
    strPb.resize(int1Size);
    szData = (uint8_t *)strPb.c_str();
    int1.SerializeToArray(szData, int1Size);   // 拷贝序列化后的数据
    printHex(szData, int1Size);

    int1.set_int1(0x123456);
    int1Size = int1.ByteSize();        // 序列化后的大小
    std::cout << "0x123456 int1Size = " << int1Size << std::endl;
    strPb.clear();
    strPb.resize(int1Size);
    szData = (uint8_t *)strPb.c_str();
    int1.SerializeToArray(szData, int1Size);   // 拷贝序列化后的数据
    printHex(szData, int1Size);
}

void TString(void)
{
    std::string strPb;
    uint8_t *szData;
    IM::Login::TString str1;

    uint32_t str1Size = str1.ByteSize();        // 序列化后的大小

    std::cout << "null str1Size = " << str1Size << std::endl;

    str1.set_str1("1");
    str1Size = str1.ByteSize();                 // 序列化后的大小
    std::cout << "1 str1Size = " << str1Size << std::endl;
    strPb.clear();
    strPb.resize(str1Size);
    szData = (uint8_t *)strPb.c_str();
    str1.SerializeToArray(szData, str1Size);   // 拷贝序列化后的数据
    printHex(szData, str1Size);
    
    str1.set_str1("1234");
    str1Size = str1.ByteSize();                 // 序列化后的大小
    std::cout << "1234 str1Size = " << str1Size << std::endl;
    strPb.clear();
    strPb.resize(str1Size);
    szData = (uint8_t *)strPb.c_str();
    str1.SerializeToArray(szData, str1Size);   // 拷贝序列化后的数据
    printHex(szData, str1Size);

    str1.set_str1("老师");
    str1Size = str1.ByteSize();                 // 序列化后的大小
    std::cout << "老师 str1Size = " << str1Size << std::endl;
    strPb.clear();
    strPb.resize(str1Size);
    szData = (uint8_t *)strPb.c_str();
    str1.SerializeToArray(szData, str1Size);   // 拷贝序列化后的数据
    printHex(szData, str1Size);
}

int main(void)
{
    TInt();
    TString();
    return 0;
}
  • 使用下面的命令进行编译,编译时可能会警告,可以忽略
g++ -o code_test code_test.cpp IM.BaseDefine.pb.cc IM.Login.pb.cc -lprotobuf -lpthread

  • 运行程序,效果如下:

编写测试代码②

  • 下面我们再编写一个pb_speed.cpp,用来测试相关接口性能

//pb_speed.cpp
#include <iostream>
#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/wait.h>

#include "IM.BaseDefine.pb.h"
#include "IM.Login.pb.h"

static uint64_t getNowTime()
{
    struct timeval tval;
    uint64_t nowTime;

    gettimeofday(&tval, NULL);

    nowTime = tval.tv_sec * 1000L + tval.tv_usec / 1000L;
    return nowTime;
}

/*
如果proto结构体的变量是基础变量,比如int、string等等,那么set的时候直接调用set_xxx即可。

如果变量是自定义类型(也就是message嵌套),那么C++的生成代码中,就没有set_xxx函数名,取而代之的是三个函数名:
set_allocated_xxx()
release_xxx()
mutable_xxx()

使用set_allocated_xxx()来设置变量的时候,变量不能是普通栈内存数据,
必须是手动new出来的指针,至于何时delete,就不需要调用者关心了,
protobuf内部会自动delete掉通过set_allocated_设置的内存;

release_xxx()是用来取消之前由set_allocated_xxx设置的数据,
调用release_xxx以后,protobuf内部就不会自动去delete这个数据;

mutable_xxx()返回分配内存后的对象,如果已经分配过则直接返回,如果没有分配则在内部分配,建议使用mutable_xxx
*/
bool ProtobufEncode(std::string &strPb)
{
    IM::Login::Person person;

    person.set_name("dongshao");      // 设置以set_为前缀
    person.set_age(80);

    person.add_languages("C++");    // 数组add 自带类型 protobuff关键字支持的
    person.add_languages("Java");

    // 电话号码
    // mutable_ 嵌套对象时使用,并且是单个对象时使用,比如对应的Person里面的Phone  phone = 4;
    // 比如mutable_phone如果phone已经存在则直接返回,如果不存在则new 一个返回
    IM::Login::Phone *phone = person.mutable_phone();
    if(!phone)
    {
        std::cout << "mutable_phone failed." << std::endl;
        return false;
    }
    phone->set_number("18888888888");
    phone->set_phone_type(IM::BaseDefine::PHONE_HOME);

    // 书籍
    // add_则是针对repeated的嵌套对象,每次调用都返回一个新的对象,注意和mutable_的区别。
    // 比如Person里面的repeated Book   books   = 5;
    IM::Login::Book *book = person.add_books();
    book->set_name("Linux kernel development");
    book->set_price(7.7);

    
    book = person.add_books();
    book->set_name("Linux server development");
    book->set_price(8.0);

    // vip
    person.set_vip(true);
    // 地址
    person.set_address("anhui");

    uint32_t pbSize = person.ByteSize();        // 获取序列化后的大小
    
    strPb.clear();
    strPb.resize(pbSize);
    uint8_t *szData = (uint8_t *)strPb.c_str();

    if (!person.SerializeToArray(szData, pbSize))   // 拷贝序列化后的数据
    {
        std::cout << "person pb msg SerializeToArray failed." << std::endl;
        return false;
    }
    return true;
}

static void printPerson(IM::Login::Person &person)
{
    std::cout << "name:\t" << person.name() << std::endl;
    std::cout << "age:\t" << person.age() << std::endl;
    std::string languages;
    for (int i = 0; i < person.languages_size(); i++)
    {
        if (i != 0)
        {
            languages += ", ";
        }
        languages += person.languages(i);
    }
    std::cout << "languages:\t" << languages << std::endl;

    if (person.has_phone()) // 自定义message的嵌套并且不是设置为repeated则有has_
    {
        // 注意引用
        const IM::Login::Phone &phone = person.phone();
        std::cout << "phone number:\t" << phone.number() << ", type:\t" << phone.phone_type() << std::endl;
    }
    else
    {
        std::cout << "no phone" << std::endl;
    }
    

    for (int i = 0; i < person.books_size(); i++)
    {
        const IM::Login::Book &book = person.books(i);
        std::cout << "book name:\t" << book.name() << ", price:\t" << book.price() << std::endl;
    }

    std::cout << "vip:\t" << person.vip() << std::endl;
    std::cout << "address:\t" << person.address() << std::endl;
}

bool ProtobufDecode(std::string &strPb)
{
    IM::Login::Person person;
    person.ParseFromArray(strPb.c_str(), strPb.size()); // 反序列化

    printPerson(person);

    return true;
}

#define TEST_COUNT 1000000
int main(void)
{
    std::string strPb;
    ProtobufEncode(strPb);  // 序列化后是二进制
    std::cout << "ProtobufDecode, size: " << strPb.size() << std::endl;
    ProtobufDecode(strPb);

#if 1
    uint64_t startTime;
    uint64_t nowTime;

    startTime = getNowTime();
    std::cout << "protobuf encode time testing" << std::endl;
    for (int i = 0; i < TEST_COUNT; i++)
    {
        ProtobufEncode(strPb);
    }
    nowTime = getNowTime();
    std::cout << "protobuf encode " << TEST_COUNT << " time, need time: "
              << nowTime - startTime << "ms" << std::endl;

    startTime = getNowTime();
    std::cout << "protobuf decode time testing" << std::endl;
    for (int i = 0; i < TEST_COUNT; i++)
    {
        ProtobufDecode(strPb);
    }
    nowTime = getNowTime();
    std::cout << "protobuf decode " << TEST_COUNT << " time, need time: "
              << nowTime - startTime << "ms" << std::endl;
#endif
    return 0;
}
  • 使用下面的命令进行编译,编译时可能会警告,可以忽略
g++ -o pb_speed pb_speed.cpp IM.BaseDefine.pb.cc IM.Login.pb.cc -lprotobuf -lpthread

  • 运行程序,效果如下:

猜你喜欢

转载自blog.csdn.net/qq_41453285/article/details/106745639