本文介绍Cwinux框架中,noticeXXX和onXXX的实现。
先对Framework做个简要介绍。
Cwinux是一个框架(Framework),不单纯是库(lib)。
框架和库的主要区别,在于:
框架封装了处理流程(all the control flow is already there),库通常只是提供了工具型的代码。从复用角度看,库是简答的代码复用,框架有控制流(处理流程)的复用。
框架、库和用户代码的关系不同。框架会调用用户的代码,而库的代码通常只被用户代码调用。
用形象一点的比喻的话,可以如下描述:当我们建造房屋时,库提供的是墙壁、门、窗等部件;框架则提供的是一个空架子,留下了很多空白的洞,用户去填充。
用比较常见的MFC和glibc为例,当我们要写MFC程序时,需要继承自MFC内置类,然后填充上自己的代码(如onDraw);glibc提供的则多是诸如sqrt(x)的工具类。
库和框架各适合有其应用的场景。通常来说,编写一个框架要比编写一个库难度大。
好,框架和库的概念介绍到这里,文末贴有几个链接,可供读者详细阅读。
下面具体到Cwinux框架,来看看框架本身和用户代码的关系,以及这种调用关系是如何实现的。
首先,介绍一下Cwinux的使用时的关键点。
开发者的应用需要继承CwxAppFramework。例如 CwxEchoApp:CwxAppFramework。
CwxAppFramework提供两类函数,开发者的应用按需重载其中的函数。其中的函数基本划分为两类:noticeXXX 和 onXXX。noticeXXX是主动的动作(通知框架去做XXX),例如,noticeTcpConn通知框架去建立一个Tcp连接。onXXX 是被动动作,当应用收到XXX事件后,框架会调用应用的onXXX函数,例如,收到消息时,App的onRecvMsg会被调用。
通常需要创建一个处理消息的Handler。CmdOp的子类,注册给Commander以便分发。
通常需要建立线程池。将Commander注册给ThreadPool,线程执行时调用Commander的分发函数。
第3、4两个点正是此前几篇博文介绍的Cwinux消息处理框架。现在重点介绍1,2的内容。
Cwinux框架封装了网络编程应用的处理流程,这个流程可以被复用,并以CwxAppFramework的形式暴漏给用户(使用Cwinux的应用程序开发者)。
现在,我们深入Cwinux内部,看看onXXX和noticeXXX是如何实现的。
首先,看CwxAppFramework类的定义(只保留了用于本文解释的部分)。
1 | class CWX_API CwxAppFramework |
如前所述,CwxAppFramework中声明了很多用于主动执行型的noticeXXX和事件发生响应型的onXXX的函数(截取的代码中只是一部分)。此外,CwxAppFramework的成员变量中有CwxAppReactor*对象m_pReactor。
在看代码之前,我们先自己思考一下,如何实现onXXX这样的响应函数。
首先,要写一个框架,必然得有一个CwxAppFramework类来将框架留出的空白的洞暴漏给用户,处理流程已经封装在框架中,用户填充上这些空白的洞后,一个应用程序就可以运行了。
接下来,我们以CwxAppFramework:: onRecvMsg函数为例,开始思考一个具体的onXXX的实现。onRecvMsg的语义是onRecvMsg函数在接收到连接上的数据时调用。这里,对用户来说,网络连接上数据到来是事件,onRecvMsg执行是对应的响应(被动响应)。我们知道,说到底,事件的来源是硬件中断。当然,应用层的Cwinux看不到硬件中断,它只能看到操作系统对中断的封装,具体到当前问题,这个封装表现出来的API就是epoll。
继续思考,我们想想这个事件是如何从硬件到Cwinux的onRecvMsg的。首先网卡硬件中断,该事件被操作系统路由到网络驱动中处理,再经过TCP/IP协议层处理,最后反映到epoll相关函数(epoll_create / epoll_ctl / epoll_wait)在内核中建立的数据结构。这时,某个已经执行epoll_wait函数的线程从epoll_wait的等待过程中返回,并且epoll_wait还告诉我们fd=N的网络连接有数据到来,我们从该连接读取数据。这时,我们需要通知CwxAppFramework调用onRecvMsg函数。
因此,在epoll_wait返回后,框架要能够找到CwxAppFramework对象。我们猜测,一种可能的方法就是把CwxAppFramework对象(指针)通过某种方式传递到响应epoll_wait的对象中。这样,该对象在响应处理epoll_wait反馈的新发生的事件时,调用CwxAppFramework对象指针的onRecvMsg,这样达到通知用户的App应该该调用onRecvMsg。
下面我们来看看Cwinux中到底是如何实现onXXX的。
由前面的分析,再回顾一下前面所描述的Reactor模式相关代码,我们知道,响应epoll_wait的对象是CwxAppHandler4Base。查看其代码,没有发现CwxAppFramework的踪影!好吧,再看看CwxAppHandler4Base的具体子类,例如CwxAppHandler4Msg,找到了CwxAppFramework*****。CwxAppHandler4Msg的代码大意如下(为方便阅读,做了修改,只保留了核心内容):
1 | class CWX_API CwxAppHandler4Msg:public CwxAppHandler4Base |
这段代码清晰表明了事件响应过程中是如何通知用户应用程序的。代码很清晰,不再赘述。另外,需要填个坑。在Cwinux简介那篇博文中,最后部分说过:建立Tcp监听时有两种模式可选,对应的响应函数不同(一个由框架接收数据生成消息,另一个只是由框架通知有数据到来)。这里正可从代码看出,两个处理方式的不同。建立Tcp监听时,指定isRawData的值,可以决定框架的不同动作:raw情况下,框架只通知有数据到来,getApp()->onRecvMsg(*this, bSuspend);not raw情况下,框架接收数据生成消息,通知有消息到来, this->getApp()->recvMessage(m_header, this->m_recvMsgData, *this, bSuspend)。
至此,我们已经解析了Cwinux实现onXXX这类通知用户应用程序有事件到来的具体过程。
下面介绍noticeXXX,其实现过程如图示。
CwxAppNoticePipe是一个列表,里面串了CwxAppNotice对象。CwxAppNotice对象用来传递通知命令。AppFramework调用noticeTcpConnect函数,该函数会创建一个Handler4TcpConn对象handle,handle中填充该notice事件的相关参数,并调用m_pReactor串入noticePipe中。AppFramework在启动后进入事件循环run。在hook函数中,AppFramework检查是否有notice事件,如果有,则调用Reactor::noticed处理。
这里有一点需要注意:由于AppFramework在执行事件循环中会因epoll_wait等待(当前没有网络事件等),当这是我们执行noticeXXX时,需要唤醒AppFramework的事件循环,Cwinux的做法是预先创建一个管道(两个fd:m_pipeReader和m_pipeWriter),读取端m_pipeReader加入epoll_wait中监听,写入端m_pipeWriter留着,执行noticeXXX时,向写入端m_pipeWriter写入一个字节,则读取端m_pipeReader发生可读事件,epoll_wait旋即返回,进而有机会执行hook和noticeEvent函数来处理notice事件。
Referrence:
Framework vs Library:
http://www.programcreek.com/2011/09/what-is-the-difference-between-a-java-library-and-a-framework/
扫描二维码,分享此文章