protobuf 入门学习

1.安装protobuf(LINUX)

在安装protobuf时会使用到一些依赖库,如果没有安装就先执行下面的命令

Ubuntu下:

sudo apt-get install autoconf automake libtool curl make g++ unzip -y

centos下

sudo yum install autoconf automake libtool curl make gcc-c++ unzip

ProtoBuf下载地址:https://github.com/protocolbuffers/protobuf/releases

根据自己的语言下载或者选择all.zip支持全部语言
 

进入到下载的目录下

# 第⼀步执⾏autogen.sh,但如果下载的是具体的某⼀⻔语⾔,不需要执⾏这⼀步。
./autogen.sh
# 第⼆步执⾏configure,有两种执⾏⽅式,任选其⼀即可,如下:
# 1、protobuf默认安装在 /usr/local ⽬录,lib、bin都是分散的
./configure
# 2、修改安装⽬录,统⼀安装在/usr/local/protobuf下

./configure --prefix=/usr/local/protobuf

再依次执行

make // 执⾏15分钟左右
make check // 执⾏15分钟左右
sudo make install

执行完之后,需要你回忆⼀下在执⾏configure时,如果当时选择了第⼀种执⾏⽅式,也就是./configure ,那么到这就可以正常使⽤protobuf了。如果选择了第⼆种执⾏⽅式,即修改了安装⽬录,那么还需要在/etc/profile中添加⼀些内容:

sudo vim /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/

最后执行命令:

source /etc/profile

检查安装是否成功

protoc --version
libprotoc 3.21.11

2.使用protobuf

2.1-上手第一步:

创建.proto文件---  创建.proto⽂件时,⽂件命名应该使⽤全⼩写字⺟命名,多个字⺟之间⽤ _ 连接。例如:lower_snake_case.proto

2.2-了解protobuf语法

/*指定protobuf的语法版本, 不写默任proto2*/
syntax = "proto3"; 

/*这是指定命名空间,选择性添加*/
package yourname;

/*
在message中我们可以定义其属性字段,字段定义格式为:字段类型字段名=字段唯⼀编号;
字段名称命名规范:全⼩写字⺟,多个字⺟之间⽤_连接。
字段类型分为:标量数据类型和特殊类型(包括枚举、其他消息类型等)。
字段唯⼀编号:⽤来标识字段,⼀旦开始使⽤就不能够再改变
*/
message YourMessage{
  string name = 1;
  int32 age = 2;
}

该表格展示了定义于消息体中的标量数据类型,以及编译.proto⽂件之后⾃动⽣成的类中与之对应的字段类型。在这⾥展⽰了与c++语⾔对应的类型。

.proto typenotesc++ type
doubledouble
floatfloat
int32使⽤变⻓编码[1]。负数的编码效率较低⸺若字段可能为负值,应使⽤sint32代替。int32
int64使⽤变⻓编码[1]。负数的编码效率较低⸺若字段可 能为负值,应使⽤sint64代替int64
uint32使⽤变⻓编码[1]。uint32
uint64使⽤变⻓编码[1]。uint64
sint32使⽤变⻓编码[1]。符号整型。负值的编码效率⾼于 常规的int32类型。sint32
sint64使⽤变⻓编码[1]。符号整型。负值的编码效率⾼于 常规的int64类型。sint64
fixed32定⻓4字节。若值常⼤于2^28则会⽐uint32更⾼效。uint32
fixed64定⻓8字节。若值常⼤于2^56则会⽐uint64更⾼效。uint64
sfixed32定长4字节int32
sfixed64定长8字节int64
boolbool
string包含UTF-8和ASCII编码的字符串,⻓度不能超过2^32。string
bytes可包含任意的字节序列但⻓度不能超过2^32string

2.3-编译 .proto文件

