一、简介
Google Protocol Buffer( 简称 Protobuf) 是 Google 公司内部的混合语言数据标准。他们用于 RPC 系统和持续数据存储系统。提供一个具有高效的协议数据交换格式工具库(类似Json)。
但相比于Json,Protobuf有更高的转化效率,时间效率和空间效率都是JSON的3-5倍。
可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。目前提供了 C++、Java、Python 、OC、Swift等语言的 API。总只一句话就是很好,支持多平台且与语言无关。
二、小试牛刀
2.1、编写proto文件
首先我们需要编写一个 proto 文件,定义我们程序中需要处理的结构化数据,在 protobuf 的术语中,结构化数据被称为 Message。proto 文件非常类似 java 或者 C 语言的数据定义。
package lpk;
message helloworld
{
required int32 id = 1; // ID
required string name = 2; // str
optional int32 age = 3; //optional field
repeated string gender = 4 //至少被重复0次
}
message otherMassage{
....
}
解释下上文内容,package 名字叫做 lpk,定义了一个消息 helloworld,该消息有三个成员,类型为 int32 的 id,另一个为类型为 string 的成员 name。age 是一个可选的成员,即消息中可以不包含该成员。proto3的语法解释;再修饰符上和proto2存在差异。
标量:正如上述文件格式,在消息定义中,每个字段都有唯一的一个数字标识符。这些标识符是用来在消息的二进制格式中识别各个字段的,一旦开始使用就不能够再改变。注:[1,15]之内的标识号在编码的时候会占用一个字节。[16,2047]之内的标识号则占用2个字节。所以应该为那些频繁出现的消息元素保留 [1,15]之内的标识号。切记:要为将来有可能添加的、频繁出现的标识号预留一些标识号。
2.2、编译protoc文件
写好 proto 文件之后就可以用 Protobuf 编译器将该文件编译成目标语言了。我们暂且使用 C++。
假设您的 proto 文件存放在 $SRC_DIR (你的protoc文件路径)下面,您也想把生成的文件放在同一个目录下,则可以使用如下命令:
protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/你的protoc文件
--cpp_out 输出c++文件命令;如果要生成oc文件:--objc_out 即可;
命令将生成两个文件:
lpk.helloworld.pb.h , 定义了 C++ 类的头文件
lpk.helloworld.pb.cc , C++ 类的实现文件
OC对应的文件:
LpkHelloworld.pbobjc.h
LpkHelloworld.pbobjc.m
更多语言:http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/reference/overview.html
2.3、使用protoc文件
这里做简单的读写文件事例
2.3.1写入本地文件
#include "lpk.helloworld.pb.hpp"
…
int main(void)
{
lpk::helloworld msg1;
msg1.set_id(101);
msg1.set_name(“李平宽”);
msg1.set_age(18);
// Write the new address book back to disk.
fstream output("./log", ios::out | ios::trunc | ios::binary);
if (!msg1.SerializeToOstream(&output)) {
count << "失败" << endl;
return -1;
}
return 0;
}
2.3.2 读取文件
#include "lpk.helloworld.pb.hpp"
…
void ListMsg(const lpk::helloworld & msg) {
cout << msg.id() << endl;
cout << msg.name() << endl;
}
int main(int argc, char* argv[]) {
lpk::helloworld msg1;
{
fstream input("./log", ios::in | ios::binary);
if (!msg1.ParseFromIstream(&input)) {
cerr << "读取失败" << endl;
return -1;
}
}
ListMsg(msg1);
}
运行结果:
101
李平宽
三、安装protoc
3.1 检查是否安装过或安装成功
打开终端输入 protoc --version ,如果提示有版本号则表示安装过;
lipingkuandepingmu:~ linkface$ protoc --version
libprotoc 3.6.1
3.2、安装:mac环境配置
下载地址: http://code.google.com/p/protobuf/downloads/list
两种安装方式,不过首先得安装homebrew;
首先检查电脑是否安装了homebrew,在终端输入 brew --version;
linkface$ brew --version
Homebrew 1.7.1
Homebrew/homebrew-core (git revision 0ff2c; last commit 2018-08-02)
说明已经安装过了;
如果没有安装:
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
然后使用 下面这两个命令;
brew install automake
brew install libtool
cd到你已经下载好的protoc的文件夹下;执行autogen.sh这个脚本;
./autogen.sh
然后依次执行下面命令:
./configure
make check
make
make install
检查是否安装成功
protoc --version
另外一种安装方式直接安装,
brew protoc
四、进阶
4.1 嵌套message
嵌套是一个神奇的概念,一旦拥有嵌套能力,消息的表达能力就会非常强大。
message Person {
required string name = 1;
required int32 id = 2; // Unique ID number for this person.
optional string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
required string number = 1;
optional PhoneType type = 2 [default = HOME];
}
repeated PhoneNumber phone = 4;
}
在 Message Person 中,定义了嵌套消息 PhoneNumber,并用来定义 Person 消息中的 phone 域。这使得人们可以定义更加复杂的数据结构。
4.2、import otherMessage
在一个 .proto 文件中,还可以用 Import 关键字引入在其他 .proto 文件中定义的消息,这可以称做 Import Message,或者 Dependency Message。
import common.header;
message youMsg{
required common.info_header header = 1;
required string youPrivateData = 2;
}
其中 ,common.info_header定义在common.header包内。
Import Message 的用处主要在于提供了方便的代码管理机制,类似 C 语言中的头文件。您可以将一些公用的 Message 定义在一个 package 中,然后在别的 .proto 文件中引入该 package,进而使用其中的消息定义。
Google Protocol Buffer 可以很好地支持嵌套 Message 和引入 Message,从而让定义复杂的数据结构的工作变得非常轻松愉快。
五、其他
5.1、和其他类似技术的比较
看完这个简单的例子之后,希望您已经能理解 Protobuf 能做什么了,那么您可能会说,世上还有很多其他的类似技术啊,比如 XML,JSON,Thrift 等等。和他们相比,Protobuf 有什么不同呢?
简单说来 Protobuf 的主要优点就是:简单,快。
参考:
六、介绍下OC的使用;
主要区别是生成的OC文件。
1、使用cocoapod 引入框架
pod "ProtocolBuffers"
pod "Protobuf"
这两个框架一个是协议,一个是路径配置文件;缺一不可;然后不需要任何和配置。
2、 将上面生成的文件拖入工程中
#import "LpkHelloworld.pbobjc.h"
helloworld *lpk = [[helloworld alloc] init];
lpk.id_p = 100000;
lpk.age = 18;
lpk.name = @"我是一坨牛粪";
NSData *LpkData = [lpk data];
NSLog(@"%@",LpkData);
/// 反序列化
NSError *error ;
helloworld *lpkStr = [helloworld parseFromData:LpkData error:&error];
NSLog(@"%@",lpkStr);
NSLog(@"name = %@,age = %d",lpkStr.name,lpkStr.age);
打印结果
2018-08-07 10:21:04.602563+0800 007[20329:2150888] <08a08d06 1212e688 91e698af e4b880e5 9da8e789 9be7b2aa 1812>
2018-08-07 10:21:09.552132+0800 007[20329:2150888] : {
id: 100000
name: "我是一坨牛粪"
age: 18
}
018-08-07 10:40:11.078623+0800 007[20616:2267973] name = 我是一坨牛粪,age = 18
注意:这里特别提到id_p值必须给;否则data是null;