使用Google Protocol Bufffers进行通信(Ruby & ObjC & Java)

Mar 22, 2013 at 17:04:05

一、概览

在后台需要与多种终端如iPhone,Android,Web或者WinPhone之类的不同平台作通信的时候,常常需要使用一种中间的通信协议,并且使用通用数据类型如XML。

Protocol Buffers(以下简称protobuf)就是类似于XML这样的东西,可以在后台与多终端间进行通信,但是比它要远强大的多。

Protobuf由Google出品,08年的时候Google把这个项目开源了,截至发稿已发展到2.5.0版本,官方支持C++,Java和Python三种语言,但是由于其设计得很简单,所以衍生出很多第三方的支持,基本上常用的PHP,C,Actoin Script,Javascript,Perl等多种语言都已有第三方的库

二、简介

Protobuf比起XML有很多优势,首先是更简单了

一个XML文件我们编写的时候需要定义各种各种的节点,而Proto文件则使用Google定义的有点像Java的语言来编写,简洁很多。

XML长得像这样:

<person>
    <name>John Doe</name>
    <email>jdoe@example.com</email>
</person>

而proto文件则长得像这样:

# Textual representation of a protocol buffer.
# This is *not* the binary format used on the wire.
person {
  name: "John Doe"
  email: "jdoe@example.com"
}

其次是快了。proto文件是给程序猿阅读的时候用的,真正传输的时候是序列化的二进制数据,比起XML要小得多,解析起来也快得多。

第三是可以直接生成供程序使用的类。XML文件接收后我们还得手工去解析然后转化为可以使用的对象,但是PB文件接收后,PB的库就已经帮我们转化为对应的数据类了。

Protobuf主要分为两个部分,一是编译器protoc,一是分包组包用的库。
编译器是用来编译proto文件为目标语言的,比如一个上面那个 Person.proto 文件,我可以用 protoc 直接编译成C++类 Person,用的时候就很方便了:

cout << "Name: " << person.name() << endl;
cout << "E-mail: " << person.email() << endl;

对应的可以变成ObjC的类、Java的类等等。

在我接收到数据之后,我可以使用 parseFrom 方法直接对 byte 数据进行解析,得到一个可以用的类,如Java例子:

byte[] msg = b.getByteArray(PERSON_MSG_EXTRA); // 接收byte数据
person = Person.parseFrom(msg).toBuilder(); // 直接解析为对应的类

三、使用

下面几节介绍一下一个服务器对多种终端通信的实际例子。服务器上使用 Ruby on Rails,终端有 iOS 和 Android。也就是 Ruby 和 ObjC 、 Android 之间的通信了。

1.定义proto文件

首先,最重要的是定义好 proto 文件:

package Tutorial;

message Source {
required string title = 1;
required string description = 2;
optional int id = 3;
}

message SourceAllResponse {
required uint32 count = 1;
repeated Source source_list = 2;
}

有点像 Java 语法,有个 package 在最前面,这个 Tutorial 在 Java 里面就会生成一个类为 Tutorial,对于 Java,有个可选的选项,可以填上包名。

option java_package = "com.example.protobuf";
option java_outer_classname = "Tutorial";

如果是 Ruby 则生成module Tutorial, ObjC 则是 TutorialRoot,用来管理 extensionRegistry(暂时还没搞懂用来干啥)。

message 是对应一个类,required 是必须字段,通信发起方必有的字段,对应 optional 则是可选的。如果后台某天升级了协议要增加返回字段,那么新增的字段就必须是 optional 的,以防客户端接收失败(当然如果能保证客户端永远最新那是另一回事)。repeated 可以看成是返回多个同类型的值,如一个数组,像SourceAllResponse会返回所有的source,第一个是source的个数,第二个是多个source对象。

protobuf 数据类型看起来像 C++ 有 double, float, int32等等,在 https://developers.google.com/protocol-buffers/docs/proto 里有表格详细说明。

