Linux下安裝protobuf以及在C++中使用


前言

本文只介紹如何安裝protobuf, 如何編譯使用了protobuf的 C++ 代碼
至於詳細的protobuf對應產生的 C++ 的 API, 下一篇博客再討論

安裝並配置環境變量

  1. 安裝輔助工具
sudo apt-get install autoconf automake libtool curl make g++ unzip
  1. 獲取源代碼,在發佈頁面中下載一個.tar.gz.zip軟件包
  2. 解壓至指定目錄
 tar -xzf protobuf-all-3.7.0.tar.gz -C ~/Protocol_Buffer
  1. 執行以下命令安裝
(默認是安裝在 /usr/local,也可以通過添加--prefix==PATH參數指定安裝目錄)
$./configure --prefix=/usr/local/protobuf
$ make
$ make check
$ make install

以上命令需要sudo權限, 並且可能需要一段時間, 耐心等待

  1. /etc/profile中添加 (這部分內容摘自博客)
#(動態庫搜索路徑) 程序加載運行期間查找動態鏈接庫時指定除了系統默認路徑之外的其他路徑
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/protobuf/lib/
#(靜態庫搜索路徑) 程序編譯期間查找動態鏈接庫時指定查找共享庫的路徑
export LIBRARY_PATH=$LIBRARY_PATH:/usr/local/protobuf/lib/
#執行程序搜索路徑
export PATH=$PATH:/usr/local/protobuf/bin/
#c程序頭文件搜索路徑
export C_INCLUDE_PATH=$C_INCLUDE_PATH:/usr/local/protobuf/include/
#c++程序頭文件搜索路徑
export CPLUS_INCLUDE_PATH=$CPLUS_INCLUDE_PATH:/usr/local/protobuf/include/
#pkg-config 路徑
export PKG_CONFIG_PATH=/usr/local/protobuf/lib/pkgconfig/
  1. reboot, 使用echo $LIBRARY_PATH驗證是否成功

簡單例子

  1. 創建address.proto文件
syntax = "proto3"; 	//指明使用proto3語法

package tutorial;		//包聲明符, 產生的類會被包裝在C++命名空間中

message Persion //聲明消息
{
    string name = 1;
    int32 age = 2;
}

message AddressBook //AddressBook消息中包含一個Persion消息
{
    Persion   persion = 1;
}

消息中每個字段後"= 1", “= 2"標記標識該字段在二進制編碼中使用的唯一"標記”
protobufmessage是一些列鍵值對, message的二進制版本只是使用字段號作爲key, 每個字段的名稱和聲明類型只能在解碼端通過引用消息類型的定義(即.proto文件)來確定

  1. 編譯address.proto文件

protobuf 編譯器 protoc, 查看版本使用

$ protoc -- version

編譯文件 : protoc 待編譯文件 --cpp_out=輸出目錄, –cpp_out指的是輸出爲 C++ 版本

$ protoc address.proto --cpp_out=.

編譯成功後, 在你制定的輸出目錄下生成address.pb.haddress.pb.cc兩個文件

  1. 創建useProtoBuf.cpp文件, 在 c++ 代碼中使用我們以protobuf協議編寫的消息
#include "address.pb.h"
void func(tutorial::Persion* ptr) 
{}
int main() 
{
    tutorial::AddressBook addressBook_;
}
  1. 編譯
$ g++ useProtoBuf.cc address.pb.cc -lprotobuf

在C++中使用

本篇博客討論使用protobuf定義的消息 message, 使用 protoc 編譯生成對應的.h.cc文件後, 其中對應產生的API


定義一個.proto文件 : msg.proto


syntax = "proto3";

package test_2;

message usr_login 
{
    string usrname = 1;
    string usrpasswd = 2;
}

message usr_info 
{
    int32 classrome = 1;
    string studentID = 2;
}

message student 
{
    int32 STAMP = 1;
    usr_login usrLogin = 2;
    usr_info usrInfo = 3;
    repeated int32 array = 4;
}

使用方法

#include <assert.h>
#include <iostream>
#include "msg.pb.h"

using namespace std;

