其实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也提供这些基础而又贴心的方法。