首页 > 代码库 > mac TeamTalk开发点点滴滴之一——DDLogic框架分解下

mac TeamTalk开发点点滴滴之一——DDLogic框架分解下

4 TCP/IP长连接

大部分客户端应用程序的网络I/O模型采用阻塞模式就够用了,如遇到UI和网络需要异步,很常用的一种实现方式是启用多线程将网络数据的收发放到工作者线程中去。但是对网于IM这种应用场景来说阻塞模式就不适用了,试想聊天过程中你和服务器之间的交互是多么的频繁,你可以同时和几十位用户一起聊天,为了不阻塞难道每次聊天收发信息都需要建立一个线程来实现吗?这当然是不现实的,所以我们需要选择非阻塞模式异步socket IO。下面分别讲讲mac pro 和 windows的网络异步I/O的实现。

mac TT

mac TT得益于oc提供的良好平台目前借助的是CFNetwork和NSStream类实现TCP/IP 异步I/O socket,利用CFNetwork创建socket通信通道,利用NSStream传递单向的数据流,具体实现如下: 
通过在NSStream中增加一个类方法扩展用于建立TCP/IP连接的一系列过程。

+ (void)getStreamsToHostNamed:(NSString *)hostName port:(NSInteger)port inputStream:(NSInputStream **)inputStream outputStream:(NSOutputStream **)outputStream{    CFHostRef           host;    CFReadStreamRef     readStream;    CFWriteStreamRef    writeStream;    host = CFHostCreateWithName(NULL, (__bridge CFStringRef) hostName);    CFStreamCreatePairWithSocketToCFHost(NULL, host, (SInt32)port, &readStream, &writeStream);    CFRelease(host);    ...}

NSStream的两个派生类NSInputStream/NSOutputStream把整个socket通信抽象成了一个输入/输出流,通过oc平台的RunLoop将异步I/O事件通知到如下回调函数中:

