天天看點

As3 , Flash 序列化

                  如題啦

    最近在寫一個戰棋類的網頁遊戲,前台不用說用的是Flex,背景用了java,Nio用的是Mina因為第一次使用Mina,碰到很多問題(處理粘包,斷包)。

    而最讓我們頭疼的,是如何把前端的資料包裝後傳給背景。一開始我們想到用Amf3封包,然後再在背景解包。這種方法是最簡便,相容性最好,也是最容易實作的。但後來的一次測試讓我們放棄了AMF——它太慢了。根本無法滿足我們的需求。幾經波折到處尋求高人指點,後來在一個群裡問AMF的相關問題時,一個網友‘囧囧’提點了一下,說可以用Google protocol Buffer (PB) 封包。我去網上找了一下資料,PB官網上說它是一個可以跨平台的資料封裝協定,可以将一個類對象序列化成ByteArray,并且支援壓縮。

    後來就開始研究起PB了,一開始以為隻要導入幾個jar,swc包就可以了。沒想到它不像我想象的那麼簡單。。。那就是一大堆沒有用過的東西,我到現在還不知道配置的那些東西起到了什麼作用。後來的後來,終于配置好了環境,文檔上說,每個要被序列化的Vo都要有一個.proto檔案才行(我嘞個去,能不能再麻煩一點!),後來我又為每一個Vo類編寫了.proto檔案。經過幾次調試,終于序列化<>反序列化成功!但付出了很大的代價。

    PB的使用:前端操作起來很友善,基本感覺不到有PB的存在。但背景Java就讓人崩潰了!因為vo的每一個屬性如果沒有set,你去parse就會報錯!丫的,後來找到原因——proto檔案下的每個變量都用了required去修飾。。。傷不起呀!

    pB最讓人欣慰的,就是它的速度,我試過10000條資料(簡單的UserVo對象),經過flash序列化-->socket-->java反序列化-->print。這個過程隻用了737ms,那時候我激動了一晚上。

    放棄:我把使用PB的這段經過跟其他人讨論了一遍,可能說的都是遇到的挫折,後來我們打算放棄使用PB。雖然我有點舍不得。。。

    項目進行了差不多一個月。因為很多都是‘第一次’,是以進展緩慢,不過還是都克服了。最近幾天又在讨論如何通信了,我之前試過用自己定義的封包格式序列化int[] 、float[]、double[]、String[]以及String。通過了前端enCode到後端的deCode測試——成功!後來我用socket傳了100條int數組,但到背景隻看到不到20條被成功解析出來,一開始以為是TCP協定的問題。後來,我做了一個實驗,我直接用Mina給的TextLineCodecFactory,成功收發1000了條字元串,發現沒問題。後來發現原來是自己寫ProtocolDecoder的時候沒有處理好粘包、斷包。這是才恍然大悟!解決了這個問題,我立刻傳輸了100w條,自己序列化的int[],測試時間顯示5173ms。我頓時欣喜若狂!

    但很快,我發現傳int[],double[]對我們的項目來說沒有任何意義——遊戲的每一條動作,都不可能是一連串沒有關聯的Number!是以又回到原來的狀态——我們沒有适用的封包協定!

   轉機: 就在當天晚上,用戶端這邊寫的實在無聊,然後又一直為這件事情困擾着。突然想起了之前讨論過用“Map”對ValueObject進行序列化。立刻開始行動起來!

                   首先在用戶端定義了幾個類:MapDecoder,MapEncoder,Map。MapDecoder負責将序列化後的ByteArray,反序列化成Map。而MapEncoder正好相反,Map起到存儲的作用。看代碼:

Map.as