int main() 
{
    test_2::student student_;

    cout << "類所佔字節數 = "
         << student_.ByteSize()
         << endl;
    
    //對於int類型的只提供獲取,修改和清楚三個API
    student_.set_stamp(123); //生成的API都是小寫

    //消息中的自定義類型,並沒有set藉口,而是要通過mutable_*接口
    //來得到對應指針進而去進行操作
    auto usrLoginPtr = student_.mutable_usrlogin();
    auto usrInfoPtr = student_.mutable_usrinfo();

    string name("lzj");
    string passwd("123456");
    usrLoginPtr->set_usrname(name);
    usrLoginPtr->set_usrpasswd(passwd);

    string id("04161027");
    usrInfoPtr->set_classrome(5);
    usrInfoPtr->set_studentid(id);

    cout << "未初始化前array長度 = "
         << student_.arr_size()
         << endl;

    
    for (int i = 0; i < 5; i++) 
    {
        //對於repeated的類型,add_*()接口增加一條記錄
        //該接口返回一個指向該條記錄的指針,用來做相關操作
        //我這裏只是簡單的裏面一個int
        auto arrPtr = student_.add_arr();
        arrPtr->set_id(i);
    }

    //將其轉化爲字符串
    //即可用在網絡編程通信時
    string buff;
    //序列化爲字符串
    bool isSucceed = student_.SerializePartialToString(&buff);
    assert(isSucceed == true);


    /*********parsing***************/
    test_2::student res;
    //反序列化
    isSucceed = res.ParseFromString(buff);
    assert(isSucceed == true);

    cout << "student`s STAMP = "
         << res.stamp()
         << endl;
    
    usrLoginPtr = res.mutable_usrlogin();
    cout << "usrLogin`s usrname = "
         << usrLoginPtr->usrname()
         << endl;
    cout << "usrLogin`s usrpasswd = "
         << usrLoginPtr->usrpasswd()
         << endl;
    
    usrInfoPtr = res.mutable_usrinfo();
    cout << "usrInfo`s classrome = "
         << usrInfoPtr->classrome()
         << endl;
    cout << "usrInfo`s studentID = "
         << usrInfoPtr->studentid()
         << endl;

    //遍歷數組
    for (int i = 0; i < res.arr_size(); i++) 
    {
        //獲得一個該下標的元素的引用
        //返回值爲const類型的,不能通過該返回值去進行修改
        auto tmpPtr = res.arr(i);
        cout << "arr["
             << i
             << "] = "
             << tmpPtr.id()
             << endl;
    }
    cout << endl;

    //返回下標爲2的數組元素的指針
    auto tmpArr = res.mutable_arr(2);
    //改變其值
    tmpArr->set_id(99);
    for (int i = 0; i < res.arr_size(); i++) 
    {
        auto tmpPtr = res.arr(i);
        cout << "arr["
             << i
             << "] = "
             << tmpPtr.id()
             << endl;
    }
}

輸出結果

類所佔字節數 = 0
未初始化前array長度 = 0
student`s STAMP = 123
usrLogin`s usrname = lzj
usrLogin`s usrpasswd = 123456
usrInfo`s classrome = 5
usrInfo`s studentID = 04161027
arr[0] = 0
arr[1] = 1
arr[2] = 2
arr[3] = 3
arr[4] = 4

arr[0] = 0
arr[1] = 1
arr[2] = 99
arr[3] = 3
arr[4] = 4

總結

對於我們定義的消息, 編譯生成對應的 c++ 類 msg.pb.cc

其相應的常用 API 爲:

  1. SerializePartialToString() 序列化爲字符串
  2. ParseFromString()從字符串反序列化, 這兩個也是我們可以在網絡編程中收發數據時用到的
  3. 每一個成員變量, 函數名直接爲變量名時, 表示返回其const reference
  4. set_成員變量名()表示賦值
  5. mutable_成員變量名()表示返回指向該成員的指針
  6. 至於類似數組的repeated標識符字段如何操作代碼中的註釋有
  7. 對於每一個message還會生成一個CopyFrom()函數可以進行復制操作, 但注意, 他可以接受任何類型的message, 也就是有可能出現兩個不同類型的消息之間進行復制, 所以注意它的使用, 或者使用賦值operator =代替(賦值類型不同會報錯)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章