【转载+原创】Flash 接收 Socket 数据包
最近要写一个c++和flash通过tcp通信的程序, 遇到了不少的麻烦, 最后是解决了, 现把解决方法记录一下
需要解决的问题就是使用C++通过本地tcp连接向flash程序单向循环发送字符串, 遇到的问题就是在flash端接收的时候发现明明在C++端分好几次send的数据, 在flash一端的回调却只会调用一次。
转载自:http://cjmxp007.blog.163.com/blog/static/35473837201061054116916/
AS3.0 中使用Socket使用 tcp服务器协议,它是一种流协议,不停的将分片传输给客户端,作为流,发包是不会整包到达的,而是源源不断的。
它不同于UDP服务器协议,UDP作为数据包协议,整包到达。
如果要使用Socket接收数据我们必须使用ProgressEvent.SOCKET_DATA
事
件。这个事件在帮助文档中是这样描述的 ——在套接字接收到数据后调度。
而事实却并非如此,做过一次尝试,服务器发送了20000次数据而
rogressEvent.SOCKET_DATA事件只产生了2000多次。
那么为什么说"服务器发送了20000次数据而
rogressEvent.SOCKET_DATA事件只产生了2000多次",
因为flash socket使用的TCP/IP协议,
这个协议跟UDP不同,它不是以单个"包"的形式发送数据,它发送的是"流数据",所以即便你发来20000次数据(也就是你所想象的20000个
包),TCP协议也是将它视作"流"发送.
换句话说,你的20000次数据,实际上只被分割成了2000多个"包"来发送,因此socket收
到了2000多个包,,因此只产生了2000多次的事件.
另外,如果as3 的data事件函数正在执行的时候,比如在此函数中用while循环解码,此时有新的数据发送过来,data事件还会触发么?触发的话,正在执行的怎么 办?原有数据还有么?
答案是会触发的,所以将socket数据read的时候,必须做一个循环 while,每到一个包刚好读取完成的时候(包头用一个整型记录完整包的长度。每次都先读取一个包长度,然后按照包长度读取指定长度的数据作为一个完整数 据包传递到到逻辑层),又继续读取下一个包,然后把解码后的每个包都放进一个数组里面依次读取。还有一点要注意的是 socket.bytesAvailable长度是每read一次就减去所读的长度,直至读取完毕,最后为0;此处的bytesAvailable如果重 新设置position为0,那该数组的bytesAvailable又是满的。
附一下代码进行研究:
private function Net_Data(evt:ProgressEvent):void { var ba:ByteArray = new ByteArray();//创建一个 socket.readBytes(ba, 0, evt.bytesTotal); //服务器一次性发送的总共的数据,可能是几个包,也可能是几个半包 packetBuffer.push(ba); //把ba放入缓冲区,其实就是把ba放入packetBuffer类中的一个ByteArray对象里 var packets:Array = packetBuffer.getPackets(); //这里就是在进行解码(包含循环) for each(var packet:MsgPacket in packets) { dispatch(packet); //对解码后的数据进行处理,可以说是直接使用、赋值 } }
packetBuffer.as
package org.green.server.data { import flash.utils.ByteArray; public class PacketBuffer { private var buf:ByteArray = new ByteArray(); private static const SPLIT:int = 21316;// "DS" public function PacketBuffer() { } public function push(ba:ByteArray):void { if(buf == null) { buf = ba; }else { buf.position = buf.length; buf.writeBytes(ba); } } public function getPackets():Array { var ps:Array = []; var ptr:uint = 0; buf.position = ptr; while(buf.bytesAvailable >= 2) //这里是说当可用数据大于包头时,一个包==包头(body的长度)+包体(body),也就是说包里如果一旦有数据就开始执行 { //2其实是readShort()后,少了的2个字节,也就是body有数据的时候才开始解码 var len:uint = buf.readShort(); //不足一个包,这里完全有可能,当只读取完包头len,但是body却没有读取到末尾 if(buf.bytesAvailable < len) { var ba:ByteArray = MsgUtil.createByteArray(); buf.position = ptr; ba.writeBytes(buf, 0, buf.bytesAvailable); buf = ba; //返回 return ps; } buf.position = 2; var mb:ByteArray = new ByteArray(); buf.readBytes(mb, 0, len); //len为body的长度,将body的数据放入mb mb.position = 0; var msg:MsgPacket = MsgUtil.createMsgPacket(mb,magic);//这里在对body解码过程 略 buf.position=0; ps.push(msg); //放入数组 //下一个包 while语句进行下一个循环 } if(buf.bytesAvailable <= 0)buf = null; return ps; } public function clear():void { buf=null; } } }
上文中使用的是一个缓存的技术解决了问题, 但是我不需要这么复杂, 最后, 我是这样解决问题的:
package { import flash.display.MovieClip; import flash.events.Event; import flash.events.ProgressEvent; import flash.net.Socket; /** * ... * @author */ public class get_udp extends MovieClip { var RFIDSocket:Socket; var receive_buffer:String=""; var comm_splitter:String = "\n"; public function get_udp() { init(); } //var RFIDSocket:Socket; function init():void { RFIDSocket = new Socket("localhost",16000); RFIDSocket.addEventListener(ProgressEvent.SOCKET_DATA, socketData); } private function socketData(e:ProgressEvent):void { trace("收到的字节数"+RFIDSocket.bytesAvailable); while (RFIDSocket.bytesAvailable) { // TODO: 进行优化, 一次读入多个字节, 检查是否含有回车, 然后进行截取 var temp_char:String = RFIDSocket.readUTFBytes(1); // 如果读入的是通信分割, 则进行处理 if (temp_char == comm_splitter) { processKinectData(receive_buffer); receive_buffer = ""; }else // 如果读入的是普通字符, 则存储 { receive_buffer += temp_char; } } } private function processKinectData(data:String):void { trace("处理Kinect数据:" + data); } } }
-------------------------EOF----------------------