muduo源代码分析--我对muduo的理解
分为几个模块 EventLoop、TcpServer、Acceptor、TcpConnection、Channel等
对于EventLoop来说:
他仅仅关注里面的主驱动力,EventLoop中仅仅关注poll,这类系统调用使得其成为Reactor模式,EventLoop中有属于这个loop的全部Channel,这个loop属于哪一个Server.
几个类存在的意义:
从应用层使用的角度来看。用户须要初始化一个EventLoop。然后初始化一个TcpServer(当然也能够自己定义个TcpServer,自己定义数据处理函数须要注冊到TcpServer内),然后调用TcpServer的start函数,然后调用EventLoop的loop()函数。
整个用户层的使用流程就是这种。
从用户层的应用方法来解析Muduo库的设计思想:
首先来看TcpServer这个类,从名字来看。它是一个server,里面肯定须要有一个用于监听某个地址的套接字,这个是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_。既然是一个server,那么肯定保留着在这个server上的全部连接。这个连接的结合就是connecions_。
跟踪到最后,这个变量保存的变量就是TcpConnection,由此也就引出了另外一个重要的类TcpConnecion!事实上TcpServer中并没有直接托管全部的client连接,map仅仅是保留了指向每个连接的指针,所以全部TcpConnection所属权并不在TcpServer!
从名字上来看,TcpConneciton类是管理着一个连接到server上的一个连接,不错,每个TcpConnectin管理着一个连接套接字。这个连接套接字就是Acceptor调用accept()系统调用后创建个那么套接字,可是这两者是怎么联系的呢?到眼下为止还没见到server监听,怎么就開始扯到创建连接这个地步呢?
还记得刚開始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印发额一系列操作:
当server中的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(如果一个server仅仅管理一个监听套接字)再管理一个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管理这指向连接套接字的指针。同一时候这个server须要用户层的消息处理函数的注冊(通过TcpServer穿过TcpConnection,然后经过TcpConnection的handleRead handleWrite handleClose等一系列的函数注冊到Channel的readCallbackwriteCallback,而Channel中的handleEvent允许接管Channel的readCallback writeCallback)
2、一旦接受到一个client的连接,就会调用TcpServer中的newConnection函数。
3、start()函数使得Acceptor类管理的监听套接字处于监听状态。
Acceptor类:
1、 这个类中管理着一个监听套接字,在被TcPServer初始化的时候就收了newConnection函数来。后者是创建一个连接套接字属于的类
2、 Listen被TcpServer中的start函数调用,最后的enablereading()使得监听套接字加入到epoll中。
3、 监听套接字上可读,那么监听套接字相应的Channel调用handleEvent来处理。就调用了Acceptor中的handleRead函数,内部使用了TcpServer注冊给他的newConnection来创建一个新的client连接!在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处理非常easy。
比方在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能够被调用!