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