1.socket 解码
下述内容为转载,对socket解码代码设计有帮助:
主要想说一下socket解码在设计时忽略的一个问题。(感谢Lite3的反馈)。
对于客户端的Socket的数据基本读取方式一般来说可以分为三种:
1、按照数据流的结尾标记截取
2、按照包头记录的包长度截取
3、按照包长度截取并验证结尾标记。
第一种方式:如图,通常做发是每个包结尾发送一个\0标记,表示这个数据包发送完了。socket每次读取1个字节直到遇到结尾符,结束读取将数据包传递到逻辑层。这种方式在xmlSocket时就已经用了很多了。
第二种方式:如图包头用一个整型记录完整包的长度。每次都先读取一个包长度,然后按照包长度读取指定长度的数据作为一个完整数据包传递到到逻辑层。
第三种方式:如图。这种方式把以上两种方式做了结合,读取的时候无需一个一个字节读,直接读取指定长度。结尾符可以用来做校验判定,同时可以作为包长度字节的读取依据。(如果上一个包有问题可以丢弃之后,以结尾符为标记读取下一个包的包长度。)
介绍了一点基本原理,下面说一下这个设计缺陷。
问题:
通讯中我们使用的是第二种方式——包长读取。在通讯类中创建一个临时存储变量_dataArray,提供一个getData():*公用方法,供外部取出数据。每次读完一个完整包后压入_dataArray,同时触发"recievedData"事件。代码如下:
private function socketDataHandler(event:ProgressEvent):void
{
//_readFlag:int;//0表示全部读完了,1表示长度读取完毕 2表示正在读取数据
while (bytesAvailable)
{
if (_readFlag == 0&&bytesAvailable>=4)
{
_length = Number(readInt());
_readFlag=1
}
if (_readFlag == 1 && bytesAvailable >= _length)
{
var temp:Object = readObject();
_dataArray.push(temp);
dispatchEvent(new Event("recievedData"));//
_length = 0;
_readFlag = 0;
}
}
}
以上代码从结构上看是没有任何问题的,而且在测试前期我们也一直用着没有任何问题。逻辑相当清楚:接收到socket的事件后首先读取一个包长,然后按照包长读取数据。读取完毕发出事件。。。
那么,问题终于来了——网友Lite3出现了。^ _ ^
Lite3发来了一个出错提示:
Error: Error #1502: 脚本的执行时间已经超过了 15 秒的默认超时设置。
at qdooo.net::mySocket/socketDataHandler()
相信大家一看就能知道问题出在了哪里——问题就在while上。
while的循环等待时间因为某种原因超过了15秒。这里所指的某种原因就是网速,那天Lite3那边的网速非常慢。这样一来放大了设计中一处缺陷所照成的影响——必须等到可读数据的长度等于或者大于包长度的时候才开始读取,也就是下面这句:
if (_readFlag == 1 && bytesAvailable >= _length)
正是由于这个判断做了限定,如果数据长度不够,那么他会在while中一直循环等待。直到超过15秒报错。
解决:
分析好了原因那么就很好解决了,想办法读空流里面的数据让while跳出等待就行了。
读后:测试时,使用php 作socketserver ,连续发送定长string 到 as3 中,几点注意:
- 接收时,buffer长度为一次接收所有的string长度和;
- 而as3的read读取后,socket.bytesAvailable长度减去所读的长度,直至读取完毕;
- 按照前面转载的代码方式,就可以顺利接收;
2.粘包问题
注意,每一次的读取ba.readBytes(temp,offest,length);方法,真实的position=offest都是从当前的位置为起点+上一次的位置,length就是长度了
var ba:ByteArray=new ByteArray();
ba.writeUTFBytes("for test for tews for test for tewsfor test for tews");
// ba.writeUnsignedInt(15);
trace(ba.length);
ba.position=0;
for(var i:uint=0;ba.bytesAvailable;i++)
{
var temp:ByteArray=new ByteArray();
ba.readBytes(temp,2,4);
trace("\n",temp.readUnsignedInt(),"ba.postion",ba.position);
}
}
52
26223 ba.postion 4
29797 ba.postion 8
8294 ba.postion 12
8308 ba.postion 16
29472 ba.postion 20
29216 ba.postion 24
29556 ba.postion 28
28530 ba.postion 32
25975 ba.postion 36
28530 ba.postion 40
25971 ba.postion 44
26223 ba.postion 48
29797 ba.postion 52
3.汉字乱码问题
正所谓眼见为实,耳听为虚,在编程开发时,听别人说的东西一定要自己进行验证。因为网络这个东西,跟风的东西太多,以讹传讹的事情比比皆是。
近来在做AS3与.net的socket通信工作,相继解决了通信协议解析和数据粘包等问题,终于要跟汉字编码这个问题一争高下了。在经历了一番深入研究之后,我终于将汉字乱码这个问题彻底解决了,以后不管它AS3的socket要跟谁通信,我的办法都可以让你在面临汉字乱码问题时知道该怎么办了。
要说as3的socket发送汉字,首先得认识as3的两个类:flash.net.Socket和flash.utils.ByteArray,这两个类的协作关系就是ByteArray
将字符串(包括汉字)按照特定的编码格式转化成字节数组,然后Socket类将这个字节数组发送出去。整体思路是很简单的。
但是这里面有个陷阱或者说很多人在讲解flash乱码问题时没有说清楚的问题。这也是我开始遇到乱码问题时非常困惑的地方。首先,我们来介绍这个问题的所在:
ByteArray有两个函数可以转化汉字成字节数组:
第一个是writeMultiByte;另
第二个是writeUTFBytes;
这两个方法有何区别?
这正是乱码问题之所在!我们来做个试验加以说明.试验方式如下:设定一个字符串“曹操”,然后分别用ByteArray的这两个方法将其转化成字节数组,然后读出字节数组的内容看看结果,看到字节数组内容的时候大家就会有所收获了。
var arrBuffer:ByteArray=new ByteArray(); var strTest:String="曹操"; arrBuffer.writeMultiByte(strTest,"utf8");
读出这个字节数组的内容,得到的字节值是:-78 -36 -78 -39 ,每个汉字占两个字节。我们在来看下面的例子var arrBuffer:ByteArray=new ByteArray(); var strTest:String="曹操"; //arrBuffer.writeMultiByte(strTest,"utf8"); arrBuffer.writeUTFBytes(strTest); 读出这个字节数组的内容,得到的字节值是:-26 -101 -71 -26 -109 -115 ,很明显的,每个汉字占到三个字节。
陷阱已经显露出来了,在解决汉字乱码这个问题的时候,我查了很多的网上信息,见到最多的一行代码就是arrBuffer.writeMultiByte(strTest,"utf8")了,也不做什么说明,根本就是误人子弟嘛。现在,这个问题被我揪出来了。大家如果还有什么疑问,就继续往下看。
经我查证,Unicode字符集在编码汉字的时候,应该是一个汉字三个字节的,为何第一次试验的时候用arrBuffer.writeMultiByte(strTest,"utf8");得到的却是一个汉字两个字节呢?我又试验性的将编码格式那个参数换成"gbk"和"gb2312",结果得到的字节数组与"utf8"时一样。我还没有搞清楚这是参数失效还是Adobe有别的什么考虑。但是试验已经很清楚的告诉我们了一个事实:如果你想将汉字用真正的UTF-8编码的话,就用ByteArray的writeUTFBytes函数来解决问题。不要再用writeMultiByte(strTest,"utf8")了。这个函数是不会将汉字转化成UTF编码的。
至于.net端,我就不多着笔墨了,要在.net端转化汉字编码时,Encoding和UnicodeEncoding这两个类打开看看,使用合适的编码类和函数就可以解决问题了。实在还是有问题,按照我解决flash端编码的经验,查看实际编码得到的字节值,你也就能够找到合适的编码集来解决问题了。