moduo网络库的reactor模式(中)

moduo网络库的reactor模式基本构成为“non-blocking I/O + I/O multiplexing”,程序的基本结构是一个事件循环(event loop),以事件驱动(event-driven)和事件回调(event callback)的方式实现业务逻辑。在moduo网络库的reactor模式(上)理清了事件循环(event loop)的基本框架,在此基础上此文加上事件驱动(event-driven)和事件回调(event callback)结构,即reactor最核心的I/O多路复用(I/O mutiplexing)和事件分发(dispatching)机制:进行I/O多路复用并将拿到的I/O事件分发给各个文件描述符(fd)的事件处理函数。


1、事件驱动与分发:I/O多路复用与事件分发

(1)Channel类:封装文件描述符(fd)实现事件分发

每个Channel对象都只属于某一个EventLoop,因此只属于某一个I/O线程。每个Channel对象只负责一个文件描述符的I/O事件分发,Channel会把不同的I/O事件(读、写、错误等)分发为不同的回调。Channel类就是对文件描述符的封装,构造函数Channel(EventLoop* loop, int fd)即将此Channel对象与唯一所属的EventLoop以及文件描述符(fd)绑定了起来。Channel类数据成员events_表示fd事件,用于更新I/O多路复用poll(2)。数据成员revents_表示现正要执行的fd事件,用于事件回调。在目前程序中

1)Channel::enableReading()、Channel::enableWriting()等为设置文件描述符(fd)事件的接口函数:

首先设置fd事件events_,然后执行update()将该Channel的新事件更新到I/O多路复用器poll(2)(update()是通过数据成员EventLoop* loop_,即自己所属的EventLoop对象指针调用EventLoop::updateChannel(),再调用Poller::updateChannel()间接更新poll(2)中的事件。此处疑问:为什么不直接在Channel中添加数据成员Poller指针直接更新事件到poll(2),而是要绕一圈间接更新事件?)

void enableReading() { events_ |= kReadEvent; update(); }
// void enableWriting() { events_ |= kWriteEvent; update(); }

void Channel::update()
{
  loop_->updateChannel(this);
}

2)Channel::setReadCallback(const EventCallback& cb)、Channel::setWriteCallback(const EventCallback& cb)、Channel::setErrorCallback(const EventCallback& cb)为设置fd对应事件的用户回调函数的接口函数。

void setReadCallback(const EventCallback& cb) { readCallback_ = cb; }
void setWriteCallback(const EventCallback& cb) { writeCallback_ = cb; }
void setErrorCallback(const EventCallback& cb) { errorCallback_ = cb; }

3)Channel::handleEvent()是Channel的核心,实现事件分发功能,它由EventLoop::loop()调用,它的功能是根据revents_的值分别调用不同的用户调用。而revents_则是在Poller::poll()中得以更新的。

void Channel::handleEvent()
{
  if (revents_ & POLLNVAL)
    LOG_WARN << "Channel::handle_event() POLLNVAL";

  if (revents_ & (POLLERR | POLLNVAL))
    if (errorCallback_) 
      errorCallback_();

  if (revents_ & (POLLIN | POLLPRI | POLLRDHUP))
    if (readCallback_) 
      readCallback_();

  if (revents_ & POLLOUT) 
    if (writeCallback_) 
      writeCallback_();
}

//inside of function EventLoop::loop():
while (!quit_)
{
    activeChannels_.clear();
    pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_);
    for (ChannelList::iterator it = activeChannels_.begin();
        it != activeChannels_.end(); ++it)
    {
        (*it)->handleEvent();
    }
    doPendingFunctors();
}

(2)Poller类:封装I/O多路复用poll(2)实现I/O多路复用

首先看系统调用poll(2)的函数原型为

#include <poll.h>
int poll(struct pollfd fd[], nfds_t nfds, int timeout);

struct pollfd的结构为

struct pollfd{

 int fd; // 文件描述符