protoc [--proto_path=IMPORT_PATH] --cpp_out=DST_DIR path/to/file.proto
protoc 是 Protocol Buffer 提供的命令⾏编译⼯具。
--proto_path 指定 被编译的.proto⽂件所在⽬录,可多次指定。可简写成 -I
IMPORT_PATH 。如不指定该参数,则在当前⽬录进⾏搜索。
当某个.proto ⽂件 import 其他.proto ⽂件时,
或需要编译的 .proto ⽂件不在当前⽬录下,这时就要⽤-I来指定搜索⽬录。
--cpp_out= 指编译后的⽂件为 C++ ⽂件。
OUT_DIR 编译后⽣成⽂件的⽬标路径。
path/to/file.proto 要编译的.proto⽂件。

例如:protoc --cpp_out=. contacts.proto

2.4-常见规则

1.消息类型可作为字段类型使⽤
2.可导⼊其他.proto⽂件的消息并使⽤ 需要(import "文件名")

3.// GOOGLE_PROTOBUF_VERIFY_VERSION 宏: 验证没有意外链接到与编译的头⽂件不兼容的库版本。如果检测到版本不匹配,程序将中⽌。注意,每个 .pb.cc ⽂件在启动时都会⾃动调⽤此宏。在使⽤ C++ Protocol Buffer 库之前执⾏此宏是⼀种很好的做法,但不是绝对必要的。

4.// 在程序结束时调⽤ ShutdownProtobufLibrary(),为了删除 Protocol Buffer 库分配的所
有全局对象。对于⼤多数程序来说这是不必要的,因为该过程⽆论如何都要退出,并且操作系统将负责回收其所有内存。但是,如果你使⽤了内存泄漏检查程序,该程序需要释放每个最后对象,或者你正在编写可以由单个进程多次加载和卸载的库,那么你可能希望强制使⽤ Protocol Buffers 来清理所有内容

repeated:消息中可以包含该字段任意多次(包括零次),其中重复值的顺序会被保留。可以理解为定义了⼀个数组。

举例:

syntax = "proto3";
package contacts;
// 联系⼈
message PeopleInfo {
  string name = 1; // 姓名
  int32 age = 2; // 年龄
  message Phone {
    string number = 1; // 电话号码
  }
  repeated Phone phone = 3; // 电话
}

//字段设置
PeopleInfo people_info;
for(int i = 1; ; i++) {
  cout << "请输⼊联系⼈电话" << i << "(只输⼊回⻋完成电话新增): ";
  string number;
  getline(cin, number);
  if (number.empty()) {
    break;
  }
  PeopleInfo_Phone* phone = people_info.add_phone();
  phone->set_number(number);
}

2.5-特殊类型

2.5.1-enum类型

语法⽀持我们定义枚举类型并使⽤。在.proto⽂件中枚举类型的书写规范为:
枚举类型名称:
使⽤驼峰命名法,⾸字⺟⼤写。例如: MyEnum
常量值名称:
全⼤写字⺟,多个字⺟之间⽤ _ 连接。例如: ENUM_CONST = 0;

enum PhoneType {
  MP = 0; // 移动电话
  TEL = 1; // 固定电话
}
/*
0值常量必须存在,且要作为第⼀个元素。这是为了与proto2的语义兼容:第⼀个元素作为默认
值,且值为0。
枚举类型可以在消息外定义,也可以在消息体内定义(嵌套)。
枚举的常量值在32位整数的范围内。但因负值⽆效因⽽不建议使⽤(与编码规则有关)。
*/

使用举例:

//字段声明
 message Phone
 {
    enum PhoneType
    {
        MP = 0;
        TEL = 1;
    }
    string number = 1;
    PhoneType type = 2;
 }

//设置字段
Phone phone;
phone.set_type(Phone_PhoneType::Phone_PhoneType_MP);

//获取字段
phone.PhoneType_Name(phone.type());

2.5.2-Any类型

字段还可以声明为Any类型,可以理解为泛型类型。使⽤时可以在Any中存储任意消息类型。Any类型的字段也⽤repeated来修饰。
 

