Google Protocol Buffers总结-基础篇
转载请注明出处:http://elijahdou.github.io/
以前的公司主要是用protobuf做协议和数据传输,现在公司不用这个,为了以后方便使用,把以前的使用经验总结一下,方便以后重拾回来。
protobuf
Protocol Buffers简称protobuf或者pb,是由Google开发维护的夸语言、跨平台的 可扩展的结构化数据串行机制,官方地址Protocol Buffers。
现在常用的网络数据传输的结构化方式有:XML、JSON和Protobuf等。有很多篇介绍他们的对比和不同:blogprotobuf,json,xml,binary,Thrift之间的对比 使用 Protocol Buffers 代替 JSON 的五个原因。网上还有很多其他对比的帖子。不过简言之,protobuf的优缺点如下:
- 效率高:用protobuf序列化后的数据大小是json的10+分之一,xml格式的20+分之一,是二进制序列化的10+分之一。protobuf虽然会需要一定的本地编解码成本,但是数据压缩体积变小,网络传输速度加快,在效率和体验上的提升非常可观。
- 语法简答易学:保证做一次就会。
- 夸语言、跨平台、可扩展:不收平台的限制,并且无偿向后兼容。
- 缺点:唯一的缺点就是可读性差,因为数据都是编码后的,不像XML和JSON可读。
安装
Google的官方版本,只提供了JAVA C++ C 和 Python版本的pb,但是在github的开源项目上有OC和最新的SWIFT语言的版本。git还在维护的版本有qzix/protobuf-objc 和 alexeyxo/protobuf-objc,其中后者还支持swift,推荐使用后者,同时安装步骤也在该页。借助homebrew安装更方便,如果已安转homebrew请跳过第一步,摘录如下:
操作前请先获取系统权限,sudo一下
ruby -e "$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)"
brew install automake
brew install libtool
brew install protobuf
ln -s /usr/local/Cellar/protobuf/2.6.0/bin/protoc /usr/local/bin (optional)
git clone git@github.com:alexeyxo/protobuf-objc.git
./build.sh
Add /src/runtime/ProtocolBuffers.xcodeproj in your project.
使用-编写.proto
源文件
使用pb的第一步就是编写.proto
源文件,选用自己顺手的任一款编辑器即可。对应OC中的类,protobuf
称为message
消息,你会发现其语法结构与常见语言类似,所以简单易学。举例代码入下:
message Person {
required int32 age = 1;
repeated string name = 2;
optional string email = 3;
}
.proto
源文件格式从上面的源码中可以猜测一个大概,举例并解析如下:
message msgName(消息名称) {
required/repeated/optional(限定符) dataType(数据类型) variableName(变量名) =(赋值号) serialNumber(序号,必须从1开始,不能跳跃);(结束的分号)
}
关于msgName
.proto
中可以有多个message,dateType也可以是自定义message,但是源文件最好与主message同名,即mainMsgName.proto
,否则在使用的时候会出现一些名称不一致的问题,虽然仍能影响使用,但是会增加使用难度。
tips:一些公共的协议类的.proto
源文件,可以独立成一个文件,在其他使用到的该消息的文件的开头位置用import引入即可,如import "myproject/CommonMessages.proto"
关于required/repeated/optional(限定符)
- 在每个消息中必须至少留有一个required类型的字段(在实际使用中,可以全部是optional的),这样的变量在相应类的初始化时必须初始化,否则报错。
- 每个消息中可以包含0个或多个optional类型的字段,从名字可以看出,可选的,可以不初始化或者不使用该字段。推荐尽可能使用optional限定符,即使协议文档规定为required,两者仍可以兼容,并且避免以后协议修改造成的重写和重新编译。
- repeated表示的字段可以包含0个或多个数据。需要说明的是,这一点有别于C++/Java中的数组,因为后两者中的数组必须包含至少一个元素,可以理解为repeated也是一种可选类型。
- 如果打算在原有消息协议中添加新的字段,同时还要保证老版本的程序能够正常读取或写入,那么对于新添加的字段必须是optional或repeated。道理非常简单,老版本程序无法读取或写入新增的required限定符的字段。
关于dataType(数据类型)
这个很简单,数据类型名字也很好记,有表格如下:
.proto Type | Notes | C++ Type | Java Type |
double | double | double | |
float | float | float | |
int32 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead. | int32 | int |
int64 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead. | int64 | long |
uint32 | Uses variable-length encoding. | uint32 | int |
uint64 | Uses variable-length encoding. | uint64 | long |
sint32 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s. | int32 | int |
sint64 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s. | int64 | long |
fixed32 | Always four bytes. More efficient than uint32 if values are often greater than 228. | uint32 | int |
fixed64 | Always eight bytes. More efficient than uint64 if values are often greater than 256. | uint64 | long |
sfixed32 | Always four bytes. | int32 | int |
sfixed64 | Always eight bytes. | int64 | long |
bool | bool | boolean | |
string | A string must always contain UTF-8 encoded or 7-bit ASCII text. | string | String |
bytes | May contain any arbitrary sequence of bytes. | string | ByteString |
oc语言的数据类型参考C++ 版本即可,此外pb还支持枚举类型的数据定义,举例如下:
enum UserStatus {
OFFLINE = 0;
ONLINE = 1;
}
message UserInfo {
required int64 acctID = 1;
required string name = 2;
required UserStatus status = 3;
}
message LogonRespMessage {
required LoginResult logonResult = 1;
required UserInfo userInfo = 2;
}
使用-编译.proto
源文件
完成源文件的编写之后,就是要编译该源文件,编译的环境和命令在安装时就已经配置好了,我们只需要调用指令编译即可。编译得到的结果对于OC来说会生成一对.h .m
文件,即一个类,类名即为.proto
文件的名字;对于swift语言因该是生成一个.swift
文件。编译指令以如下:
protoc .proto源文件路径 --objc_out="输出文件路径"
protoc .proto源文件路径 --swift_out="输出文件路径"
使用-项目中引用
将上一步生成的目标文件引入到项目中,就可以当做普通的类来使用了。
tips:不同的oc版本的pb的开源项目略有不同,不过大同小异,如类名的命名方式等,一个较大的区别是有的支持ARC,有的不支持ARC 需要禁用,所以推荐用cocopods引入。
实际使用举例,以前面的Person.proto为例,常规的使用步骤如下(当然要导入必要的头文件):
- (void)viewDidLoad
{
// 串行化一个,Person结构的数据
NSData *personData = [self makePersonDataWithAge:21
names:@[@"测试", @"test"]
email:@"test@zuche.com"];
NSLog(@"data : %@", personData); // 用于数据传输
// 反串行化得到的串行化数据
Person *aPerson = [Person parseFromData:personData];
NSLog(@"person age : %d\n person names : %@\n person email : %@\n", aPerson.age, aPerson.names, aPerson.email); // 接收到的数据的还原
}
- (NSData *)makePersonDataWithAge:(int32_t)age names:(NSArray *)names email:(NSString *)email
{
PersonBuilder *builder = [[PersonBuilder alloc] init]; // 有的版本的是这样的Person_Builder,不过大同小异
builder.age = age;
builder.email = email;
[builder setNamesArray:names];
Person *args = [builder build];
return [args data];
}
swift Demo
总结
这只是pb使用的基础用法,看源码能得到很多收获。pb的精髓在于其编码方式,有兴趣的可以看一下。这一篇只是讲了pb的基本使用,再其使用过程中,还有很多需要注意的点,这一篇中就不讲了。
补充延伸阅读