其实Flash上做通讯很多情况都选择AMF,毕竟他是AS内部基于对象进制序列协议,容量小效率高。但有时为了去调用一些已经有的Tcp服务,而这些服务并不是提供AMF支持;这时你就不得不实现一个协议的分析。其实AS提ByteArray提供了很多write和read方法,这样使我们应用起来非常方便。以下是用AS简单封装基于消息头描述大小的协议分析器。
为了更好地管理消息,通过一接口来制写消息写入和读取规范。
package
Beetle.AS
{
import
flash.utils.ByteArray;
public
interface
IMessage
{
function
Load(data:Reader):
void
;
function
Save(data:Writer):
void
;
}
}
|
接口比较简单分别是保让到流中和从流中获取,其中Reader和Writer都派生于ByteArray对象;为什么没有直接用ByteArray呢?其原因就可以自己实现更高级的信息获取方法.
package
Beetle.AS
{
import
flash.net.getClassByAlias;
import
flash.utils.ByteArray;
import
flash.utils.getDefinitionByName;
import
mx.controls.Image;
public
class
Reader
extends
ByteArray
{
public
function
Reader()
{
super
();
}
public
function
ReadMessages(className:
String
):Vector.<IMessage>
{
var
results:Vector.<IMessage> =
new
Vector.<IMessage>();
for
(
var
i:
int
=
0
;i<readInt();i++)
{
var
msg:IMessage = IMessage(getDefinitionByName(className));
results.push(msg);
}
return
results;
}
}
}
package
Beetle.AS
{
import
flash.utils.ByteArray;
public
class
Writer
extends
ByteArray
{
public
function
Writer()
{
super
();
}
public
function
WriteMessages(items:Vector.<IMessage>):
void
{
writeInt(items.length);
for
each
(
var
item:IMessage
in
items)
{
item.Save(
this
);
}
}
}
}
|
基础规则都构建好了,下面就开始做协议分析部分。
package
Beetle.AS
{
import
flash.net.Socket;
import
flash.utils.ByteArray;
import
flash.utils.Endian;
import
mx.graphics.shaderClasses.ExclusionShader;
public
class
HeadSizeOfPackage
{
public
function
HeadSizeOfPackage()
{
mWriter.endian = Endian.LITTLE_ENDIAN;
mReader.endian = Endian.LITTLE_ENDIAN;
}
private
var
mMessageReceive:Function;
//消息接收回调函数
public
function
get
MessageReceive():Function
{
return
mMessageReceive;
}
public
function
set
MessageReceive(value:Function):
void
{
mMessageReceive = value;
}
//写入消息类型标识
protected
function
WriteMessageTag(message:IMessage,data:Writer):
void
{
throw
new
Error(
"WriteMessageTag not implement!"
);
}
//获取消息对象
protected
function
GetMessageByTag(data:Reader):IMessage
{
throw
new
Error(
"GetMessageByTag not implement!"
);
}
private
var
mReader:Reader =
new
Reader();
private
var
mSize:
int
=
0
;
//导入当前Socket接收的数据
public
function
Import(socket:Socket):
void
{
socket.endian = Endian.LITTLE_ENDIAN;
while
(socket.bytesAvailable>
0
)
{
if
(mSize==
0
)
{
mSize= socket.readInt()-
4
;
mReader.clear();
}
if
(socket.bytesAvailable>= mSize)
{
socket.readBytes(mReader,mReader.length,mSize);
var
msg:IMessage = GetMessageByTag(mReader);
msg.Load(mReader);
if
(MessageReceive!=
null
)
MessageReceive(msg);
mSize=
0
;
}
else
{
mSize= mSize-socket.bytesAvailable;
socket.readBytes(mReader,mReader.length,socket.bytesAvailable);
}
}
}
private
var
mWriter:Writer =
new
Writer();
//发磅封装的协议数据
public
function
Send(message:IMessage,socket:Socket):
void
{
socket.endian = Endian.LITTLE_ENDIAN;
mWriter.clear();
WriteMessageTag(message,mWriter);
message.Save(mWriter);
socket.writeInt(mWriter.length+
4
);
socket.writeBytes(mWriter,
0
,mWriter.length);
socket.flush();
}
}
}
|
协议分析器的实现比较简单,基础功能有消息封装,对流进行分析还源对象并把消息回调到指定的函数中.MessageReceive是一个指向函数的属性,用于描述消息接收工作其原理类似于C#的委托。分析器中还有两个方法需要派生类重写WriteMessageTag和GetMessageByTag,其主要作用是写入消息类型标记和根据读取的标记信息创建相关联的对象。
Send方法是一个协议包装过程,主要把对象写入流的信息加工后用指定的Socket对象发送出去。
public
function
Send(message:IMessage,socket:Socket):
void
{
socket.endian = Endian.LITTLE_ENDIAN;
mWriter.clear();
WriteMessageTag(message,mWriter);
message.Save(mWriter);
socket.writeInt(mWriter.length+
4
);
socket.writeBytes(mWriter,
0
,mWriter.length);
socket.flush();
}
|
工作原理是通过消息接口的Save方法把对象信息写入到流中,第一步是先写入消息类型标记具体写入方法由派生类来确用string或int都可以,然后再写入消息内容;最后计算所有数据长度的头写入到socket再写入信息流即可。
Import方法是一个数据导入工作,主要负责从Socket中读取数据进行加载分析。
public
function
Import(socket:Socket):
void
{
socket.endian = Endian.LITTLE_ENDIAN;
while
(socket.bytesAvailable>
0
)
{
if
(mSize==
0
)
{
mSize= socket.readInt()-
4
;
mReader.clear();
}
if
(socket.bytesAvailable>= mSize)
{
socket.readBytes(mReader,mReader.length,mSize);
var
msg:IMessage = GetMessageByTag(mReader);
msg.Load(mReader);
if
(MessageReceive!=
null
)
MessageReceive(msg);
mSize=
0
;
}
else
{
mSize= mSize-socket.bytesAvailable;
socket.readBytes(mReader,mReader.length,socket.bytesAvailable);
}
}
}
|
原理很简单如果当前需要加载的数据为零,则表示为一个表新的消息;读取该消息需要加载的数据的长度,然后从Socket读取数据写入到流中,值到读取的长度和当前消息长度一致的情况就加载消息,并通过回调函数把消息回调到具体的工作方法中.
到这里一个以头4字节描述的消息分析器就完成,直接下来就是使用这个分析器。派生出一个新的分析器,并根据实际的需要实现对消息标记的处理.
public
class
HeadSizePackage
extends
HeadSizeOfPackage
{
public
function
HeadSizePackage()
{
super
();
}
override
protected
function
GetMessageByTag(data:Reader):IMessage
{
var
name:
String
= data.readUTF();
switch
(name)
{
case
"Register"
:
return
new
Register();
case
"User"
:
return
new
User();
case
"GetUser"
:
return
new
GetUser();
default
:
return
null
;
}
}
override
protected
function
WriteMessageTag(message:IMessage, data:Writer):
void
{
if
(message
is
Register)
{
data.writeUTF(
"Register"
);
}
else
if
(message
is
User)
{
data.writeUTF(
"User"
);
}
else
if
(message
is
GetUser){
data.writeUTF(
"GetUser"
);
}
else
{
data.writeUTF(
"NULL"
);
}
}
}
|
对于消息对象的实现也很简单,只要实现IMessage接口即可
public
class
Register
implements
IMessage
{
public
function
Register()
{
}
public
var
UserName:
String
;
public
var
EMail:
String
public
function
Load(data:Reader):
void
{
UserName= data.readUTF();
EMail = data.readUTF();
}
public
function
Save(data:Writer):
void
{
data.writeUTF(UserName);
data.writeUTF(EMail);
}
}
|
发送这个消息也比较简单
var
reg:Register=
new
Register();
reg.UserName = txtUserName.text;
reg.EMail = txtEmail.text;
mPackage.Send(reg,mSocket);
|
在使用AS的Socket时发现其实挺方便,很多基础的方法socket提供,即使是ByteArray也提供这些基础而又贴心的方法。