分为几个模块 EventLoop、TcpServer、Acceptor、TcpConnection、Channel等
对于EventLoop来说:
他只关注里面的主驱动力,EventLoop中只关注poll,这类系统调用使得其成为Reactor模式,EventLoop中有属于这个loop的所有Channel,这个loop属于哪一个Server.
几个类存在的意义:
从应用层使用的角度来看,用户需要初始化一个EventLoop,然后初始化一个TcpServer(当然也可以自定义个TcpServer,自定义数据处理函数需要注册到TcpServer内),然后调用TcpServer的start函数,然后调用EventLoop的loop()函数。整个用户层的使用流程就是这样的!
从用户层的应用方法来解析Muduo库的设计思想:
首先来看TcpServer这个类,从名字来看,它是一个服务器,里面肯定需要有一个用于监听某个地址的套接字,这个是Acceptor类,这是由TcpServer引出的第一个类,在Acceptor类中封装了监听套接字,Acceptor负责了一个socketfd,这个socketfd就是一个监听套接字。当这个套接字上有可读事件时,调用了Acceptor的handleRead函数,此函数的内部就是accept()系统调用了,函数返回产生了一个连接套接字,紧接着就是调用Acceptor中的回调函数newConnectionCallback_,那么这个回调是谁注册的呢?肯定是谁拥有Acceptor谁就负责初始化Acceptor中的newConnectionCallback_回调喽!那么就是TcpServer负责注册!在进行TcpServer初始化时调用Acceptor中的setNewConnectionCallback()函数将newConnection赋值给newConnectionCallback_。也就是说,在Acceptor中一旦accept()系统调用成功返回就立马调用newConnection函数。
到目前为止,遗留下了以下几个问题:
1、 Acceptor中的handleRead()函数是什么时候被调用的!
2、 newConnecion虽说属于TcpServer,但是newConnection函数的作用是创建了一个类!这个类的作用也是举足轻重!
接下来介绍下由TcpServer引出的Acceptor类:
首先这个类是属于内部类。既然这个类是管理监听套接字的,那么这个监听套接字的生命周期就是由Acceptor类来管理。这个套接字在Acceptor就是Socket,同时也有一个EventLoop指针,表明这个Acceptor属于某一个EventLoop(因为Acceptor依赖于某一个TcpSever,同时TcpServe和EventLoop是有依赖关系的)。同时还有一个newConnectionCallback_函数,这个函数是在TcpServer初始化的时候被赋值的。Listening_表示当前这个监听套接字的状态,idleFd_是一个输出错误的描述符。这里还有一个新的类—Channel!这个类在整个库中起着桥接的作用,整个这个类将有些东西单独提取,是的其他各个类的功能更加单一,关于这个类的介绍不在这里,毕竟Acceptor类是一个内部类,如果这个一个庞大的类由内部类引出,显得不够重视!呵呵!这里暂时雪藏Channel类!
关于Acceptor类的接口,只有很少的三个:
其中一个是setNewConnectionCallback,由于Acceptor类属于TcpServer类,所以调用合格函数的肯定是属于Acceptor的所有者,也就是TcpServer类,这个函数在TcpServer的构造函数中被调用,将newConnectionCallback_函数赋值为newConnection,已经说过了,有点啰嗦了!呵呵!另外一个就是listen()函数,从感觉上来看,这个是使得Acceptor类中的acceptSocket_处于监听状态的函数,暂时记住这个函数,尤其是这个函数中的最后一句,这事欠下的有待解决的问题!
有待解决的问题:
1、 在Acceptor中的listen()函数中,属于Channel类中的enableReading()是干什么的?
2、 Acceptor的listen()何时被调用!
到此需要记住的几点:
监听套接字是单独的类Acceptor,是脱离TcpServer类存在的一个类!
同时TcpServer类中不包含任何一个套接字(无论是监听套接字还是连接套接字,监听套接字属于Acceptor,连接套接字在下面一个类的介绍)。
TcpServer类中是没有监听套接字的,但是他负责注册监听套接字上接受到一个连接后的响应操作(也就是TcpServer::newConnection,关于这个函数在介绍完EventLoop这个大块头再来介绍,不然衔接不上!)
至此我们大概介绍完了由TcpServer引出的第一个类Acceptor!
继续来看TcpServer类,发现里面有几个函数回调, connectionCallback_、messageCallback_、writeCompleteCallback_函数,这几个函数暂时留着下面解释.在这里有一个Map类型的变量connection_,既然是一个服务器,那么肯定保留着在这个服务器上的所有连接,这个连接的结合就是connecions_。跟踪到最后,这个变量保存的变量就是TcpConnection,由此也就引出了另外一个重要的类TcpConnecion!其实TcpServer中并没有直接托管所有的客户端连接,map只是保留了指向每一个连接的指针,所以所有TcpConnection所属权并不在TcpServer!
从名字上来看,TcpConneciton类是管理着一个连接到服务器上的一个连接,不错,每一个TcpConnectin管理着一个连接套接字,这个连接套接字就是Acceptor调用accept()系统调用后创建个那么套接字,但是这两者是怎么联系的呢?到目前为止还没见到服务器监听,怎么就开始扯到创建连接这个地步呢?
还记得刚开始muduo库使用方法么?记得TcpServer注册到Acceptor中的newConnectionCallback_函数么?
在应用层代码中调用了TcpServer中的start()函数,这个函数就是的Acceptor处于监听状态(注意这里还遗留了一个问题,既然这里muudo是一个I/O复用的库,怎么没看到调用epoll这类函数就开始监听了呢?(其实在Acceptor类中的listen()函数的最后一句就是将监听套接字放置到epoll管理的文件描述符内),其实是Acceptor中的listen()函数中的最后一句话,下文解释!),使得监听套接字处于监听状态以后,就可以接受外部链接了,那么接受函数accept()是在Acceptor中的handleRead()函数中调用的,那么这里就又要遗留一个问题了,handleRead()是在哪里调用的呢?暂时不管遗留的几个问题,咱们只知道TcpServer中的start()函数使得管理监听套接字的Acceptor类中的监听套接字处于监听状态,Acceptor中的handleRead()函数被触发以后调用accept()系统调用来接受一个新的连接,同时调用了TcpServer注册的回调函数newConnection,正是这个函数将TcpConneciotn类拉上了舞台!
分析newConnection印发额一系列操作:
当服务器中的Acceptor接受到一个连接,就调用了这个函数,在这个函数内创建了一个TcpConnection类,并且从threadPoo中选择一个EveentLoop,将这个新的连接交付给这个EventLoop!(这句话的两个新词非常重要,正是这个构建了muduo的per reactor per thread的框架,首先从线程池内选择一个EventLoop,将这个连接托付给这个EveentLoop,而且我们知道一个EventLoop就是一个Reactor,这就是所谓的main Reactor和sub Reactor的思想!如果这里没有创建threadPool_,那么我们就只有一个EventLoop,而且这个EventLoop是就是用户空间定义的那个EventLoop,如果用户代码设置了创建threadPool,也就是创建了多个sub Reactor的话,这里就可以选择一个EventLoop了!)同时这个函数还进行了几个设置,调用的函数都是set*系列,那么这些函数参数都是从哪里来的呢?很明显,newConneciton属于TcpServer,函数参数自然就是TcpServer的变量喽,在上面也提到了TcpServer中存在的几个函数定义(connectionCallback_、messageCallback_、writeCompleteCallback_),那么这些函数定义是从哪里来呢?看谁在使用TcpServer,这么说来就是用户了,用户使用了TcpServer,那么用户就必须负责给TcpServer中的这个几个变量进行赋值,这么一说,从用户层定义的这几个函数赋值给了TcpServer,然后在渗透到TcpConnection中!我们假设系统只有一个Reactor,也就是只有一个EventLoop。这newConneciton这个函数中set系列的函数只是赋值,但是最后一行是执行,因为只有一个EventLoop,所以我们认为那句话就是直接运行TcpConnection::connectEstable函数。(在这个函数中我们好像见到了在Acceptor类中的listen()函数也见到的一个调用enableReading(),好熟悉,但是隐约感觉到了它的伟大!)然后就是调用connectionCallback_函数,记住这个函数是在用户层定义通过TcpServer渗透过来的!这么一来,在这里使用了用户层的代码!分析了引出TcpConneciton这个类的newConneciton函数,来看看这个类!
回过头来看,TcpServer引出的Accpetor管理着监听套接字,解析TcpServer::newConnection函数引出的TcpConnection类管理着连接套接字。而在TcpServer只需要管理着一个Acceptor(假设一个服务器只管理一个监听套接字)再管理一个TcpConnection的指针集合集合(Connectionmap)!在TcpConnection类中还是有一个EventLoop指针(目前为止介绍的三个类都存在了这么一个定义),在管理套接字的类(Acceptor类和TcpConnection类)中还会还有一个Channel,Channel和EventLoop都是重量级的类!
TcpConnection类中没有什么特别的东西,只是管理了一个连接套接字和几个回调(而且这几个回调都是从用户层传递给TcpServer然后再渗透到这里的),但是里面有几个很有重量的函数,从感觉上来说,连接套接字上可读、可写、可关闭、可错误处理,还记得Acceptor的接受是在哪个函数中挖成的么?在Acceptor内的handleRead()函数,在TcpConnection类中有handlRead()、handleWrite()、handleClose()函数,我们很清楚只要是套接字上,肯定是需要交互的,肯定是有可读可写发生的,从上面的分析,我们恍惚感觉到了是管理套接字(监听&连接)的类的handleRead handleWrite系列函数完成了套接字上的读写操作,那么这些函数是什么时候在哪里被激发的呢?这里我们需要引入Channel类了,由Accetor和TcpConnection类一起来引入这个Channel类!
也就是说,管理套接字的类中都会有一个Channel类,在之前说过Channel是有一个桥接作用的,那么它桥接的是什么呢?(冥冥之中我们应该有一定意识,因为到目前为止,仍然没有介绍muduo中的Reactor驱动器,还没有牵连到I/O复用的操作),在这之前,我们先来看看Channel类的内容!
这个类中的内容非常工整,所说Channel不能拥有套接字,但是在创建这个类的时候都传递了这个套接字!既然Acceptor和TcpConnection类中都使用了Channel类,那么我们就挑选TcpConneciton来分析怎么使用Channel类的,在TcpConnection的构造函数中,使用了Channe类的set*系列函数进行复制,将TcpConnection中的handleRead hadleWite handleClose handleError(要知道在这些函数中调用了从用户层传递给TcpServer并且渗透到TcpConnection中的messageCallback_ writeCompleteCallback_函数)函数赋值给了Channel中的readCallback_writeCallback_ closeCallback_。同时我们也看到了前面提到的感觉很伟大的enableReading()函数,在Acceptor中的listen()函数中调用了Channel中的enableReding()函数,在TcpConnection中的connectEstablished()函数也调用了这个函数,那么connectEstablished什么时候被调用了呢?能不能猜得到,应该在创建一个新的连接的时候吧,也就是TcpServer::newConnection中被调用的!
Channel的这个函数是干什么用的呢?尤其是最后的那个update()函数,还有和这个函数类似的enableWriteing(),我们跟踪这个函数,这么一来,发现调用了EventLoop的updateChannel()函数,这么一来,我们就必须引入EventLoop这个大块头了?
在Channel中还有一个函数就是handleEvent()函数,先来解释这个,我们发现在这个函数中最后调用了Channel中的readCallback_ writeCallback_ errorCallback_等这些函数,但是这些函数是在哪里注册的呢?是拥有Channel的类中的进行注册的!那么就是TcpConnection和Acceptor,后者将内部的handleRead handleWrite handleClose(当然这里可是有从用户渗透过来的消息处理函数的)这些函数注册到Channel中的readCallback_ writeCallback_ errorCallback。这么一来,我们已经知道消息处理的函数调用是在Channel的handlEvent函数中被调用的,当某个套接字上有事件发生时,我们只需要调用和这个套接字绑定的Channel类的handleEvent函数即可!到此为止,我们明白了事件的处理流程,已经用户的消息处理是如何被传递的,现在唯一的关键就是Channel中的handleEvent何时被调用!
事已至此,我们也不得不引入EventLoop类了,这个类是有Channel的update引入的!我们已经明白EvenLoop就是一个Reactor,就是一个驱动器,我们是不是感觉到Channel是套接字和EvenTLoop之间的桥梁!是连接套接字和驱动器的桥梁。但是我们知道一个Channel中有一个套接字,但是这个Channel不拥有套接字,他是不管理套接字的生命周期的!他们之间只是绑定,套接字的拥有者是Acceptor和TcpConnection。
介绍EventLop:
我们已经才想到这个一个Reactor,那么它肯定有一个I/O复用,就是一个驱动器,就是变量poller_,那么poller_需要知道它所要关注的所有套接字,那么poller_怎么知道呢,就是通过Channel中的enableReading()调用update()函数,调用EventLoop的updateChannel来实现的。由于每个套接字和一个Channel相关联。所以EventLoop只需要管理所有需要关注的套接字相关的Channel即可,所以这里有一个ChannelList,EventLoop只需要关注有事件的套接字,在Poller_返回后将有事件发生的套接字作为一个集合,activeChannels_就是被激活的套机字所在的Channel组成的结合!还记得刚开始介绍muudo库使用方法的时候介绍的调用EvengLoop的loop()函数么?在这个函数中,首先调用I/O复用,等待着有激活事件的发生,将所有的被激活的事件存放到activeChannels中,然后调用每个Channel的handleEvent函数(还记得这个函数的威力么,在这个函数内,来辨别这个套接字上的可读可写事件,然后调用readCallback_ writeCallback_closeCallback_等一系列的函数,这些函数是Acceptor和TcpConnection中的handleReadhandleWrite handleClose函数,而这些函数中调用了用户层定义的通过TcpServer传递渗透到TcpConnection中的消息处理函数)
走到这里,其实我们是为了超找loop->updataChannel这个函数而来的,不觉间已经走偏了!这个函数中调用了poller_->updateChannel()函数,到了这里,我们就不再深究了,我明确的告诉你,这个poller_->updateChannel()函数就是更新了I/O复用的关注的事件集合!
走到这里,我们已经大概把muduo库的但Reactor模式的工作流程已经介绍完了!下面再梳理下各个类的作用:
TcpServer:
1、里面没有一个套接字,而是由一个管理监听套接字的类Acceptor来管理,里面只有这么一个套接字。
2、它不管理连接套接字,只有一个map管理这指向连接套接字的指针,同时这个服务器需要用户层的消息处理函数的注册(通过TcpServer穿过TcpConnection,然后经过TcpConnection的handleRead handleWrite handleClose等一系列的函数注册到Channel的readCallbackwriteCallback,而Channel中的handleEvent同意接管Channel的readCallback writeCallback)
2、一旦接受到一个客户端的连接,就会调用TcpServer中的newConnection函数。
3、start()函数使得Acceptor类管理的监听套接字处于监听状态。
Acceptor类:
1、 这个类中管理着一个监听套接字,在被TcPServer初始化的时候就收了newConnection函数来,后者是创建一个连接套接字属于的类
2、 Listen被TcpServer中的start函数调用,最后的enablereading()使得监听套接字添加到epoll中。
3、 监听套接字上可读,那么监听套接字对应的Channel调用handleEvent来处理,就调用了Acceptor中的handleRead函数,内部使用了TcpServer注册给他的newConnection来创建一个新的客户端连接!在newConnection中选择一个合适的EveentLoop将这个套接字进行托管!
TcpConnection类:
1、表示一个新的连接。定义了Channel需要的handleReadhandleWrite handleClose等函数
属于一个内部的类,所以对外的接口没有!
EventLoop:
1、 驱动器,关于被激活的事件!成员变量poller_包含着这个驱动器需要关注的所有套接字,这个套接字是怎么被添加的呢?对于Acceptor来说,在Listen()函数中,调用了Channel->enablereading(),然后调用了eventLoop的updateChannel函数!
2、 对于链接套接字,在newConnection中的connectEstablished函数中完成添加!
到这里为止,我们是在接受单个Reactor的流程,这并不muduo的真意,他的思想是:
有一个main reactor,这个main reactor只管接受新的练级,一旦创建好新的连接,就从EventloopThreadPool中选择一个合适的EventLoop来托管这个连接套接字。这个EventLoop就是一个sub reactor。
至于这种模式的使用方法和流程,下回分解!
EvenLoop内部的WakeupFd_是供线程内部使用的套接字,不是用来通信的!因为这里线程间也没必要通信!(个人理解)
我觉得正是pendingFunctors_和wakeupFd_使得很多个Reactor处理很简单。
比如在main reactor中接收到一个新的连接,那么就是在Acceptor中的handleRead函数中的accept结束后调用了newConnection,在这个函数中从EventLoopThreadPoll中选择一个EventLoop,让这个子reactor运行接下来的任务(就是connectionEstablished来将这个连接套接字添加到sub reactor中,那么就是调用了EventLoop的runInLoop函数,此函数最后调用了queueInLoop函数,queueInLoop函数将函数添加到pendingFunctors_中,然后直接调用wakeup()来唤醒这个线程,为啥要唤醒呢?因为一旦唤醒,那么就是EventLoop中的loop())函数返回,在函数返回以后有一个专门处理pendingFunctors_集合的函数,那么什么时候需要唤醒呢?如果调用runInLoop函数的线程和runInLoop所在的EvenLoop所属的线程不是同一个(要明白TcpSercver中的EventLoopThredPool,使得每一个线程都拥有一个EventLoop)或前的EventLoop正在处理pendingFunctors_中的函数。
那么这种事情什么时候发生呢?我们明白TcpServer中肯定拥有一个EventLoop,因为在用户层定义了一个EventLoop,TcpServer绑定到这个EventLoop上,如果用户使用了TcpServer中的EventLoopThreadPool,那么每个线程中包含了一个EventLoop。还记得main Reactor负责接收新的连接吧,TcpServer中的Acceptor调用了accept后直接回调了TcpServer中的newConnection,在最后选择了一个ioLoop作为托管新连接的EventLoop。然后调用了ioLoop->runInLoop(),那么这个时候就需要唤醒了,因为调用runInLoop的线程和runInloop所在线程不是同一个!那么将个调用(也就是connectEstablished)添加到pendingFunctors_中,然后唤醒本线程,使得pendingFunctors_内的connectEstablished可以被调用!