muduo学习笔记(二)Reactor关键结构


Reactor简述

什么是Reactor

Reactor是一种基于事件驱动的设计模式,即通过回调机制,我们将事件的接口注册到Reactor上,当事件发生之后,就会回调注册的接口。

Reactor必要的几个组件

Event Multiplexer事件分发器:即一些I/O复用机制select、poll、epoll等.程序将事件源注册到分发器上,等待事件的触发,做相应处理.

Handle事件源:用于标识一个事件,Linux上是文件描述符.

Reactor反应器:用于管理事件的调度及注册删除.当有激活的事件时,则调用回调函数处理,没有则继续事件循环.

event handler事件处理器:管理已注册事件和的调度,分成不同类型的事件(读/写,定时)当事件发生,调用对应的回调函数处理.

Reactor模型的优缺点

优点

1)响应快,不必为单个同步时间所阻塞,虽然Reactor本身依然是同步的;

2)编程相对简单,可以最大程度的避免复杂的多线程及同步问题,并且避免了多线程/进程的切换开销;

3)可扩展性,可以方便的通过增加Reactor实例个数来充分利用CPU资源;

4)可复用性,reactor框架本身与具体事件处理逻辑无关,具有很高的复用性;

缺点

Reactor模式在IO读写数据时还是在同一个线程中实现的,即使使用多个Reactor机制的情况下,那些共享一个Reactor的Channel如果出现一个长时间的数据读写,会影响这个Reactor中其他Channel的相应时间,比如在大文件传输时,IO操作就会影响其他Client的相应时间,因而对这种操作,使用传统的Thread-Per-Connection或许是一个更好的选择,或则此时使用Proactor模式。

poll简述

  1. poll的使用方法与select相似,轮询多个文件描述符,有读写时设置相应的状态位,poll相比select优在没有最大文件描述符数量的限制.
  1. # include <poll.h>
  2. int poll ( struct pollfd * fds, unsigned int nfds, int timeout);
  3. struct pollfd {
  4. int fd; /* 文件描述符 */
  5. short events; /* 等待的事件 */
  6. short revents; /* 实际发生了的事件 */
  7. } ;

  每一个pollfd结构体指定了一个被监视的文件描述符,可以传递多个结构体,指示poll()监视多个文件描述符。每个结构体的events域是监视该文件描述符的事件掩码,由用户来设置这个域。revents域是文件描述符的操作结果事件掩码,内核在调用返回时设置这个域。events域中请求的任何事件都可能在revents域中返回。合法的事件如下:

  POLLIN         有数据可读。

  POLLRDNORM      有普通数据可读。

  POLLRDBAND      有优先数据可读。

  POLLPRI         有紧迫数据可读。

  POLLOUT       写数据不会导致阻塞。

  POLLWRNORM      写普通数据不会导致阻塞。

  POLLWRBAND      写优先数据不会导致阻塞。

  POLLMSGSIGPOLL     消息可用。

