web服务器开发之Reactor模式以及实现(c++)

     一.概述

Reactor是一个使用了同步非阻塞的I/O多路复用机制的模式,I/O多路复用是指使用一个线程来检查多个文件描述符(Socket)的就绪状态,比如调用select,poll,epoll函数(现在多使用epoll函数,关于epoll可见上篇博文),传入多个文件描述符,如果有文件描述符就绪,就返回,否则阻塞直到超时。得到就绪状态后启动线程执行

     I/O 复用机制需要事件分发器。 事件分发器的作用,将那些读写事件源分发给各读写事件的处理者。Reactor正是这个事件分发器。其是事件驱动的,有一个或多个并发输入源,处理如下图

当acceptor接收到请求后会把它交给reactor,在分发给各个文件描述符的事件处理函数。

所以该模式主要包含两个组件——Reactor和Handler。Reactor:负责响应IO事件,当检测到一个新的事件,将其发送给相应的Handler去处理;新的事件包含连接建立就绪、读就绪、写就绪等。Handler:将自身(handler)与事件绑定,负责事件的处理,完成channel的读入,负责将结果写出channel。

二.来看具体的实现

在编写中将其分为三个函数类

(1)第一个是channel,其负责一个文件描述符的分发,即在其中存储IO事件类型及注册回调函数,带有一个存储fd的变量,可知这个fd变量可被赋予不同的值。Channel可以给不同的IO事件分发不同的回调,其中包含一个主要的函数能根据上层传过来的值进行函数调用。

说到底Channel就是把fd 和fd感兴趣的IO事件,和fd上就绪的IO事件,以及对相应的IO事件进行处理的函数封装到一起,部分代码如下

class Channel
{
private:

    int fd_; // Channel管理的fd
    __uint32_t events_; // 保存了fd上感兴趣的IO事件
    __uint32_t revents_; // 目前fd上就绪的事件

private:
    CallBack readHandler_;  //事件处理回调函数
    CallBack writeHandler_;
    CallBack errorHandler_;
    CallBack connHandler_;

public:
    Channel(EventLoop *loop);
    Channel(EventLoop *loop, int fd);
    ~Channel();
  
    
    void setHolder(std::shared_ptr<HttpData> holder)    //存储相应的事件
    {
        holder_ = holder;
    }
    std::shared_ptr<HttpData> getHolder()
    {
        std::shared_ptr<HttpData> ret(holder_.lock());
        return ret;
    }

    void setReadHandler(CallBack &&readHandler)
    {
        readHandler_ = readHandler;
    }
    void setWriteHandler(CallBack &&writeHandler)
    {
        writeHandler_ = writeHandler;
    }
    void setErrorHandler(CallBack &&errorHandler)
    {
        errorHandler_ = errorHandler;
    }
    void setConnHandler(CallBack &&connHandler)
    {
        connHandler_ = connHandler;
    }

    // handleEvent()是Channel的核心,它根据revents_的值分别调用不同的用户回调
    void handleEvents()
    {
        events_ = 0;
        if ((revents_ & EPOLLHUP) && !(revents_ & EPOLLIN)) // 挂起
        {
            events_ = 0;
            return;
        }
        if (revents_ & EPOLLERR) // 错误
        {
            if (errorHandler_) errorHandler_(); //如果对应的文件描述符发生错误,调用错误处理函数
            events_ = 0;
            return;
        }
        if (revents_ & (EPOLLIN | EPOLLPRI | EPOLLRDHUP)) // 可读
        {
            handleRead();
        }
        if (revents_ & EPOLLOUT) // 可写
        {
            handleWrite();
        }
        handleConn(); // 处理连接
    }
    void handleRead();
    void handleWrite();
    void handleError(int fd, int err_num, std::string short_msg);
    void handleConn();

    void setRevents(__uint32_t ev) // 设置fd上的就绪事件
    {
        revents_ = ev;
    }

    void setEvents(__uint32_t ev) // 设置fd上的感兴趣事件
    {
        events_ = ev;
    }
    __uint32_t& getEvents() // 返回感兴趣事件
    {
        return events_;
    }

    bool EqualAndUpdateLastEvents() // 更新上次注册的事件
    {
        bool ret = (lastEvents_ == events_);
        lastEvents_ = events_;
        return ret;
    }

    __uint32_t getLastEvents()
    {
        return lastEvents_;
    }

};

(2)epoll类,该类负责将一个文件描述符与一个channel类联系起来,其中也封装了epoll相应的文件描述符添加,修改,删除函数,它可将channel感兴趣的事件注册到内核表中,部分代码如下

class Epoll
{
public:
    Epoll();
    ~Epoll();
    void epoll_add(SP_Channel request, int timeout);    //sp_channel 是channel的智能指针,相应epoll函数的封装
    void epoll_mod(SP_Channel request, int timeout);
    void epoll_del(SP_Channel request);
    std::vector<Channel> poll(); // 核心功能,能够返回就绪的channel数组

private:
    
    std::vector<epoll_event> events_; // epoll_event结构体数组,从内核事件表将所有就绪的事件复制到此
   
};

(3)eventloop类,该类由一个IO线程创建,并管理一个epoll对象,并管理归属于本EventLoop对象的Channel对象,其主要功能是运行loop函数,其函数是调用epoll::poll函数获得当前活动事件的channel列表,然后依次调用每个channel的事件处理函数。部分代码如下

class EventLoop
{
public:
    
    EventLoop();
    ~EventLoop();
    void loop(); // 循环函数
    void quit(); // 退出函数
    
    void assertInLoopThread() // 检查当前线程是否已经创建了其他EventLoop对象,一个线程稚嫩对应一个EventLoop对象
    {
        assert(isInLoopThread());
    }
    //管理其中的channel对象
    // 从IO复用类对象中poller_中移除Channel对象
    void removeFromPoller(shared_ptr<Channel> channel)
    {
        //shutDownWR(channel->getFd());
        poller_->epoll_del(channel);
    }

    // 更新Channel对象
    void updatePoller(shared_ptr<Channel> channel, int timeout = 0)
    {
        poller_->epoll_mod(channel, timeout);
    }

    // 添加Channel对象
    void addToPoller(shared_ptr<Channel> channel, int timeout = 0)
    {
        poller_->epoll_add(channel, timeout);
    }

private:
   
    shared_ptr<Epoll> poller_; // 每个事件循环中都有一个IO复用
 
    void wakeup();
    void handleRead();
    void doPendingFunctors();
    void handleConn();
};

以上三部分就组成了Reactor的核心部分,概述流程

  1. 初始化channel,然后将若干个事件handler注册到其中,同时指定感兴趣的事件,channel里应还有一个文件描述符,其可以根据请求调用相应不同的用户回调。
  2. 当所有具体事件处理器都注册完成后,会调用后eventloop::lopp()来启动循环,其会将每个具体事件处理器与一个文件描述符联系起来,然后同步阻塞等待事件的发生,epoll_wait()
  3. 当某个文件描述被激活后就会通知channel,channel会回调事件处理器来执行特定的应用功能

为了更高的提升提高Reactor线程的I/O响应,加入线程池,通过重用现有的线程而不是创建新线程,可以在处理多个请求时分摊在线程创建和销毁过程产生的巨大开销。当请求到达时,工作线程通常已经存在,因此不会由于等待创建线程而延迟任务的执行,从而提高了响应性。

这下再来看下面一张图,这就是加入线程池后的reactor多线程模型

后续整理下线程池相关的知识再来完善。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值