其實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也提供這些基礎而又貼心的方法。