poll使用样例

  1. #include <fcntl.h>
  2. #include <stdio.h>
  3. #include <unistd.h>
  4. #include <stdlib.h>
  5. #include <string.h>
  6. #include <time.h>
  7. #include <errno.h>
  8. #include <poll.h>
  9. #define MAX_BUFFER_SIZE 1024
  10. #define IN_FILES 1
  11. #define MAX(a,b) ((a>b)?(a):(b))
  12. int main(int argc ,char **argv)
  13. {
  14. struct pollfd fds[3];
  15. char buf[1024];
  16. int i,res,real_read, maxfd;
  17. if((fds[0].fd=open("/dev/stdin",O_RDONLY|O_NONBLOCK)) < 0)
  18. {
  19. fprintf(stderr,"open data1 error:%s",strerror(errno));
  20. return 1;
  21. }
  22. for (i = 0; i < IN_FILES; i++)
  23. {
  24. fds[i].events = POLLIN | POLLPRI;
  25. }
  26. while(1) //|| fds[1].events || fds[2].events)
  27. {
  28. int ret = poll(fds, 1, 1000);
  29. if (ret < 0)
  30. {
  31. printf("Poll error : %s\n",strerror(errno));
  32. return 1;
  33. }
  34. if(ret == 0){
  35. printf("Poll timeout\n");
  36. continue;
  37. }
  38. for (i = 0; i< 1; i++)
  39. {
  40. if (fds[i].revents)
  41. {
  42. memset(buf, 0, MAX_BUFFER_SIZE);
  43. real_read = read(fds[i].fd, buf, MAX_BUFFER_SIZE);
  44. if (real_read < 0)
  45. {
  46. if (errno != EAGAIN)
  47. {
  48. printf("read eror : %s\n",strerror(errno));
  49. continue;
  50. }
  51. }
  52. else if (!real_read)
  53. {
  54. close(fds[i].fd);
  55. fds[i].events = 0;
  56. }
  57. else
  58. {
  59. if (i == 0)
  60. {
  61. buf[real_read] = '\0';
  62. printf("%s", buf);
  63. if ((buf[0] == 'q') || (buf[0] == 'Q'))
  64. {
  65. printf("quit\n");
  66. return 1;
  67. }
  68. }
  69. else
  70. {
  71. buf[real_read] = '\0';
  72. printf("%s", buf);
  73. }
  74. }
  75. }
  76. }
  77. }
  78. exit(0);
  79. }

muduo Reactor关键结构

  1. muduo Reactor最核心的事件分发机制, 即将IO multiplexing拿到的IO事件分发给各个文件描述符(fd)的事件处理函数。

Channel

Chanel目前我对它的理解是,它负责管理一个文件描述符(file descript)IO事件.

Channel会封装C的poll事件,把不同的IO事件分发到不同的回调:ReadCallBack、WriteCallBack等

每个Channel对象自始至终只属于一个EventLoop,因此每个Channel对象都只属于某一个IO线程。 每个Channel对象自始至终只负责一个文件描述符(fd) 的IO事件分发

  1. #ifndef NET_CHANNEL_H
  2. #define NET_CHANNEL_H
  3. #include <functional>
  4. #include "EventLoop.hh"
  5. class Channel {
  6. public:
  7. typedef std::function<void()> EventCallBack;
  8. Channel(EventLoop* loop, int fd);
  9. ~Channel();
  10. void handleEvent();
  11. void setReadCallBack(const EventCallBack& cb) { m_readCallBack = cb; }
  12. void setWriteCallBack(const EventCallBack& cb) { m_writeCallBack = cb; }
  13. void setErrorCallBack(const EventCallBack& cb) { m_errorCallBack = cb; }
  14. int fd() const { return m_fd; }
  15. int events() const { return m_events; }
  16. void set_revents(int revt) { m_revents = revt; }
  17. bool isNoneEvent() const { return m_events == kNoneEvent; }
  18. void eableReading() { m_events |= kReadEvent; update(); }
  19. int index() { return m_index; }
  20. void set_index(int idx) { m_index =idx; }
  21. EventLoop* ownerLoop() { return m_pLoop; }
  22. private:
  23. Channel& operator=(const Channel&);
  24. Channel(const Channel&);
  25. void update();
  26. static const int kNoneEvent;
  27. static const int kReadEvent;
  28. static const int kWriteEvent;
  29. EventLoop* m_pLoop;
  30. const int m_fd;
  31. int m_events; // 等待的事件
  32. int m_revents; // 实际发生了的事件
  33. int m_index;
  34. EventCallBack m_readCallBack;
  35. EventCallBack m_writeCallBack;
  36. EventCallBack m_errorCallBack;
  37. };
  38. #endif
  39. //Channel.cpp
  40. #include <poll.h>
  41. #include "Channel.hh"
  42. #include "Logger.hh"
  43. const int Channel::kNoneEvent = 0;
  44. const int Channel::kReadEvent = POLLIN | POLLPRI;
  45. const int Channel::kWriteEvent = POLLOUT;
  46. Channel::Channel(EventLoop* loop, int fd)
  47. : m_pLoop(loop),
  48. m_fd(fd),
  49. m_events(0),
  50. m_revents(0),
  51. m_index(-1)
  52. {
  53. }
  54. Channel::~Channel()
  55. {
  56. }
  57. void Channel::update()
  58. {
  59. m_pLoop->updateChannel(this);
  60. }
  61. void Channel::handleEvent()
  62. {
  63. if(m_revents & POLLNVAL)
  64. {
  65. LOG_WARN << "Channel::handleEvent() POLLNVAL";
  66. }
  67. if(m_revents & (POLLERR | POLLNVAL)){
  68. if(m_errorCallBack) m_errorCallBack();
  69. }
  70. if(m_revents & (POLLIN | POLLPRI | POLLRDHUP)){
  71. if(m_readCallBack) m_readCallBack();
  72. }
  73. if(m_revents & POLLOUT){
  74. if(m_writeCallBack) m_writeCallBack();
  75. }
  76. }

