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这个类,从名字来看,它是一个服务器,里面肯定需要有一个用于监听某个地址的套接字,这个是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可以被调用!

时间: 2024-10-10 07:47:36

muduo源码分析--我对muduo的理解的相关文章

[JavaSE 源码分析] 关于HashMap的个人理解

目录 HashMap是什么? HashMap的底层数据结构是什么? table容量为什么必须是二的倍数? table容量怎么做到二的倍数? Entry是怎样的结构? Node: Entry在HashMap中的具体实现 处理hash冲突的方法 HashMap初始化或扩容 resize() HashMap计算元素的hash HashMap添加/更新元素 HashMap取值 HashMap删除元素 HashMap为什么是非线程安全的? HashMap在并发场景下可能存在哪些问题? 通过Debug来进一

muduo源码分析--Reactor模式在muduo中的使用

一. Reactor模式简介 Reactor释义"反应堆",是一种事件驱动机制.和普通函数调用的不同之处在于:应用程序不是主动的调用某个API完成处理,而是恰恰相反,Reactor逆置了事件处理流程,应用程序需要提供相应的接口并注册到Reactor上,如果相应的时间发生,Reactor将主动调用应用程序注册的接口,这些接口又称为"回调函数". 二. moduo库Reactor模式的实现 muduo主要通过3个类来实现Reactor模式:EventLoop,Chann

muduo源码分析--Reactor模式的在muduo中的使用(二)

一. TcpServer类: 管理所有的TCP客户连接,TcpServer供用户直接使用,生命期由用户直接控制.用户只需设置好相应的回调函数(如消息处理messageCallback)然后TcpServer::start()即可. 主要数据成员: boost::scoped_ptr<Accepter> acceptor_; 用来接受连接 std::map<string,TcpConnectionPtr> connections_; 用来存储所有连接 connectonCallbac

MyBatis源码分析(三)-SqlSession理解

SqlSession理解 一.创建SqlSession 1.创建事务 a.事务工厂根据DataSource创建一个事务对象(Connection对象,事务级别,是否自动提交) b.事务工厂根据Connection创建一个事务对象(事务级别和是否自动提交已经被设置在Connection对象中) c.系统默认的事务工厂是ManagedTransactionFactory,默认是关闭连接的 private boolean closeConnection = true; 2.创建Executor(sta

Libevent源码分析-timer和signal处理

timer处理 Signal处理 timerfd和signalfd timerfd signalfd timer处理 在Libevent源码分析-event处理流程中,使用了定时器,来看一下源码: evtimer_set(&ev, time_cb, NULL);//设置定时器事件 其中evtimer_set是个宏定义 #define evtimer_set(ev, cb, arg) event_set((ev), -1, 0, (cb), (arg)) //event_set原型 void ev

TeamTalk源码分析之login_server

login_server是TeamTalk的登录服务器,负责分配一个负载较小的MsgServer给客户端使用,按照新版TeamTalk完整部署教程来配置的话,login_server的服务端口就是8080,客户端登录服务器地址配置如下(这里是win版本客户端): 1.login_server启动流程 login_server的启动是从login_server.cpp中的main函数开始的,login_server.cpp所在工程路径为server\src\login_server.下表是logi

Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)

1 背景 还记得前一篇<Android触摸屏事件派发机制详解与源码分析一(View篇)>中关于透过源码继续进阶实例验证模块中存在的点击Button却触发了LinearLayout的事件疑惑吗?当时说了,在那一篇咱们只讨论View的触摸事件派发机制,这个疑惑留在了这一篇解释,也就是ViewGroup的事件派发机制. PS:阅读本篇前建议先查看前一篇<Android触摸屏事件派发机制详解与源码分析一(View篇)>,这一篇承接上一篇. 关于View与ViewGroup的区别在前一篇的A

HashMap与TreeMap源码分析

1. 引言     在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Java这么久,也写过一些小项目,也使用过TreeMap无数次,但到现在才明白它的实现原理).因此本着"不要重复造轮子"的思想,就用这篇博客来记录分析TreeMap源码的过程,也顺便瞅一瞅HashMap. 2. 继承结构 (1) 继承结构 下面是HashMap与TreeMap的继承结构: pu

Linux内核源码分析--内核启动之(5)Image内核启动(rest_init函数)(Linux-3.0 ARMv7)【转】

原文地址:Linux内核源码分析--内核启动之(5)Image内核启动(rest_init函数)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.chinaunix.net/uid-25909619-id-4938395.html 前面粗略分析start_kernel函数,此函数中基本上是对内存管理和各子系统的数据结构初始化.在内核初始化函数start_kernel执行到最后,就是调用rest_init函数,这个函数的主要使命就是创建并启动内核线