如题啦
最近在写一个战棋类的网页游戏,前台不用说用的是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,和数组的问题了!
因为时间问题,后台还没有实现先关的功能,更没有经过解码速度测试。不过应该不会有问题了!
完