值得一提的就是 Channel::update()它会调用EventLoop::updateChannel(), 后者会转而调

用Poller::updateChannel()。Poller对象下面会讲,通过Poller::updateChannel()注册IO事件(即file descript).

Channel::handleEvent()是Channel的核心, 它由EventLoop::loop()调

用, 它的功能是根据revents发生事件的的值分别调用不同的用户回调。 这个函数以后还会扩充。

Poller

Poller class是IO multiplexing的封装。 它现在是个具体类,而在muduo中是个抽象基类,因为muduo同时支持poll(2)和epoll(4)两种IOmultiplexing机制。

Poller是EventLoop的间接成员,只供其自己在EventLoop的IO线程中调用,因此无须加锁。其生命期与EventLoop相等。

Poller并不拥有管理文件描述符事件的Channel, Channel在析构之前必须自己

unregister(EventLoop::removeChannel()) , 避免空悬指针

  1. #ifndef _NET_POLLER_HH
  2. #define _NET_POLLER_HH
  3. #include <vector>
  4. #include <map>
  5. #include "TimeStamp.hh"
  6. #include "EventLoop.hh"
  7. #include "Channel.hh"
  8. struct pollfd;
  9. class Poller{
  10. public:
  11. typedef std::vector<Channel*> ChannelList;
  12. Poller(EventLoop* loop);
  13. ~Poller();
  14. TimeStamp poll(int timeoutMs, ChannelList* activeChannels);
  15. void updateChannel(Channel* channel);
  16. void assertInLoopThread() { m_pOwerLoop->assertInLoopThread(); }
  17. private:
  18. Poller& operator=(const Poller&);
  19. Poller(const Poller&);
  20. void fillActiveChannels(int numEvents, ChannelList* activeChannels) const;
  21. typedef std::vector<struct pollfd> PollFdList;
  22. typedef std::map<int, Channel*> ChannelMap;
  23. EventLoop* m_pOwerLoop;
  24. PollFdList m_pollfds;
  25. ChannelMap m_channels;
  26. };
  27. #endif
  28. //Poller.cpp
  29. #include "Poller.hh"
  30. #include "Logger.hh"
  31. #include <assert.h>
  32. #include <poll.h>
  33. #include <signal.h>
  34. Poller::Poller(EventLoop* loop)
  35. : m_pOwerLoop(loop)
  36. {
  37. }
  38. Poller::~Poller()
  39. {
  40. }
  41. TimeStamp Poller::poll(int timeoutMs, ChannelList* activeChannels)
  42. {
  43. LOG_TRACE << "Poller::poll()";
  44. int numEvents = ::poll(/*&*m_pollfds.begin()*/m_pollfds.data(), m_pollfds.size(), timeoutMs);
  45. TimeStamp now(TimeStamp::now());
  46. if(numEvents > 0){
  47. LOG_TRACE << numEvents << " events happended";
  48. fillActiveChannels(numEvents, activeChannels);
  49. }
  50. else if(numEvents == 0){
  51. LOG_TRACE << " nothing happended";
  52. }
  53. else{
  54. LOG_SYSERR << "Poller::poll()";
  55. }
  56. return now;
  57. }
  58. /*
  59. *fillActiveChannels()遍历m_pollfds, 找出有活动事件的fd, 把它对应
  60. *的Channel填入activeChannels。
  61. */
  62. void Poller::fillActiveChannels(int numEvents, ChannelList* activeChannels) const
  63. {
  64. for(PollFdList::const_iterator pfd = m_pollfds.begin();
  65. pfd != m_pollfds.end() && numEvents > 0; ++pfd)
  66. {
  67. if(pfd->revents > 0)
  68. {
  69. --numEvents;
  70. ChannelMap::const_iterator ch = m_channels.find(pfd->fd);
  71. assert(ch != m_channels.end());
  72. Channel* channel = ch->second;
  73. assert(channel->fd() == pfd->fd);
  74. channel->set_revents(pfd->revents);
  75. activeChannels->push_back(channel);
  76. }
  77. }
  78. }
  79. void Poller::updateChannel(Channel* channel)
  80. {
  81. assertInLoopThread();
  82. LOG_TRACE << "fd= " << channel->fd() << " events" << channel->events();
  83. if(channel->index() < 0){
  84. //a new one , add to pollfds
  85. assert(m_channels.find(channel->fd()) == m_channels.end());
  86. struct pollfd pfd;
  87. pfd.fd = channel->fd();
  88. pfd.events = static_cast<short>(channel->events());
  89. pfd.revents = 0;
  90. m_pollfds.push_back(pfd);
  91. int idx = static_cast<int>(m_pollfds.size()) - 1;
  92. channel->set_index(idx);
  93. m_channels[pfd.fd] = channel;
  94. }
  95. else{
  96. //update existing one
  97. assert(m_channels.find(channel->fd()) != m_channels.end());
  98. assert(m_channels[channel->fd()] == channel);
  99. int idx = channel->index();
  100. assert(0 <= idx && idx < static_cast<int>(m_pollfds.size()));
  101. struct pollfd& pfd = m_pollfds[idx];
  102. assert(pfd.fd == channel->fd() || pfd.fd == -1);
  103. pfd.events = static_cast<short>(channel->events());
  104. pfd.revents = 0;
  105. if(channel->isNoneEvent()){
  106. //ignore this pollfd
  107. pfd.fd = -1;
  108. }
  109. }
  110. }

