关键词 AS3 Flash flash.net.Socket TCPClient
转载请注明: http://blog.csdn.net/herm_lib/article/details/8245698
这里介绍的网络层包括两个小模块:flash.net.Socket实现的TCPClient和根据命令ID分发消息到目标Handler的Dispatcher。上层逻辑将自己的处理逻辑的Handler注册到网络层的Dispatcher里,就可以在Handler里处理收到的消息了。
先说一下网络的一些常识。我们通常口头说的数据包,在IP层称做Packet, 传输层的TCP称为Segment,UDP则叫Datagram,应用层很多RFC的描述成Message。Message我们可以看成和应用协议对应的消息包,各种RFC协议规格书,Message格式描述一般都有。应用层从tcp层收到的tcp数据是基于流的,流是什么?就是我们应用层的协议数据混在一起的数据(表述不清楚)。flash.net.Socket最终给我们的就是这个流。
简单说一下flash.net.Socket实现原理。
这个Socket是对tcp client的功能做了简要的封装,client功能包括创建一个socket句柄,connect,响应收到数据时的一个回调通知,还有比较重要的连接关闭和各种错误的回调通知。
Socket内部维护了两个队列,一个是接收数据的buffer,一个是发送数据的buffer;我们应用程序就是和这两个buffer交互,达到收发数据的目的。
当Socket内部收到数据时,先把数据放到他的接收buffer;接着就给应用程序发ProgressEvent事件;应用程序收到事件,可以尝试着从接收buffer取数据。
应用发数据,先把数据放到Socket内部的发送buffer,调用flush后,发送buffer的数据将被发送出去。
经过初步地测试,有一点基本可以肯定,网络接收到的数据放到Socket内部的接收buffer和Socket内部的发送buffer的数据传到网络,驱动的线程和我们应用的线程是不一样的。Socket.flush()基本原理应该是给内部发送数据到网络的接口一个WRITE网络事件通知。
用一个独立的线程从网络底层收发数据,逻辑层的Handler走自己线程,这个设计很合理(很网络库的设计Handler的线程和网络线程是一样的)。我的Herm的设计方式也是类似,逻辑层是走自己的线程,网络层可以启动多个线程。
请参考,Herm简要介绍。http://blog.csdn.net/herm_lib/article/details/5980657
接下来 ,进入我们实现flash tcp client的话题。
flash.net.Socket这个对象,给我们的是tcp流,我们要做的一个主要的事情是把这个流能分解成我们应用程序能理解的消息,消息的格式由我们应用决定。比如一般应用比较简单的协议消息会这样定义:
total_msg_len | ----- msg_data ----- |
total_msg_len
定义成4个字节,正常情况下约定为网络字节序(big-endian),这里4个字节表示整个消息的长度(可以包括total_msg_len本身)。
msg_data
我们协议要的协议消息数据内容了,这个格式完全由应用开发者决定,可以用二进制、XML、Jason,还是protobuf之类的。
TCPClient简单实现大概像下面一样:
public class TCPClient
{
private var _sock:Socket;
private var _totalMsgLen:uint;
private var _handleBuf:ByteArray;
private var _msgDispatcher:MsgDispatcher;
public function TCPClient(sockStateReportor:Function)
{
_sock = new Socket();
_totalMsgLen = 0;
_handleBuf = new ByteArray();
_sock.addEventListener(ProgressEvent.SOCKET_DATA, onSocketData);
}
private function onSocketData(event:ProgressEvent):void
{
while (true)
{
if (!handleSocketData())
break;
}
}
private function handleSocketData():Boolean
{
if (_sock.bytesAvailable <= MSG_LEN_SIZE) // MSG_LEN_SIZE==4,数据没超过总长度大小,不处理
return false;
if (_totalMsgLen == 0)
_totalMsgLen = _sock.readUnsignedInt(); // 先读总长度
// Socket接收buffer数据还没有达到协议消息要的长度,先返回,等下一次数据收全了再处理
if (_totalMsgLen - MsgDef.MSG_LEN_SIZE > _sock.bytesAvailable)
return false;
_handleBuf.clear(); // 重复地用_handleBuf,用的时候清空一下
_sock.readBytes(_handleBuf, 0, _totalMsgLen - MSG_LEN_SIZE);
_msgDispatcher.Dispatch(_handleBuf, _handleBuf.length); // 数据分发给注册进来的Handler
_totalMsgLen = 0;
return true;
}
}
这里面要注意的细节就是 _sock.readUnsignedInt()字节序,参考:
AS3 socket一些细节记录。
代码应该比较清晰,如果Socket支持Peek数据(仅仅从buffer复制数据,内部buffer不清除数据)的话,这块会更清晰一点,_totalMsgLen不用保存起来了。
逻辑层的代码类似这样:
public class Auth
{
private var _tcpClient:TCPClient;
public function Auth(tcpClient:TCPClient)
{
_tcpClient = tcpClient;
tcpClient.msgDispatcher.register(CmdType.CT_AUTH, CmdCodeAuth.CC_AUTH_SIGN_RES, onAuthRes);
}
private function onAuthRes(mb:MsgBody):void
{
}
}
把Auth::onAuthRes注册到TCPClient里的MsgDispatcher。收到对应的消息,最终会在onAuthRes得到处理了。
MsgDispatcher的实现,可以参考下面文章的AS3 MsgDispatcher部分:对比基于boost::function/bind和AS3 Function回调机制
TCPClient发送,发的时候先发4字节的总长度,也要注意一下通信双方约定的字节序,再发消息数据。
其余的就是关闭和各种错误的事件响应了。