简介
Protocol Buffers (a.k.a., protobuf)
是google 的一种数据交换的格式,它独立于语言,独立于平台。提供了三种语言的实现:java、c++ 和 python,每一种实现都包含了相应语言的编译器以及库文件。由于它是一种二进制的格式,比使用 xml 进行数据交换快许多。可以把它用于分布式应用之间的数据通信或者异构环境下的数据交换。作为一种效率和兼容性都很优秀的二进制数据传输格式,可以用于诸如网络传输、配置文件、数据存储等诸多领域。
相对于传统的Json、XML数据存储格式,PB是一种更加轻量化、更加高效的结构化数据存储格式。
基本用法
数据结构体:
message message_name{message_body;}
message_body格式:
例如required int32 query = 1[defaut=10];
形式为:rule type name = value[other_rule];
规则:
required表示必须具有该值域;
optional表示可选的值域;
repeated表示可重复的值域(即>=0);
其中requered/optional是常用rule,而repeated则不常用同时因为是历史遗留现使用repeated int32 samples=4[packed=true];形式;
value值:
value值最小为1,是底层编码时使用其中1-15占一位,>15则会占多位;
不同的message中的value值互不干扰,常以1开始计数。
数据类型之基本类型:
.proto Type C++ Type Java Type
double double double
float float float
int32 int32 int
int64 int64 long
uint32 uint32 int
uint64 uint64 long
sint32 int32 int
sint64 int64 long
fixed32 uint32 int
fixed64 uint64 long
sfixed32 int32 int
sfixed64 int64 long
bool bool boolean
string string String
bytes string ByteString
数据类型之复杂类型:
复杂类型主要包括:枚举,其他message,groups等。
枚举定义例如:enum Corpus{WEB=0;LOCAL=1}
枚举定义在message中。
可以使用其他message作为类型来定义成员。
groups我的理解有些像C++中的union结构。
嵌套定义:
可以嵌套定义message结构,而嵌套定义的message被其他message作为成员类型时需要形式为outmessage.inmessage形式。
包结构:
定义形式:package foo.bar;
对应C++中则生成两个命名空间foo和bar,且bar定义在foo中;
可以通过import "myproject/other_protos.proto";来引入.proto文件;
引用其他package中message时需要完整的package路径;
Services:
主要用于RPC系统中,在.proto中定义接口;
定义形式如例子:
service SearchService {
rpc Search(SearchRequest) return (SearchResponse);
}
.proto文件编译:
格式:
protoc -–proto_path=(.proto文件路径) -–cpp_out=(.cc .java生成文件路径) (.proto文件路径)/?.proto
-–proto_path 简化为: --I
其中可根据需要更改:cpp_out选项为java_out/python_out。
例子:
protoc -I=./ --cpp_out=./ model.proto
复制代码
简单实例
测试环境:
Ubuntu 18.04
GCC 7.2.0
protoc 3.15.8
- 编写
.proto
文件
syntax = "proto3";
package test;
message person
{
int32 id = 1; // ID
string name = 2; // str
repeated int32 email = 3; //optional field
}
复制代码
Protobuf 有两个大版本,proto2 和 proto3,同比 python 的 2.x 和 3.x 版本,如果是新接触的话,同样建议直接入手 proto3 版本。所以下文的描述都是基于 proto3 的。
proto3 相对 proto2 而言,简言之就是支持更多的语言(Ruby、C#等)、删除了一些复杂的语法和特性、引入了更多的约定等。
syntax = "proto3"; // PB协议版本
import "google/protobuf/any.proto"; // 引用外部的message,可以是本地的,也可以是此处比较特殊的 Any
package jet.protobuf; // 包名,其他 proto 在引用此 proto 的时候,就可以使用 test.protobuf.PersonTest 来使用,
// 注意:和下面的 java_package 是两种易混淆概念,同时定义的时候,java_package 具有较高的优先级
option java_package = "com.jet.protobuf"; // 生成类的包名,注意:会在指定路径下按照该包名的定义来生成文件夹
option java_outer_classname="PersonTestProtos"; // 生成类的类名,注意:下划线的命名会在编译的时候被自动改为驼峰命名
message PersonTest {
int32 id = 1; // int 类型
string name = 2; // string 类型
string email = 3;
Sex sex = 4; // 枚举类型
repeated PhoneNumber phone = 5; // 引用下面定义的 PhoneNumber 类型的 message
map<string, string> tags = 6; // map 类型
repeated google.protobuf.Any details = 7; // 使用 google 的 any 类型
// 定义一个枚举
enum Sex {
DEFAULT = 0;
MALE = 1;
Female = 2;
}
// 定义一个 message
message PhoneNumber {
string number = 1;
PhoneType type = 2;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
}
}
复制代码
- 生成对应类
protoc test.proto --cpp_out ./
复制代码
会在当前目录下生成两个文件test.pb.cc test.pb.h
- 编写测试用的c++代码
#include <stdio.h>
#include <string.h>
#include "test.pb.h"
using namespace std;
int main()
{
test::person a;
a.set_id(8);
a.set_name("Kios");
string tmp;
bool ret = a.SerializeToString(&tmp);
if(ret)
{
cout << "encode success!" << endl;
}
else
{
cout << "encode failed!" << endl;
}
test::person b;
ret = b.ParseFromString(tmp);
if(ret)
{
cout << "decode success!\n id = " << b.id() << "\n name = " << b.name().c_str() << endl;
}
else
{
cout << "decode faild!\n" << endl;
}
return 0;
}
复制代码
- 指定链接库并编译
g++ test.pb.cc main.cpp -o main.o -L/usr/local/protobuf/lib/ -I/usr/local/protobuf/include/ -I./ -lprotobuf --std=c++11
//值得注意的是要记得指定c++版本
复制代码
5.查看运行结果
(base) seemmo@ubuntu:~/projects/proto_try$ ./main.o
> encode success!
> decode success!
> id = 8
> name = Kios
复制代码
需要注意的一些环境问题
command not found
一般为环境变量未设置好,将<
.../protobuf/bin
>添加至PATH
- 编译通过后运行时报
error while loading shared libraries: libprotoc.so.9: cannot open shared object file:No such...
动态链接库设置问题
第一步:创建 /etc/ld.so.conf.d/libprotobuf.conf 输入 /usr/local/protobuf/lib/ 执行 sudo ldconfig
第二步:vim /etc/profile --> 输入export LD_LIBRARY_PATH=/usr/local/protobuf/lib:$LD_LIBRARY_PATH --> # source /etc/profile
复制代码
临时使用也可以直接输入:
export LD_LIBRARY_PATH = /usr/local/protobuf/lib:$LD_LIBRARY_PATH
,
使用export
赋值的变量能够被子进程继承,而直接使用=
的不能;
但是在子进程中,无论使用export
还是=
都无法改变父进程的值。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END