EventLoop

  1. EventLopp在上一篇文章写过,这里给出改动.

EventLoop 新增了quit()成员函数, 还加了几个数据成员,并在构造函数里初始化它们。注意EventLoop通过智能指针来间接持有poller.

  1. +class Poller;
  2. +class Channel;
  3. class EventLoop
  4. ------------
  5. bool isInloopThread() const {return m_threadId == CurrentThread::tid(); }
  6. +void quit();
  7. +void updateChannel(Channel* channel);
  8. static EventLoop* getEventLoopOfCurrentThread();
  9. private:
  10. EventLoop& operator=(const EventLoop&);
  11. EventLoop(const EventLoop&);
  12. void abortNotInLoopThread();
  13. +typedef std::vector<Channel*> ChannelList;
  14. bool m_looping;
  15. +bool m_quit;
  16. const pid_t m_threadId;
  17. +std::unique_ptr<Poller> m_poller;
  18. +ChannelList m_activeChannels;
  19. };
  20. //EventLoop.cpp
  21. m_threadId(CurrentThread::tid()),
  22. + m_poller(new Poller(this))
  23. {
  24. ------
  25. +void EventLoop::quit()
  26. +{
  27. + m_quit = true;
  28. + //wakeup();
  29. +}
  30. +
  31. +void EventLoop::updateChannel(Channel* channel)
  32. +{
  33. + assert(channel->ownerLoop() == this);
  34. + assertInLoopThread();
  35. + m_poller->updateChannel(channel);
  36. +}

上一篇文章的EventLoop->loop()什么也没做,现在它有了实实在在的使命,它调用Poller::poll()获得当前活动事件的Chanel列表, 然后依次调用每个Channel的handleEvent()函数

  1. void EventLoop::loop()
  2. {
  3. assert(!m_looping);
  4. assertInLoopThread();
  5. m_looping = true;
  6. m_quit = false;
  7. LOG_TRACE << "EventLoop " << this << " start loopig";
  8. while(!m_quit)
  9. {
  10. m_activeChannels.clear();
  11. m_poller->poll(1000, &m_activeChannels);
  12. for(ChannelList::iterator it = m_activeChannels.begin();
  13. it != m_activeChannels.end(); ++it)
  14. {
  15. (*it)->handleEvent();
  16. }
  17. }
  18. LOG_TRACE << "EventLoop " << this << " stop loopig";
  19. m_looping = false;
  20. }

Reactor时序图

测试程序-单次触发的定时器

程序利用timerfd_create 把时间变成了一个文件描述符,该“文件”在定时器超时的那一刻变得可读,这样就能很方便地融入到 select/poll 框架中,用统一的方式来处理 IO 事件和超时事件,这也正是 Reactor 模式的长处。

  1. #include <errno.h>
  2. #include <thread>
  3. #include <strings.h>
  4. #include "EventLoop.hh"
  5. #include "Channel.hh"
  6. #include "Poller.hh"
  7. //Reactor Test
  8. //单次触发定时器
  9. #include <sys/timerfd.h>
  10. EventLoop* g_loop;
  11. void timeout()
  12. {
  13. printf("timeout!\n");
  14. g_loop->quit();
  15. }
  16. int main()
  17. {
  18. EventLoop loop;
  19. g_loop = &loop;
  20. int timerfd = ::timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK |TFD_CLOEXEC);
  21. Channel channel(&loop, timerfd);
  22. channel.setReadCallBack(timeout);
  23. channel.eableReading();
  24. struct itimerspec howlong;
  25. bzero(&howlong, sizeof howlong);
  26. howlong.it_value.tv_sec = 3;
  27. timerfd_settime(timerfd, 0, &howlong, NULL);
  28. loop.loop();
  29. close(timerfd);
  30. }
  1. ./test.out
  2. 2018-10-31 22:25:54.532487 [TRACE] [EventLoop.cpp:16] [EventLoop] EventLoop Create 0x7FFEB9567CC0 in thread 3075
  3. 2018-10-31 22:25:54.533563 [TRACE] [Poller.cpp:64] [updateChannel] fd= 3 events3
  4. 2018-10-31 22:25:54.534000 [TRACE] [EventLoop.cpp:41] [loop] EventLoop 0x7FFEB9567CC0 start loopig
  5. 2018-10-31 22:25:54.534334 [TRACE] [Poller.cpp:20] [poll] Poller::poll()
  6. 2018-10-31 22:25:55.535827 [TRACE] [Poller.cpp:28] [poll] nothing happended
  7. 2018-10-31 22:25:55.536287 [TRACE] [Poller.cpp:20] [poll] Poller::poll()
  8. 2018-10-31 22:25:56.538334 [TRACE] [Poller.cpp:28] [poll] nothing happended
  9. 2018-10-31 22:25:56.538802 [TRACE] [Poller.cpp:20] [poll] Poller::poll()
  10. 2018-10-31 22:25:57.534175 [TRACE] [Poller.cpp:24] [poll] 1 events happended
  11. timeout!
  12. 2018-10-31 22:25:57.534766 [TRACE] [EventLoop.cpp:55] [loop] EventLoop 0x7FFEB9567CC0 stop loopig

