01protobuf的安装编译与基本使用
protobuf
一、简介
protobuf
(protocol buffer) 是谷歌内部的混合语言数据标准。通过将结构化的数据进行序列化(串行化),用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。
我们说的 protobuf
通常包括下面三点:
- 一种二进制数据交换格式。可以将 C++ 中定义的存储类的内容 与 二进制序列串 相互转换,主要用于数据传输或保存
- 定义了一种源文件,扩展名为
.proto
(类比.cpp
文件),使用这种源文件,可以定义存储类的内容 - protobuf有自己的编译器
protoc
,可以将.proto
编译成.cc
文件,使之成为一个可以在 C++ 工程中直接使用的类
序列化:将数据结构或对象转换成二进制串的过程。反序列化:将在序列化过程中所产生的二进制串转换成数据结构或对象的过程。
二、定义proto文件
2.1 message
介绍
message
:protobuf
中定义一个消息类型是通过关键字message
字段指定的,这个关键字类似于C++/Java中的class关键字。使用protobuf编译器将proto
编译成C++代码之后,每个message
都会生成一个名字与之对应的C++类,该类公开继承自google::protobuf::Message
。
2.2 message
消息定义
创建tutorial.person.proto
文件,文件内容如下:
// FileName: tutorial.person.proto
// 通常文件名建议命名格式为 包名.消息名.proto
// 表示正在使用proto2命令
syntax = "proto2";
//包声明,tutorial 也可以声明为二级类型。
//例如a.b,表示a类别下b子类别
package tutorial;
//编译器将生成一个名为person的类
//类的字段信息包括姓名name,编号id,邮箱email,
//以及电话号码phones
message Person{
required string name = 1; // (位置1)
required int32 id = 2;
optional string email = 3; // (位置2)
enum PhoneType{ //电话类型枚举值
MOBILE = 0; //手机号
HOME = 1; //家庭联系电话
WORK = 2; //工作联系电话
}
//电话号码phone消息体
//组成包括号码number、电话类型 type
message PhoneNumber{
required string number = 1;
optional PhoneType type = 2 [default = HOME]; // (位置3)
}
repeated PhoneNumber phones = 4; // (位置4)
}
// 通讯录消息体,包括一个Person类的people
message AddressBook {
repeated Person people = 1;
}
2.3 字段解释
2.3.1 包声明
proto
文件以package
声明开头,这有助于防止不同项目之间命名冲突。在C++中,以package
声明的文件内容生成的类将放在与包名匹配的namespace
中,上面的.proto
文件中所有的声明都属于tutorial
。
//在proto文件中
package IM.BaseDefine;
//在生成的C++文件中
namespace IM {
namespace BaseDefine {
//.....
} // namespace BaseDefine
} // namespace IM
2.3.2 字段规则
required
:消息体中必填字段,不设置会导致编解码异常。(例如位置1)optional
: 消息体中可选字段,可通过default关键字设置默认值。(例如位置3)repeated
: 消息体中可重复字段,重复的值的顺序会被保留(例如位置4)。其中,proto3默认使用packed方式存储,这样编码方式比较节省内存。
2.3.3 标识号
标识号
:在消息体的定义中,每个字段都必须要有一个唯一的标识号,标识号是[0,2^29-1]范围内的一个整数。以Person为例,name=1,id=2, email=3, phones=4 中的1-4就是标识号。
2.3.4 数据定义
许多标准的简单数据类型都可以用作message
字段类型,包括bool
,int32
,float
,double
和string
。还可以使用其他message
类型作为字段类型在消息体中添加更多结构。在上面的示例中,Person
包含PhoneNumber message
, 而AddressBook
包含Person message
。甚至可以定义嵌套在其他message中的message类型。例如,上面的PhoneNumber
定义在Person
。
2.3.5 函数方法
用message
关键字声明的的消息体,允许你检查、操作、读、或写整个消息,包括解析二进制字符串,以及序列化二进制字符串。除此之外,也定义了下列方法:
Person:缺省的构造函数。
~Person():缺省的析构函数。
Person(const Person& other):拷贝构造函数。
Person& operator=(const Person& other):
赋值 (Assignment )操作符。
const UnknownFieldSet& unknown_fields() const:
返回当解析信息时遇到的未知字段的集合。
UnknownFieldSet* mutable_unknown_fields():
返回当前解析信息时遇到的未知字段的集合的一个mutale指针。
//例如
class IpAddr : public ::google::protobuf::MessageLite {
public:
IpAddr();
virtual ~IpAddr();
IpAddr(const IpAddr& from);
inline IpAddr& operator=(const IpAddr& from) {
CopyFrom(from);
return *this;
}
inline const ::std::string& unknown_fields() const {
return _unknown_fields_;
}
inline ::std::string* mutable_unknown_fields() {
return &_unknown_fields_;
}
static const IpAddr& default_instance();
//.......................................
}
三、编译proto文件
可以执行以下protoc
命令对.proto
文件进行编译,生成对应的c文件。Linux系统通过 help protoc
查看protoc
命令的使用详解。
protoc -I=$SRC_DIR --cpp_out=$DST_DIR xxx.proto
$SRC_DIR:proto
所在的源目录--cpp_out
生成C++代码$DST_DIR
生成代码的目标目录xxx.proto
:要针对哪个proto
文件生成接口,例如tutorial.person.proto
编译完成后,将生成2个文件 tutorial.pb.h
和tutorial.pb.c
其中tutorial
表示包名,pb是protobuf
的缩写。
此外,protocol buffer
编译器为.proto
文件中定义的消息的每个字段生成一套存取器方法:
对于 message Person
中的 required int32 id = 2
,编译器将生成下列存取器方法:
bool has_id() const
: 用于判断字段id
是否存在。如果字段被设置,返回true。int32 id() const
: 返回字段id
的当前值,如果字段没有被设置,返回缺省值。void set_id(int32 value)
: 设置字段id
的值。调用此方法后,has_id()
将返回true
以及id()
将返回value
。void clear_id()
:清除字段的值。调用此方法后,has_id()
将返回false
以及id()
将返回缺省值。
当然,对于其他类型的字段,编译器也会生成不同的存取方法,这里就不一一列举了。
#!/bin/sh
SRC_DIR=./
DST_DIR=./gen
#C++
mkdir -p $DST_DIR/cpp
protoc -I=$SRC_DIR --cpp_out=$DST_DIR/cpp/ $SRC_DIR/*.proto
#JAVA
mkdir -p $DST_DIR/java
protoc -I=$SRC_DIR --java_out=$DST_DIR/java/ $SRC_DIR/*.proto
#PYTHON
mkdir -p $DST_DIR/python
protoc -I=$SRC_DIR --python_out=$DST_DIR/python/ $SRC_DIR/*.proto
把生成的编程语言的文件移动工程文件夹中
#!/bin/sh
CPP_DIR=../project/pb/protocol
DST_DIR=./gen
#C++
mkdir -p ../project/pb/protocol
cp $DST_DIR/cpp/* $CPP_DIR/
#rm -rf ./gen
四、使用message
//获取person实例:
tutorial::AddressBook addressBook;
Person *person =
addressBook.add_people();
//写入一个message:
person->set_id(id);
getline(cin,*person->mutable_name());
//读取一个message:
person.id();
person.name();
person.email();
扩展一个message:
- 你不得更改任何现有字段的字段编号
- 你可以删除
optional
或repeated
属性的字段 - 你可以添加新的
optional
或repeated
字段,但必须使用新的标记
五、protobuf的优点
性能方面
- 序列化后,数据大小可缩小3倍
- 序列化速度快
- 传输速度快
使用方面
- 使用简单:
proto
编译器自动进行序列化和反序列化 - 维护成本低:多平台只需要维护一套对象协议文件,即
.proto
文件 - 可扩展性好:不必破坏旧的数据格式,就能对数据结构进行更新
- 加密性好:http传输内容抓包只能抓到字节数据
使用范围
- 跨平台、跨语言、可扩展性强
六、总结
知乎链接:
上面的原始链接:
比较官方的:
补充
七、编译protobuf框架
这里编译的是protobuf-2.6.1
#!/bin/bash
PROTOBUF=protobuf-2.6.1
CUR_DIR=./
# 判断当前用户是不是root
check_user() {
if [ $(id -u) != "0" ]; then
echo "Error: You must be root to run this script, add \" sudo \""
exit 1
fi
}
build_protobuf(){
tar -xf $PROTOBUF.tar.gz
cd $PROTOBUF
./configure --prefix=$CUR_DIR/protobuf
make
make install
cd ..
mkdir -p ./project/pb/lib/linux/
cp ./protobuf/lib/libprotobuf-lite.a ./project/pb/lib/linux/
cp -r ./protobuf/include/* ./project/pb/
}
check_user # 检查用户,判断是不是root
build_protobuf
编译生成三个文件:
- bin:可执行文件
protoc
- lib:动态库和静态库
- include:文件
为了能够使用这个protoc
工具:
因为程序使用了动态库,需要把头文件和库文件拷贝到系统目录中
#!/bin/bash
# 判断当前用户是不是root
check_user() {
if [ $(id -u) != "0" ]; then
echo "Error: You must be root to run this script, add \" sudo \""
exit 1
fi
}
work()
{
cp -r include/* /usr/local/include/
cp -r lib/* /usr/local/lib/
sudo ldconfig
}
check_user
work
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人