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

  • 運行程序,效果如下:

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章