As3 , Flash 序列化

本文详细记录了作者在开发战棋类网页游戏时,面对AMF协议的性能瓶颈,转而尝试使用Google Protocol Buffers(PB)进行数据传输的过程。文中描述了从AMF到PB的转变,包括配置环境、编写.proto文件、序列化与反序列化等问题,以及最终解决通信协议优化的挑战。同时,分享了PB在速度方面的优势,并在之后通过自定义封装协议实现了高效的数据传输。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

                  如题啦

    最近在写一个战棋类的网页游戏,前台不用说用的是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,和数组的问题了!

因为时间问题,后台还没有实现先关的功能,更没有经过解码速度测试。不过应该不会有问题了!





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值