linux环境下protobuf的安装与使用

2,346 阅读5分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

零、最快捷的方式pip安装

(1)如下pip安装protobuf并制定版本

sudo yum install pip  #安装pip
pip install protobuf==2.5.0   #pip安装并制定protobuf的版本
sudo pip install --upgrade protobuf #更新protobuf版本
pip install gevent==1.4.0 #pip安装gevent并制定版本
pip install -i https://pypi.douban.com/simple/ locustio==0.11.0 #pip安装locust并制定版本

一、protobuf的下载安装

1、protobuf的下载这里(next可找到更早版本)。此处下载的是protobuf-all-3.5.1.tar.gz。

2、安装准备:安装protobuf前确保以下软件都已经被安装。方法也很简单yum -y install XXXX即可。

#下面这些可能都是需要是事先安装的(如果后续安装出错往这里想想)
autoconf
automake 
libtool   
make  
g++    
unzip

注1:可能会遇到一个蛋疼的问题。那就是执行make提示你要“yum install make”安装;指令安装语句又提示你“already install”,特别的蛋疼(见下图)。

解决办法:remove后重新安装即可。

#查看rpm是否安装
rpm -qa | grep "make"

#通过yum卸载
yum remove make

#然后重新安装
yum install make -y

  

注2:值得注意的是我们要将g++设为默认使用C++11,可以在终端执行:

alias g++='g++ -std=c++11'

但是切记:这个只是临时生效的,永久有效的方法是将上述语句写入/root/.bashrc 文件中。

vim /root/.bashrc

添加:alias g++='g++ -std=c++11'

3、安装protobuf

执行如下命令,安装时间有点长,耐心等待(可以写成自动化脚本,就无需一步一步执行):

./configure 
make  #时间有点长
make check  #时间有点长
sudo make install 
sudo ldconfig  # refresh shared library cache.

这时一般来说
libprotobuf库在/usr/local/lib路径下;protoc一般在/usr/local/bin路径下 。如下:

4、配置环境变量

主要是完善PATH和PKG_CONFIG_PATH等环境变量

$ sudo vim /etc/profile

添加

export PATH=$PATH:/usr/local/bin/
export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig/  #这个参数是必须的

#我添加了这些,感觉有些没有也可以。
export PATH=$PATH:/usr/local/bin
export PATH=$PATH:/usr/local/include
export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib

保存生效

source /etc/profile

注:(1)保存生效的语句需要在每个终端都执行一遍才可以生效;

(2)通过echo $PATH   /  echo $PKG_CONFIG_PATH的方法验证配置在本终端有没有生效。

(3)上述配置中PKG_CONFIG_PATH是必须的(当然其他可能也是必须的),如果没有在编译的时候会报如下错误:

这里的protobuf.pc存了protobuf相关的头文件和库相关的信息,这些信息都是编译的时候要用到的。这里是因为我们使用了pkg-config来方便引用了的,关于pkg-config参见 这里

5、配置动态路径

sudo vim /etc/ld.so.conf

追加

/usr/local/lib

然后以root权限更新动态库路径

ldconfig

6、验证有没有安装成功

执行:

protoc --version

能够显示相应版本信息就说明安装成功。

7、继续安装python版protobuf的话继续指令如下指令即可:

cd ./python
python setup.py build
python setup.py test
python setup.py install

之后python脚本导入编译后的pb文件后就不会出现“ImportError: No module named google.protobuf.internal”报错了。

二、使用举例

1、person.proto文件:

syntax = "proto3";#指定使用proto3
package tencent; #命名空间

message Person
{
    string name = 1;
    uint32 age  = 2;
    uint64 phnum = 3;
}

注意:proto3和proto2的区别还是蛮大的,此处没有了proto2中常见的optional、required之类的修饰。

2、编译.proto文件。 执行如下指令对.proto文件按照c++、python、go形式编译,此时会生成相对应的pb.cc/pb.h、*py2.py、.pb.go文件。

#推荐这种顺序
protoc person.proto --cpp_out=./    #末尾的'./'表示生成的cc、h文件存放在当前目录(可调)
protoc person.proto --python_out=./ #使用python进行编译
protoc person.proto --go_out=./     #使用go进行编译。注意原生protoc不包含go版本的插件,需额外装protoc-gen-go(其实就是把可执行文件protoc-gen-go放到默认搜索路径即可如/bin)

#当然调换一下参数顺序也是一样的
protoc --cpp_out=./ person.proto
protoc --python_out=./ person.proto
protoc --go_out=./ person.proto

#另外也是可以一次性编很多文件的,如下:
protoc com.tencent.*.proto --python_out=.
protoc com.tencent.epc.qidian.cc*.proto --python_out=.
protoc *.protoc --go_out=.

注意事项1:“=”号后面不能有空格,一个都不能有。否则出错

效果如下:

注意事项2:原生的protoc并不包含Go版本的插件,我们需要额外安装下protoc-gen-go(其实就是把可执行文件protoc-gen-go放到可搜索路径例如/bin下即可)。这个就是protobuf编译插件系列的go版本。

3、数据读写之write.cc写数据到本地硬盘

#include<iostream>
#include<fstream>

#include "person.pb.h"
using namespace std;
using namespace tencent;

int main()
{
    Person p1;

    p1.set_name("shuozhuo");
    p1.set_age(22);
    p1.set_phnum(17367078333);

    fstream output("./log",ios::out | ios::trunc | ios::binary);

    if (!p1.SerializeToOstream(&output)){
        cerr << "Failed to write msg."<<endl;
    }else{
        cout << "serialize success!" << endl;
    }
    return 0;
}

执行如下语句进行编译,生成可执行文件write,并执行之:

g++ person.pb.h person.pb.cc write.cc -o write `pkg-config --cflags --libs protobuf` -lpthread

注意:(1)上述指令中的“`”并不是单引号,而是键盘上按键“1”左边的那个按键对应的符号。

(2)关于pkg_config还是值得继续学习的。

(3)编译 时添加 -lprotobuf库到编译选项中。

4、编写read.cc从本地读取数据

#include<iostream>
#include<fstream>
#include "person.pb.h"

using namespace std;
using namespace tencent;

void PrintInfo(const Person &p1){
    cout << "The information is:" << endl;
    cout << "    name:" <<  p1.name() <<endl;
    cout << "    age:" << p1.age() <<endl;
    cout << "    phnum:" << p1.phnum() <<endl;
}


int main()
{
    cout<<"read Test"<<endl;
    Person p1;

    fstream input("./log",ios::in | ios::binary);

    if(!p1.ParseFromIstream(&input)){
        cerr << "Failed to parse address book." << endl;
        return -1;
    }else{
        cout << "Parse success" << endl;
    }

    PrintInfo(p1);
    return 0;
}

编译语句如下,完成后生成可执行文件read:

g++ person.pb.h person.pb.cc read.cc -o read `pkg-config --cflags --libs protobuf` -lpthread

执行read,效果如下:

5、如果编译的时候报了类似“#error This file was generated by an older version of protoc which is”的错误

说明:你的proto版本应该是有升级过,这里提示现在编译链接的pb.h、pb.cc文件是由老版本生成的不太行。

解决:生成新的pb.h、pb.cc文件即可 “protoc *.proto --cpp_out=./”。

6、尽量不要使用required。经过测试如果required字段每天序列化不会出问题,但是反序列化会失败。

这种问题查起来恶心,所以还是能避免就避免比较好。

序列化: SerializeAsString  SerializeToString

反序列化:ParseFromString   ParseFromArray

7、另外值得注意的是ParseFromString返回值为true表示正确解析

三、protobuf学习

更多学习,参见 protobuf使用手册

四、protobuf使用进阶

这里面要讲的东西很简单。那就是我们要善于利用.proto文件编译得到的pb.h、pb.cc。具体来说就是看pb.h里面都生成了哪些方法然后使用之。

1、测试pb文件名为zju.ee.master.proto,内容如下:

package zju.ee.master;

message Student{
    required string name = 1;   //姓名
    required int32  age = 2;    //年龄
    optional string addr = 3;
}


message Class{
    required string name = 1;   //班级名称
    repeated Student member = 2;    //班级成员

}

2、执行编译语句得到pb.h、pb.cc两个输出文件。其中ph.h文件放在附录处。几个用到的点会截图给到。

protoc zju.ee.master.proto --cpp_out=./

大致说一句。如果proto结构体的变量是基础变量,比如int、string等等,那么get直接根据变量名,set的时候直接调用set_xxx即可。

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

3、功能验证——optional、required单字段功能

如下图分别是required字段name和optional字段addr生成的操作方法。可见两者完全一样。所以随便拿一个字段过来验证就好了。

   

(1)has_xx:判断某个字段是否存储,就不介绍了。

(2)clear_xx():作用就是清空这个字段,在示例代码中会有体现。

(3)set_xx():提供了多种重载,一个字段是可以多次执行set_xx()的,效果就是覆盖,很直观。

(4)mutable_xx():生成一个关于原调用对象指向xx字段的指针,通过修改这个指针的指向就可以实现原对象的修改;一般用来操作嵌套结构。

(5)release_xx():

(6)set_allocated_xx():使用这个方法还是注意点好,参见 Protobuf c++使用小坑(set_allocated函数)_84970000的专栏-CSDN博客_protobuf set_allocated

问题一:如何使用如何修改pb repeated对象中的某个元素(序号为index)。

答:使用如下的 mutable_elems(int index) 组合CopyFrom即可实现。

//首先是构建要替换成的elem
::tencent::im::msg::Elem elem;
::tencent::im::msg::CrmElem* pCrmElem = elem.mutable_crm_elem();
pCrmElem->set_crm_buf(str_ccnotify);

//然后调用此语句替换
updated_rich_text.mutable_elems(index)->CopyFrom(elem);

问题二:内嵌结构如何引用

如下图所示结构如何定义个MsgDetail结构的变量?—— 很简单用"_"间隔就行。

message QidianRobotMessageCopyHippoMsg
{
    message MsgDetail
    {
        optional uint64 uint64_time = 1; //消息时间
        optional uint32 uint32_dir = 2; //消息方向 1. b2c 2. c2b
        optional bytes bytes_content = 3; //普通文本消息内容
        optional im.msg.MsgBody msg_immsg = 4; //富媒体消息,复用MsgBody结构
    }
    optional uint64 uint64_kfuin = 1; //主号
    optional uint32 uint32_channel_type = 2; 
    optional bytes bytes_robot_id = 3; //机器人id
    optional uint64 uint64_visitid = 4; //WebIm visitid
    optional MsgDetail msg_detail = 5; //消息详情
}

这样描述即可“QidianRobotMessageCopyHippoMsg_MsgDetail”,如下:

txplus::QidianRobotMessageCopyHippoMsg oRobotMsg = oQidianMsg.robot_msg();
txplus::QidianRobotMessageCopyHippoMsg_MsgDetail msg_detail = oRobotMsg.msg_detail();

注意事项:

关于copyfrom经过验证确实不能跨pb的拷贝,即使拷贝的结构在两个pb文件中一毛一样。其会引发c++程序的coredump。也就是说必须for循环逐个字段的拷贝了。