 short event;// 请求的事件

 short revent;// 返回的事件

}

1)为poll(2)提供数据准备:

Poller封装类首先是通过Poller::updateChannel(Channel* channel)将文件描述符(fd)封装类Channel存储到数据成员std::map<int, Channel*> ChannelMap中(具体实现过程大致为:用户调用Channel::update()----->EventLoop::updateChannel()----->Poller::updateChannel())。接着则可将各个Channel对象的数据成员更新给poll(2),并在每次poll(2)后更新各个Channel的revents_。

2)封装I/O多路复用(I/O multiplexing):

Poller::poll(int timeoutMs, ChannelList* activeChannels)函数中首先进行I/O多路复用poll(2),所需参数从数据成员std::map<int, Channel*> ChannelMap中获得,阻塞直到有fd事件发生或定时时间到时,返回事件发生数量numEvents。然后继续执行内部函数Poller::fillActiveChannels(numEvents, activeChannels)将可发生fd事件对应的Channel反馈给外界,即在数据成员std::map<int, Channel*> ChannelMap中找出事件可发生的fd对应的Channel,存放到外部参数ChannelList* activeChannels中。

注意 2)中并未将I/O多路复用与事件分发合在一起,而是只实现了I/O多路复用事件分发Channel::handleEvent()则是在EventLoop::loop()中实现。一方面是程序安全方面的考虑,另一方面则是为了方便替换为其他更高效的I/O多路复用机制,如epoll(4)。

至此,一个完整的Reactor模式基本框架就完成了。


2、测试:利用timerfd(2)

有了Reactor基本框架后,我们使用timerfd给EventLoop加上一个定时器功能,对这个框架进行测试。

#include <sys/timerfd.h>

int timerfd_create(int clockid, int flags);

int timerfd_settime(int fd, int flags, const struct itimerspec *new_value, struct itimerspec *old_value);

int timerfd_gettime(int fd, struct itimerspec *curr_value);

传统的Reactor通过控制poll(2)或select(2)的等待时间来实现定时,而现在linux有了timerfd,我们可以用和处理I/O事件相同的方式来处理定时。具体原因参考文章Muduo 网络编程示例之三:定时器

#include "EventLoopThread.hpp"
#include "EventLoop.hpp"
#include "Thread.hpp"
#include <sys/timerfd.h>
#include <unistd.h>
#include <string.h>
#include <memory>
#include <iostream>

using namespace std;

EventLoop* loop;

void timeout()
{
  cout<<"tid "<<CurrentThreadtid()<<": Timeout!"<<endl;
  loop->quit();
}

int main()
{
  EventLoopThread ELThread;
  loop = ELThread.startLoop();//thread2
  int timerfd=timerfd_create(CLOCK_MONOTONIC,TFD_NONBLOCK|TFD_CLOEXEC);
  struct itimerspec howlong;
  bzero(&howlong, sizeof howlong);
  howlong.it_value.tv_sec=3;
  timerfd_settime(timerfd,0,&howlong,NULL);
  Channel channel(loop,timerfd);
  channel.setReadCallback(timeout);  
  channel.enableReading();  

  sleep(5);//ensure the main thread do not exit faster than thread2
  close(timerfd);
  return 0;
}
baddy@ubuntu:~/Documents/Reactor/s1.1$ g++ -std=c++11 -pthread -o test1 MutexLockGuard.hpp Condition.hpp Thread.hpp Thread.cpp Channel.hpp Channel.cpp EventLoop.hpp EventLoop.cpp Poller.hpp Poller.cpp EventLoopThread.hpp testEventLoopThread.cpp 
baddy@ubuntu:~/Documents/Reactor/s1.1$ /usr/bin/valgrind ./testTimerDemo 
==25681== Memcheck, a memory error detector
==25681== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==25681== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==25681== Command: ./testTimerDemo
==25681== 
tid 25681: create a new thread
tid 25681: waiting
tid 25682: T
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值