2.编译成对应语言

定义完proto文件后,使用官方的 protoc 可以对其进行编译。下载地址在: https://code.google.com/p/protobuf/downloads/list

如果是 Mac OS X 或者 Linux ,需要下载官方的源码,解压后根据官方的 README.txt 里的说明:

$ ./configure
$ make
$ make check
$ make install

编译安装,然后就可以使用protoc命令了。windows用户则下载 *.win32.zip 文件后里面就有 protoc.exe 了,命令行下使用就行。把上面那个 proto 结构体保存成 tutorial.proto,然后就可以用 protoc 编译了。

3.Ruby的用法:

Ruby 可以用 codekitchen 的 ruby-protocol-buffers 或者 macks 的 ruby-protobuf,我用前者。ruby-protobuf 没有能使用成功。

首先 Gemfile 里面加入:

gem 'ruby-protocol-buffers'

然后 bundle install。
使用的方法就是

ruby-protoc Tutorial.proto

注意:ruby-protocol-buffers依赖于官方的Protoc,所以需要你这台机器装了 protoc 才行。
如果用 ruby-protobuf 则是:

rprotoc examples/addressbook.proto

而且不依赖官方的protoc,不过我没使用成功就是了。
编译后会生成 tutorial.pb.rb,在ruby中:

require 'tutorial.pb'
aResponse = Tutorial::SourceAllResponse.new
aResponse.count = sources.count
//...
send_data aResponse

就可以直接使用proto来通信了。

4.JAVA的用法:

protoc --java_out=. tutorial.proto

会生成 Tutorial.java ,引入到工程里面,这时会发现一对 Error,因为还没有引入 jar 包。在解压好的 protobuf 源码目录, cd 到 java 目录里面,查看 README.txt 文件发现我们可以使用 Maven 对其进行编译。我在 Mac OS 上没编译成功, Linux 可能比较好编。

$ protoc --version
$ mvn test
$ mvn install
$ mvn package

完了就会发现在 target 文件夹里面有 jar 包了。
image
然后引入这个 jar 包,注意,如果你用Eclipse,除了 Build Path里面加了jar包,还得把它放进libs目录,否则只能编译不能使用(被这个坑惨了T_T)。

image

5.iOS(ObjC)

Protobuf 官方不支持 ObjC 需要使用别人写的库,https://code.google.com/p/metasyntactic/wiki/ProtocolBuffers 其实就是作为 protoc 的一个插件而已。这个库已经几年没更新了,还是 2.2 版本的 protobuf,不过由于 protobuf 良好的向上向下兼容,用什么版本其实无所谓,协议没有变。

首先到这里下载源码 http://code.google.com/p/metasyntactic/downloads/list ,完了根据官方的方法,到解压目录下:

./autogen.sh
./configure
make

使用的时候就

protoc --proto_path=src --objc_out=build/gen src/foo.proto src/bar/baz.proto

但是由于我的 Mac 里面已经装了官方的 protoc 了,所以我的命令带改成在源码的 src 文件夹下

./protoc --proto_path=. --objc_out=. /PATH_TO_TUTORIAL/Tutorial.proto

可以使用 shell 脚本来搞定这个,直接在 XCode 的 Build Phases 里面加个 Run Script,然后就会在每次编译的时候去编译这个 proto 文件了。编译后把生成的 Tutorial.pb.h 和 Tutorial.pb.m 文件加进工程,同样编译不过,还需要添加第三方库。

image

把源码目录下,objectiveC里面的所有Classes加入工程,然后编辑你的 prefix.pch 文件,import一下protobuffer

image

大工告成,可以接收服务器下发的PB消息了。

NSData * aData = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://xxxx"]];

SourceAllResponse * aResponse = [SourceAllResponse parseFromData:aData];

三、总结

Protobuffer 在一个后台对付多终端的通信方面还是非常好用的,方便、可扩展是它的特点,当然对于后台开发的同学来说还有性能上的优势。