muduo学习笔记(二)Reactor关键结构的更多相关文章

  1. C#学习笔记二:C#程序结构

    从最简单的HelloWorld开始入手,这是一个最低限度的C#程序结构. C# Hello World 示例 一个C#程序主要由以下几部分组成: 命名空间声明 一个类 类方法 类属性 一个Main方法 ...

  2. Android Studio安卓学习笔记(二)Android项目结构

    上一篇代码,我们学习了Android的功能以及如何用Android Studio开发第一个安卓程序.下面就要介绍Android项目结构.为日后学习打基础. 一:Android项目结构 打开MyFris ...

  3. amazeui学习笔记二(进阶开发1)--项目结构structure

    amazeui学习笔记二(进阶开发1)--项目结构structure 一.总结 1.项目结构:是说的amazeui在github上面的项目结构,二次开发amazeui用 二.项目结构structure ...

  4. [Firefly引擎][学习笔记二][已完结]卡牌游戏开发模型的设计

    源地址:http://bbs.9miao.com/thread-44603-1-1.html 在此补充一下Socket的验证机制:socket登陆验证.会采用session会话超时的机制做心跳接口验证 ...

  5. java之jvm学习笔记二(类装载器的体系结构)

    java的class只在需要的时候才内转载入内存,并由java虚拟机的执行引擎来执行,而执行引擎从总的来说主要的执行方式分为四种, 第一种,一次性解释代码,也就是当字节码转载到内存后,每次需要都会重新 ...

  6. java之jvm学习笔记十三(jvm基本结构)

    java之jvm学习笔记十三(jvm基本结构) 这一节,主要来学习jvm的基本结构,也就是概述.说是概述,内容很多,而且概念量也很大,不过关于概念方面,你不用担心,我完全有信心,让概念在你的脑子里变成 ...

  7. 《SQL必知必会》学习笔记二)

    <SQL必知必会>学习笔记(二) 咱们接着上一篇的内容继续.这一篇主要回顾子查询,联合查询,复制表这三类内容. 上一部分基本上都是简单的Select查询,即从单个数据库表中检索数据的单条语 ...

  8. DeepLearning.ai学习笔记(三)结构化机器学习项目--week2机器学习策略(2)

    一.进行误差分析 很多时候我们发现训练出来的模型有误差后,就会一股脑的想着法子去减少误差.想法固然好,但是有点headlong~ 这节视频中吴大大介绍了一个比较科学的方法,具体的看下面的例子 还是以猫 ...

  9. NumPy学习笔记 二

    NumPy学习笔记 二 <NumPy学习笔记>系列将记录学习NumPy过程中的动手笔记,前期的参考书是<Python数据分析基础教程 NumPy学习指南>第二版.<数学分 ...