使用举例:

import "google/protobuf/any.proto"; // 引⼊ any.proto ⽂件


//字段声明
message Address
{
  string home_address = 1; // 家庭地址
  string unit_address = 2; // 单位地址
}

 message Phone
 {
    enum PhoneType
    {
        MP = 0;
        TEL = 1;
    }
    string number = 1;
    PhoneType type = 2;
    google.protobuf.Any data = 3;
 }

//设置字段
Address address;
Phone phone;
...省略address的字段设置
google::protobuf::Any * data = phone.mutable_data();
data->PackFrom(address);

//获取字段
if (phone.has_data() && phone.data().Is<Address>()) {
  Address address;
  phone.data().UnpackTo(&address);
}

2.5.3-oneof类型

如果消息中有很多可选字段,并且将来同时只有⼀个字段会被设置,那么就可以使⽤ oneof 加强这个⾏为,也能有节约内存的效果。
 

使用举例

import "google/protobuf/any.proto"; // 引⼊ any.proto ⽂件


//字段声明
message Address
{
  string home_address = 1; // 家庭地址
  string unit_address = 2; // 单位地址
}

message Info
 {
    enum PhoneType
    {
        MP = 0;
        TEL = 1;
    }
    string number = 1;
    PhoneType type = 2;
    google.protobuf.Any data = 3;
    oneof other_contact { // 其他联系⽅式:多选⼀
      string qq = 4;
      string weixin = 5;
    }
 }

//设置字段
Info info;
...省略字段设置

//获取字段
switch (info.other_contact_case()) {
    case Info::OtherContactCase::kQq:
        cout << "qq号: " << info.qq() << endl;
    break;
    case Info::OtherContactCase::kWeixin:
        cout << "微信号: " << info.weixin() << endl;
    break;
    case Info::OtherContactCase::OTHER_CONTACT_NOT_SET:
    break;
}

2.5.4-map类型

语法⽀持创建⼀个关联映射字段,也就是可以使⽤map类型去声明字段类型,格式为:

map<key_type, value_type> map_field = N;
key_type是除了float和bytes类型以外的任意标量类型。value_type可以是任意类型。
map字段不可以⽤repeated修饰。
map中存⼊的元素是⽆序的。

使用举例

import "google/protobuf/any.proto"; // 引⼊ any.proto ⽂件


//字段声明
message Info
 {
    enum PhoneType
    {
        MP = 0;
        TEL = 1;
    }
    string number = 1;
    PhoneType type = 2;
    google.protobuf.Any data = 3;
    oneof other_contact { // 其他联系⽅式:多选⼀
      string qq = 4;
      string weixin = 5;
    }
    map<string, string> remark = 7; // 备注
 }

//设置字段
Info info;
for(int i = 1; ; i++) {
  cout << "请输⼊备注" << i << "标题 (只输⼊回⻋完成备注新增): ";
  string remark_key;
  getline(cin, remark_key);
  if (remark_key.empty()) {
    break;
  }
  cout << "请输⼊备注" << i << "内容: ";
  string remark_value;
  getline(cin, remark_value);
  info.mutable_remark()->insert({remark_key, remark_value});
}

//获取字段
if (info.remark_size()) {
  cout << "备注信息: " << endl;
}
for (auto it = info.remark().cbegin(); it != info.remark().cend();++it) {
  cout << " " << it->first << ": " << it->second << endl;
}

3.对比xml,json

1. XML、JSON、ProtoBuf都具有数据结构化和数据序列化的能⼒。
2. XML、JSON更注重数据结构化,关注可读性和语义表达能⼒。ProtoBuf更注重数据序列化,关注效率、空间、速度,可读性差,语义表达能⼒不⾜,为保证极致的效率,会舍弃⼀部分元信息。
3. ProtoBuf的应⽤场景更为明确,XML、JSON的应⽤场景更为丰富。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

C语言扫地僧

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值