Protobuf是谷歌开发的一套用于序列化结构数据、沟通协议、数据储存等用途的工具,具有语言无关性、平台无关性、可扩展性等优点。
用于通信时,只要通信双方都使用protobuf生成的协议代码,就可以用于将数据序列化为二进制数据或者进行解析。它本身支持C++、Java、Python。在安装了AS3版本的插件后,还可以用于flash平台与其他平台的数据沟通。
如何编写.proto文件,定义AS3与后端的协议内容 (这部分内容译自google的帮助文档)
protobuf来处理数据通信可以消除通信双方语言不通的问题,这里我们要通信的一方是flash,其语言是actionscript3.0。另一端可以是任何protobuf支持的语言,C++、JAVA等等。
首先要对通信的协议进行规定,也就是双方要传输什么样的信息,有哪几种信息,每种信息都包含了哪些数据。这些规定都将写在一个.proto为后缀的文件中(当然你想以其他的后缀结尾或者不用后缀也是没有问题的,只要在后面用到这个.proto文件的批处理文件中修改成对应的文件名就OK了)。
经过批处理文件调用proto文件后,将会生成对应平台的文件,这里我们需要生成.as文件。而通信的另一端也需要根据其平台生成对应的文件。
Proto文件的书写格式:
1、消息(Message)的书写
1
2
3
4
5
6
7
|
// 客户端向服务器提交认证信息
message OP_C_AUTH_SESSION
{
required string account_name = 1;
required string password = 2;
optional int32 somenumber = 3 [
default
= 0];
}
|
上面就定义了一条由客户端发送给服务器端的登录验证消息,这条消息可以携带account_name、password和somenumber 三个字段的信息。
message开头表示这是定义了一个消息,message后面的字符串是这个消息的名称,他将作为生成的AS3类名,生成的消息类是com.netease.protobuf.Message的子类。
- 指定字段规则
required表示这个字段是必须具备的,以required定义的字段将在对应as3类中建立一个公共成员变量,例如上面的OP_C_AUTH_SESSION类将会拥有如下2个字段:
1
2
|
public
var
accountName:
String
;
public
var
password:
String
;
|
optional表示其是可有可无的,以optional定义的字段将在对应的as3类中建立一个私有变量、对应的setter、getter、以及一个标识该字段是否存在的属性,还有一个清空该字段的方法。如下:
1
2
3
4
5
6
7
8
9
|
private
var
somenumber$field:
int
;
public
function
clearSomenumber():
void
;
public
function
get
hasSomenumber():
Boolean
;
public
function
set
somenumber(value:
int
):
void
;
public
function
get
somenumber():
int
;
|
假如在将来你希望把一个required的字段修改为一个optional的字段,那么接受消息的另一端也需要做对应修改,否则当检测到一个必须的字段不存在时会丢弃该数据包。
还有一种是以repeated打头的字段,这是用于一种可以重复任意次(包括0次)的字段,在生成的AS3文件中表现为一个Array。
- 指定字段类型
可以制定的基本字段类型见下表:
上表中的AS3类型中的Uint64、Int64是自定义类。在com.netease.protobuf.*;包内,这个包的内容会自动被import到生成的消息类中。
除了这些基本类型之外,还可以使用枚举和其他message对象来为字段赋值。枚举即AS3中静态常量的用法,可以用有实际意义的常量来代替无意义的数值。
1
2
3
4
5
6
7
8
9
10
11
12
|
message SearchRequest {
enum
Corpus {
UNIVERSAL = 0;
WEB = 1;
IMAGES = 2;
LOCAL = 3;
NEWS = 4;
PRODUCTS = 5;
VIDEO = 6;
}
optional Corpus corpus = 4 [
default
= UNIVERSAL];
}
|
如果你想在一个消息内携带另一个子消息,则可以使用类型属于message的字段,如:
1
2
3
4
5
6
7
8
9
|
message SearchResponse {
repeated Result result = 1;
}
message Result {
required string url = 1;
optional string title = 2;
repeated string snippets = 3;
}
|
上面代码中,Result这个消息就是SearchResponse消息的一个子消息,它作为父消息的一个字段而存在。
一个消息的非基础类型字段(enum和message)可以在3个地方定义:
- 与父消息并列定义,即上面的SearchResponse例子
- 定义在父消息体的内部: ,即上面的SearchRequest例子以及下面的例子
1
2
3
4
5
6
7
8
|
message SearchResponse {
message Result {
required string url = 1;
optional string title = 2;
repeated string snippets = 3;
}
repeated Result result = 1;
}
|
这种称之为嵌套类型,可以嵌套message类和enum类。被嵌套的类会在生成AS3代码时新建一个与父级类同名的文件夹,并将嵌套的类生成在这个文件夹内。
3、定义在其他.proto文件中
如果想引用其他协议文件中的消息类,需要先将这个协议文件import到当前的协议文件中:
import “myproject/other_protos.proto”;
- 设置字段ID(Tag)
在required string account_name = 1;中,1就是这个字段的Tag,它是每个字段独一无二的ID,用于在序列化对象到二进制数据时做标识用。你可以设置的最小值为1,最大值为229 – 1或者说 536,870,911。但是不能使用从19000到19999这些数字,他们已经被protobuf自身所占用了。
Tag也同样在数据包中占据体积,1-15的Tag值只占用一个字节,而16-2047则会占用2个字节。所以应该将1-15提供给较频繁的消息字段使用。注意空出一些短数字留给以后可能添加的需要频繁使用的字段。
- 设置字段的缺省值
[default = 0]语法用于设置这个字段的缺省值。
2、枚举(enum)的书写
1
2
3
4
5
6
7
8
9
10
11
12
13
|
// 服务器向客户端返回认证结果
enum
AUTH_SESSION_CODES
{
AUTH_SESSION_OK = 1;
AUTH_SESSION_FAILED = 2;
AUTH_SESSION_REJECT = 3;
AUTH_SESSION_SYSTEM_ERROR = 4;
AUTH_SESSION_UNKNOWN_ACCOUNT = 5;
AUTH_SESSION_INCORRECT_PASSWORD = 6;
AUTH_SESSION_SESSION_EXPIRED = 7;
AUTH_SESSION_WAIT_QUEUE = 8;
AUTH_SESSION_BANNED = 9;
}
|
在AS3中没有枚举类,因此以上的proto代码将生成一个普通的object类。这个类有对应的静态成员变量,用以定义各种状态/消息,便于信息交互的双方理解。生成的AS3代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
package
utils.socket.protocols {
public
final
class
AUTH_SESSION_CODES {
public
static
const
AUTH_SESSION_OK:
int
=
1
;
public
static
const
AUTH_SESSION_FAILED:
int
=
2
;
public
static
const
AUTH_SESSION_REJECT:
int
=
3
;
public
static
const
AUTH_SESSION_SYSTEM_ERROR:
int
=
4
;
public
static
const
AUTH_SESSION_UNKNOWN_ACCOUNT:
int
=
5
;
public
static
const
AUTH_SESSION_INCORRECT_PASSWORD:
int
=
6
;
public
static
const
AUTH_SESSION_SESSION_EXPIRED:
int
=
7
;
public
static
const
AUTH_SESSION_WAIT_QUEUE:
int
=
8
;
public
static
const
AUTH_SESSION_BANNED:
int
=
9
;
}
}
|
于是可以在接受到服务器的返回的验证包后,对其authResult属性进行判断:
1
2
3
4
5
6
7
8
9
10
11
|
var
message:OP_S_AUTH_SESSION =
new
OP_S_AUTH_SESSION();
message .mergeFrom(bytes);
//假设bytes是我们接收到的数据包
switch
(message. authResult){
case
AUTH_SESSION_CODES. AUTH_SESSION_OK:
//do sth.
break
;
case
AUTH_SESSION_CODES. AUTH_SESSION_FAILED:
//do sth.
break
;
//…
}
|
3、还有继承、包等更多复杂格式的写法请看