随机推荐

  1. 【HDU5687】Trie

    题目大意:需要维护一个支持以下操作的数据结构:(1)支持插入一个字符串(2)支持删除所有前缀等于给定字符串的单词(3)查询该数据结构中是否存在一个以给定字符串为前缀的字符串 题解:由题目可知,需要维护 ...

  2. 图像处理之规则裁剪(Resize)

    1 图像裁剪 在实际工作中,经常需要根据研究工作要求对图像进行裁剪(Subset Image),按照实际图像分幅裁剪的过程,可以将图像分幅裁剪分为两种类型:规则分幅裁剪(Rectangle Subse ...

  3. Centos7.4+openvpn-2.4.4+easy-rsa-3.0物理机安装教程

    完整CentOS搭建OpenVPN服务环境图文教程 大福技术 关注 2016.02.17 09:28* 字数 3017 阅读 34000评论 18喜欢 21赞赏 3 对于OpenVPN环境有什么用途老 ...

  4. bzoj千题计划231:bzoj1997: [Hnoi2010]Planar

    http://www.lydsy.com/JudgeOnline/problem.php?id=1997 如果两条边在环内相交,那么一定也在环外相交 所以环内相交的两条边,必须一条在环内,一条在环外 ...

  5. dwz中给表单项获取,设置值

    $.pdialog._current.find('form input#inputId').val(54);

  6. 20155222 2016-2017-2 《Java程序设计》第6周学习总结

    20155222 2016-2017-2 <Java程序设计>第6周学习总结 教材学习内容总结 从应用程序的角度来看,如果要将数据从来源中取出,可以使用输入串流:如果要将数据写入目的地,可 ...

  7. 第9月第26天 pairs和ipairs cocos2dx 动画

    1. a={ ip = "127.0.0.1", port = 6789 } for i,v in pairs(a) do print(i,v) end a={1} for i,v ...

  8. Ubuntu 查看CPU温度

    按照这篇文章: http://www.webupd8.org/2014/06/psensor-updated-with-option-to-display.html

  9. networkManger介绍

    http://www.linuxidc.com/Linux/2013-08/88809.htm

  10. 【干货】SIFT-Workstation 下载与安装 不跳过每一个细节部分

    SIFT-Workstation.ova     下载地址https://digital-forensics.sans.org/community/download-sift-kit       ov ...