package network
{
	import flash.utils.Dictionary;

	public class Map
	{
		private var id:int;
		private var dic:Dictionary = new Dictionary();
		private var keys:Array = [];
		private var _size:int;
		public static const BOL:int = 0;
		public static const INT:int = 1;
		public static const NUM:int = 2;
		public static const STR:int = 3;
		public static const MAP:int = 4;
		public static const MAP_END:int = 5;
		public static const ARR:int = 6;
		
		public function Map(){
			this.id = int(Math.random()*0xfffff+0xf00000);
		}
		public function push(key:String,value:Object):void{
			if(key){
				for(var i:int = 0;i<size;i++){
					if(keys[i] == key){
						return;
					}
				}
				dic[key] = value;
				_size++;
			}
		}
		public function del(key:String):void{
			if(key){
				dic[key] = null;
				delete dic[key];
			}
		}
		public function get(key:String):Object{
			if(key){
				return dic[key];
			}
			return null;
		}
		public function get size():int{
			return _size;
		}
		public function get data():Dictionary{
			return dic;
		}
		public function toString():String{
			return id.toString(16);
		}
		
		public static function typeOf(v:*):int{
			if(v is Boolean){
				return BOL;
			}
			if(v is int){
				return INT;
			}
			if(v is Number){
				return NUM;
			}
			if(v is String){
				return STR;
			}
			if(v is Map){
				return MAP;
			}
			if(v is Array){
				return ARR;
			}
			throw new Error("錯誤的資料類型!"+v);
			return -1;
		}
	}
}
           

MapDecoder.as

package network
{
	import flash.utils.ByteArray;

	public class MapDecoder
	{
		public static function deEcode(bytes:ByteArray,map:Map=null):Map
		{
			if(!map){
				map = new Map();
				bytes.position = 0;
			}
			if(bytes.readInt() != Map.MAP){
				return null;
			}
			var aLen:int = bytes.readShort();
			for(var i:int = 0;i<aLen;i++){
				var type:int = bytes.readByte();
				var key:String = null;
				var value:* = null;
				switch(type){
					case Map.BOL:
						key = bytes.readUTF();
						value = bytes.readBoolean();
						map.push(key,value);
						break;
					case Map.INT:
						key = bytes.readUTF();
						value = bytes.readInt();
						map.push(key,value);
						break;
					case Map.NUM:
						key = bytes.readUTF();
						value = bytes.readDouble();
						map.push(key,value);
						break;
					case Map.STR:
						key = bytes.readUTF();
						value = bytes.readUTF();
						map.push(key,value);
						break;
					case Map.MAP:
						key = bytes.readUTF();
						value = deEcode(bytes,new Map());
						map.push(key,value);
						break;
					case Map.MAP_END:
						return map;
						break;
				}
				
			}
			return map;
		}
	}
}
           

MapEncoder.as

package network
{
	import flash.utils.ByteArray;

	public class MapEncoder
	{
		public static function encode(map:Map,bytes:ByteArray=null):ByteArray
		{
			if(!bytes){
				bytes = new ByteArray;
			}
			bytes.writeInt(Map.MAP);
			bytes.writeShort(map.size);
			for(var attri:String in map.data){
				var type:int = Map.typeOf(map.data[attri]);
				bytes.writeByte(type);
				bytes.writeUTF(attri);
				switch(type){
					case Map.BOL:
						bytes.writeBoolean(map.data[attri]);
						break;
					case Map.INT:
						bytes.writeInt(map.data[attri]);
						break;
					case Map.NUM:
						bytes.writeDouble(map.data[attri]);
						break;
					case Map.STR:
						bytes.writeUTF(map.data[attri]);
						break;
					case Map.MAP:
						encode(map.data[attri],bytes);
						break;
				}
			}
			bytes.writeByte(Map.MAP_END);
			return bytes;
		}
		
		

		
	}
}
           
As3 , Flash 序列化

其中Map可以存儲所有的基本類型,重點是Map同時可以存儲Map.這樣就解決了ValueObject嵌套ValueObject,和數組的問題了!

因為時間問題,背景還沒有實作先關的功能,更沒有經過解碼速度測試。不過應該不會有問題了!