如題啦
最近在寫一個戰棋類的網頁遊戲,前台不用說用的是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;
}
}
}
其中Map可以存儲所有的基本類型,重點是Map同時可以存儲Map.這樣就解決了ValueObject嵌套ValueObject,和數組的問題了!
因為時間問題,背景還沒有實作先關的功能,更沒有經過解碼速度測試。不過應該不會有問題了!
完