-(void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode{   switch(eventCode) {      ...   }}

回调通知的事件有:

typedef NS_OPTIONS(NSUInteger, NSStreamEvent) {    NSStreamEventNone = 0,    NSStreamEventOpenCompleted = 1UL << 0, //输入or输出流打开成功即socket连接建立成    NSStreamEventHasBytesAvailable = 1UL << 1, //可以接受数据通知即输入缓冲区有内容了    NSStreamEventHasSpaceAvailable = 1UL << 2, //可以发送数据通知即输出缓冲区空了    NSStreamEventErrorOccurred = 1UL << 3, //错误通知    NSStreamEventEndEncountered = 1UL << 4};   以上具体代码可以参见mac tt代码NSStream+NStreamAddtion.m 以及 MGJMTalkClient.m

咋样整个过程看下来是不都看不到socket的影子?这样做有啥好处呢?我自己的理解最大的好处是足够简单,对于调用者来说socket的整个过程是透明的,调用者不需要去理解操作系统对异步socket的I/O模型的支持,不需要去理解socket建立的整个过程等。类似的还有java的NIO甚至netty库都把整个socket过程隐藏在了一个流的概念中。 
尽说好处了,差点忘记目前DDLogic的这种实现还有一个很大的问题(PS:是不是我们使用上有问题,同学们也可以帮忙看下),即connet TCP服务器的时候,回调事件里面肿么也收不到连接断失败的事件通知,导致整个TCP/IP流程不流畅,我们暂时采用了一个很龌龊的方式是:connet TCP服务器的时候设置个定时器,如果3秒钟没有收到连接建立成功的通知就认为连接失败了。这里的代码我担心也会为将来开发IOS TT埋下一个隐患,建议IOS开发同学去深入研究下或者寻找更好的技术选型。这里提供几个参考

  1. OC平台OS层的基于C的 BSD socket,这一层面提供的是socket原生态的方法,可以最大程度的控制网络编程,但是工作量也是最大的,和windows TT采用C/C++ 进行socket编程差不多。
  2. OC平台Core Foundation层提供的CFNetwork C ,对OS层的BSD socket做了一层简单的包装,并且和系统的run loop结合起来,使得异步socket I/O实现起来很方便。上面mac TT用的其实就是这一层,所以这里还是需要去深入研究上面的坑。
  3. OC平台最上层提供的Bonjour库,同学们可以自行去看下Networking and Bonjour on iPhone
  4. 另辟蹊径不走OC平台提供的库,用libevent来实现,不过对于客户端来说使用该库可能略重,但是它良好的封装使得使用起来非常简单而且本身也是轻量级高性能的网络库,客户端选择POSIX select或windows select模型足够用了。

windows TT 
windows TT是基于windows的WSAAsyncSelect模型建立的异步I/O,利用这个模型应用程序可在一个套接字上,接收以Windows消息为基础的网络事件通知,对于一个客户端程序已经足够用了。code projct有个对该模型很好的包装,同学们可以去看下(http://www.codeproject.com/Articles/3855/CAsyncSocketEx-Replacement-for-CAsyncSocket-with-p)。具体的实现windows并没有像oc平台这样好的抽象,但是实现起来其似乎也是差不多的思想,异步I/O消息通知的事件包括:FDREAD、FDWRITE、FDFORCEREAD、FDCONNECT、FDACCEPT、FDCLOSE这些,每个事件都相应的能通知到socket数据处理层就可以了。

比较下来两个系统平台对于TCP/IP异步socket IO的封装是差不多的,差别只是抽象的层次mac pro平台更加高一点,windows更加接近原生态的socket。 
以上讲的是利用各自平台的网络库实现与服务器之间通信的技术,接下来一起看下在内存中收发数据的两个buffer,因为数据传递是异步的,发送/接收数据都有可能是还没有真正发送/接收成功,所以需要在socket数据处理层维护两块buffer——inBuffer(接收数据缓存)/outBuffer(发送数据缓存)。以outBuffer(发送数据缓存)为例子,当你调用sendSocketData的时候,由于操作系统发送缓存区满了导致调用失败 ,由于是异步socket IO,系统的send过程并不会等待系统的发送缓存区空了再发送数据,而是会让send过程失败,等到系统的发送缓存区空的时候通过一个可写的事件通知你,所以在sendSocketData过程send失败的情况下,你所需要做的就是将数据缓存到outBuffer(发送数据缓存)中,等到可写事件收到了再将outBuffer(发送数据缓存)的数据发送出去,上个流程图吧:

请输入图片描述

5 业务模块拆分以及模块与模块之间通过接口交互

任何应用程序从业务角度讲都不是单一的,是由许多业务组装起来的(比如mac TT有登陆业务、文件传输业务、消息管理业务、会话管理业务等),那么这些业务需要如何有机的结合起来完成一个应用程序的所有需求呢?同学们应该会首先想到MVC(Model、View、Controller)/MVP(Model、View、Presenter),嗯没错,在OC平台中本身就是按照MVC来实现具体业务的开发的,DDLogic在MVC基础之上再加了一个Module的概念,为的是和前面:基于Task的任务调度、pdu通信协议以及拆装包过程、事件的订阅与发布、持久化数据以及基于此数据之上的一层数据监听机制(类似IDE工具调试的 Watch)这些有机的结合起来,回头看看是否还记得前面PDU协议面的moduleid和存储格式按照三元里面的moduleid呢?先上个简单的图吧:

请输入图片描述 
DDLogic的思路是这样的(以登陆业务模块为例子):

 1.所有模块的对外接口都通过DDLogic Modules Manager来管理。 2.模块与模块之间通过接口来调用,模块内部实现对外不可见。比如外部只能调用DDloginModule的doLogin()来实现登陆操作,调用方是不知道具体如何实现登陆的。 3.每个独立的业务创建成为业务module——DDLoginModule,有一个全局唯一的业务模块ID——MODULE_ID_LOGIN 4.调用业务模块的接口函数通过全局唯一业务模块ID——MODULE_ID_LOGIN来,DDLoginModule*loginModule = getDDModule(MODULE_ID_LOGIN);[loginModule doLogin]; 5.支持插件管理,n(n >=1)个模块合作来组装成1个插件,并且支持动态加载/卸载(未实现的目标)

是不感觉DDLogic框架连这种东西也拿出来分享,没啥技术含量是个程序员都知道用类似的方式来拆分业务?是的你的感觉是对的,但是只对了一半,确实看起来没啥营养,但是请你再往下看你会发觉这个点才是整个DDLigic框架的精髓,如果说上面讲的每个设计点是DDLogic框架的一条条河流的话,那么这里就应该是它们的汇聚地,下面逐个点来分析

 1.基于Task的任务调度:每个task的执行都会绑定一个module_id来知道具体是哪个模块的task在执行,并且通过module_id将任务执行的结果反馈给模块,这条河流就汇聚到module了。 2.事件的订阅与发布:每个事件都是通过指定module_id和MKN来订阅的,等到被订阅事件发布的时候同样通过指定module_id和MKN来通知出去,这条河流也汇聚到moudule了 3.pdu通信协议以及拆装包过程:通过解析pdu协议头获取module_id和command_id,然后生成NetworkTask派发到相应的模块中区,这条河流也汇聚到module了 4.持久化数据以及基于此数据之上的一层数据监听机制(类似IDE工具调试的 Watch):通过储格式三元组[module_id,module_item,module_tag],这条河流也汇聚到module了

6 持久化存储以及基于此数据模型数据监听机制(类似IDE调试工具的Watch)

DDLogic数据持久化用的是NSCoder可以支持基于业务模块的数据序列化/反序列化。基于数据模型的监听机制(暂且称作data watch机制)对一个应用程序来说是非常实用的,举个例子:你的好友管理模块的数据新增了一个好友,好友列表数据发生add事件,监听此数据变化的模块如好友列表控件、消息管理模块等都会收到相应的通知并作出及时的处理。 
这块将放到《 
mac TT开发点点滴滴之四——NSCode与DDlogic的结合》中做深入阐述,敬请期待。