muduo源码解析24-网络库2:Reactor关键结构channel,poller和eventloop

简介:

用eventloop,poller和channel共同完成一个最简单的reactor模型。

注意本文超级长(介绍了三个类,channel,poller和eventloop,用他们实现一个最基本的reactor模型

 

 

//这是一个正常的IO复用模型结构,以poll为例子

while
(1) { poll(); //等待网络事件的发生 handleEvent();   //处理各个网络事件 }

 

根据上面的小例子来简单说一下这三个类都是干嘛的:

eventloop之前说过了,是封装这整个循环体while(1){},而poller看名字就猜得出来是对 poll/epoll的封装,channel则是对handleEvent()的封装。

 虽然说得很精简,不过的确是这几个类最核心的功能。

下面来具体看看各个类的实现:

channel类:

说明:

正常IO复用流程如下:(利用poll)

while(1)
{
poll();    //此时等待,有网络事件发生了就返回
handleEvent();   //这一步通过channel来实现
}

  当调用 ::poll() 获取到发生网络事件的套接字时,channel的任务就是负责处理这些网络事件,为每一个套接字创建一个channel用于处理这个套接字上发生的网络事件。

根据muduo书上内容,有以下的原则:

每个channel对象自始至终只属于一个eventloop,因此每个channel对象都只属于某一个IO线程
每个channel对象自始至终只负责一个文件描述符(fd)的IO事件分发,channel不拥有这个fd,析构也不关闭这个fd
channel会把不同的IO事件分发成不同的回调,例如ReadCallback,WriteCallback等
muduo用户一般不直接使用channel,而会使用更上层的封装,例如TcpConnection(以后了解)
channel的生命期由其owner class 负责管理,一般是其他class直接或间接成员
channel的成员函数都只能在IO线程调用,因此更新其数据成员都不必加锁

用户一般只用set*Callback(),和enable*这几个函数用来设置回调函数和注册网络事件

 

了解channel作用是处理网络事件之后,下面是具体的channel实现:

channel.h

#ifndef CHANNEL_H
#define CHANNEL_H

#include"base/noncopyable.h"
#include<functional>

namespace mymuduo {

namespace net {

class eventloop;

class channel:noncopyable
{
public:
  typedef std::function<void()> EventCallback;
    channel(eventloop* loop,int fd);

    void handleEvent();
    //设置回调函数,read,write,error Callback函数,在处理event时被调用
    void setReadCallback(EventCallback cb)
    { m_readCallback = std::move(cb); }
    void setWriteCallback(EventCallback cb)
    { m_writeCallback = std::move(cb); }
    void setErrorCallback(EventCallback cb)
    { m_errorCallback = std::move(cb); }

    int fd()const {return m_fd;}    //channel所负责IO事件的那个fd
    //返回当前channel所注册的网络事件
    int events() const{return m_events;}
    void set_revents(int revt){m_revents=revt;} //设置网络事件
    //判断当前channel是否注册了事件
    bool isNoneEvent() const{return m_events==kNoneEvent;}

    //在m_event上注册读/写事件
    void enableReading(){m_events|=kReadEvent;update();}
    void enableWriting(){m_events|=kWriteEvent;update();}
    //在m_event上取消读/写事件
    void disableReading(){m_events&=~kReadEvent;update();}
    void disableWriting(){m_events&=~kWriteEvent;update();}

    //取消m_event所有事件
    void disableAll(){m_events=kNoneEvent;update();}

    //判断m_event是否注册了读/写事件
    bool isWriting() const{return m_events & kWriteEvent;}
    bool isReading() const{return m_events & kWriteEvent;}

    //for poller
    int index(){return m_index;}
    void set_index(int idx){m_index=idx;}

    //返回当前channel所在的那个eventloop
    eventloop* ownerLoop(){return m_loop;}

private:

//让本channel 所属于的那个eventloop回调channel::update()完成channel的更新
    void update();
    //这三个静态常量分别表示:无网络事件,读网络事件,写网络事件
    static const int kNoneEvent;
    static const int kReadEvent;
    static const int kWriteEvent;

    eventloop* m_loop;  //channel所属的那个eventloop
    const int m_fd;     //每个channel负责处理一个sockfd上的网络事件
    int m_events;       //channel注册(要监听)的网络事件
    int m_revents;      //poll()返回的网络事件,具体发生的事件
    int m_index; //这个channel在poller中m_pollfds中的序号,默认-1表示不在其中

    //当发生了读/写/错误网络事件时,下面三个函数会被调用
    EventCallback m_readCallback;
    EventCallback m_writeCallback;
    EventCallback m_errorCallback;

};

}//namespace net

}//namespace mymuduo

#endif // CHANNEL_H

channel.cpp

#include "channel.h"

#include"base/logging.h"
#include"net/channel.h"
#include"net/eventloop.h"

#include<sstream>

#include<poll.h>


namespace mymuduo {

namespace net {

const int channel::kNoneEvent=0;
const int channel::kReadEvent=POLLIN|POLLPRI;
const int channel::kWriteEvent=POLLOUT;

channel::channel(eventloop* loop,int fd)
    :m_loop(loop),m_fd(fd),m_events(0),m_revents(0),m_index(-1)
{

}

//此时eventloop::loop()中poll函数返回,说明有网络事件发生了,
//针对网络事件类型进行相应的处理
void channel::handleEvent()
{
    //处理 网络事件 POLLNVAL
    if(m_revents & POLLNVAL)
        LOG_WARN<<"channel::handle_event() POLLNVAL";
    //处理 网络事件 错误
    if(m_revents & (POLLERR|POLLNVAL))
        if(m_errorCallback)
            m_errorCallback();
    //处理 网络事件 read
    if(m_revents & (POLLIN |POLLPRI|POLLRDHUP))
        if(m_readCallback)
            m_readCallback();
    //处理 网络事件 write
    if(m_revents & POLLOUT)
        if(m_writeCallback)
            m_writeCallback();
}

//让本channel 所属于的那个eventloop回调channel::update()完成channel的更新
void channel::update()
{
    m_loop->updateChannel(this);
}


}//namespace net

}//namespace mymuduo

 

时序流程:

eventloop::loop()

  {  在loop()循环中

   ::poll(); 获取网络事件,并为每一个发生网络事件的套接字创建一个channel。

  channel::handleEvent()--->

    {  //使用channel来处理具体的网络事件:

       读网络事件:处理读;

    写网络事件:处理写;

    错误事件: 处理错误;

    }

  }

poller类:

说明:

  是对IO复用模型的封装例如poll和epoll,poller是属于eventloop的,因此会在eventloop::loop()中调用

poller::poll(),来获取发生了网络事件的套接字。

  应当注意,poller内部拥有一个pollfd集合,用于保存poll()要监听的那个套接字集合,另外有一个m_channels用于保存每个套接字fd到其所对应的channel之间的映射。可以说,关于套接字集合和channel集合的一些数据都保存在poller中,他们的更新也通过poller来管理。

poller.h:

#ifndef POLLER_H
#define POLLER_H

#include<map>
#include<vector>

#include"base/timestamp.h"
#include"net/eventloop.h"

struct pollfd;  //在.cpp文件包含<poll.h>

namespace mymuduo {

namespace net {

class channel;

class poller:noncopyable
{
public:
    typedef std::vector<channel*> ChannelList;
    //构造函数,指定poller所属的那个eventloop对象
    poller(eventloop* loop):m_ownerloop(loop){}
    ~poller()=default;

    //作为poller函数的核心,eventloop在loop()中调用poll()函数获得当前活动的IO事件
    //然后填充eventloop生成的所有channel到ChannelList中,保存所有的channel信息
    timestamp poll(int timeoutMs,ChannelList* activeChannels);

    //负责更新把channel更新到m_pollfds
    void updateChannel(channel* ch);

    //保证eventloop所在的线程为当前线程
    void assertInLoopThread() const
    {
        m_ownerloop->assertInLoopThread();
    }

private:

    void fillActiveChannels(int numEvents,ChannelList* activeChannels)const;

    typedef std::vector<struct pollfd> PollFdList;
    typedef std::map<int,channel*> ChannelMap;
    ChannelMap m_channels;  //保存从fd到Channel*的映射.
    PollFdList m_pollfds;   //pollfd集合,用户保存所有客户机套接字信息
    eventloop* m_ownerloop; //poller所属于的那个eventloop对象
};

}//namespace net

}//namespace mymuduo

#endif // POLLER_H

poller.cpp:

#include "poller.h"

#include"base/logging.h"
#include"net/channel.h"

#include<poll.h>

namespace mymuduo{

namespace net {

//调用::poll()获取发生了网络事件的套接字,并为每一个网络事件分配一个channel用于处理
//函数返回当前::poll()返回的时间戳
timestamp poller::poll(int timeoutMs, ChannelList *activeChannels)
{
    int numEvents=::poll(&*m_pollfds.begin(),m_pollfds.size(),timeoutMs);
    timestamp now(timestamp::now());

    //此时poll返回,numEvents个套接字上发生了网络事件
    if(numEvents>0)
    {
        LOG_TRACE<<numEvents<<" events happended";
        //在acticeChannels中添加numThreads个channel,每个事件分配一个channel
        fillActiveChannels(numEvents,activeChannels);
    }else if(numEvents==0)
    {
        LOG_TRACE<<"nothing happended";
    }
    else
        LOG_SYSERR<<"poller::poll()";

    return now;
}

//主要功能是负责维护和更新pollfd数组,
//传入一个channel进来,我们得到ch->index()判断它在m_pollfds中的位置,如果不存在
//需要在m_pollfds中新加一个pollfd
//如果存在直接更新即可
void poller::updateChannel(channel *ch)
{
    //保证eventloop所在线程就是其所属的线程
    assertInLoopThread();
    LOG_TRACE<<"fd= "<<ch->fd()<<" events= "<<ch->events();

    //更新m_pollfds数组,代码很长是因为加了很多断言
    //先判断ch这个channel是否已经在m_channels中,如果不在 index()<0
    if(ch->index()<0)
    {
        //ch->index()==-1,说明这个channel对应的套接字不在m_pollfds中,添加
        assert(m_channels.find(ch->fd())==m_channels.end());
        //新建一个pollfd
        struct pollfd pfd;
        pfd.fd=ch->fd();
        pfd.events=static_cast<short>(ch->events());
        pfd.revents=0;
        //加入到m_pollfds中
        m_pollfds.push_back(pfd);
        int idx=static_cast<int>(m_pollfds.size())-1;
        ch->set_index(idx);
        m_channels[pfd.fd]=ch;
    }else
    {
        //channel对应的套接字在m_pollfds中,修改
        assert(m_channels.find(ch->fd())!=m_channels.end());
        assert(m_channels[ch->fd()]==ch);
        int idx=ch->index();
        assert(0<=idx && idx<static_cast<int>(m_pollfds.size()));
        //修改m_pollfds中的这个pollfd
        struct pollfd pfd=m_pollfds[idx];
        assert(pfd.fd==ch->fd() || pfd.fd == -1);
        pfd.events=ch->events();
        pfd.revents=0;
        if(ch->isNoneEvent())
            pfd.fd=-1;
    }
}

//便利,_pollfds,找出当前活动(发生网络事件)的套接字,把它相对应的channel加入到
//activeChannels.
//这个时候poll和epoll的区别就体现出来了
//poll需要轮寻pollfd数组,而epoll直接返回发生了网络事件的epollfd数组,不需要轮寻
void poller::fillActiveChannels(int numEvents, ChannelList *activeChannels) const
{
    LOG_INFO<<"fileactivechannels size: "<<m_pollfds.size();
    //轮寻pollfd数组,找到发生网络事件的套接字,为其分配channel加入到activeChannel
    for(PollFdList::const_iterator pfd=m_pollfds.begin();
        pfd!=m_pollfds.end() && numEvents>0;pfd++)
    {
        //找到这个套接字的channel
        ChannelMap::const_iterator ch=m_channels.find(pfd->fd);
        assert(ch!=m_channels.end());
        channel* chan=ch->second;
        assert(chan->fd()==pfd->fd);
        //当前套接字是否发生了网络事件,是的话就把他对应的channel填充到activesChannel中
        if(pfd->revents>0)
        {
            chan->set_revents(pfd->revents);    //设置channel发生的网络事件
            LOG_INFO<<"add to channels: "<<chan->fd()<<" "<<chan->reventsToString();
            activeChannels->push_back(chan);    //添加到acticeChannels
            numEvents--;    //总共就numEvents个套接字,为0就提前退出
        }

    }
}


}//namespace net

}//namespace mymuduo

 

 

时序流程:

eventloop::loop()

{

  poller::poll()--->

    {

    ::poll();  获取发生了网络事件的套接字

    调用fillActiveChannels(); 把套接字对应的channel保存到eventloop::m_activeChannels

    }

  channel::handlEvent()

}

eventloop类:

  之前说过这个类了,现在需要对这个类添加一些功能,首先eventloop中要有一个poller对象来实现poller::poll()操作,还要有一个channel的集合用于调用channel::handleEvent()处理网络事件。

eventloop.h

#ifndef EVENTLOOP_H
#define EVENTLOOP_H

#include<pthread.h>

#include"base/noncopyable.h"

#include<vector>
#include<memory>

namespace mymuduo
{
namespace net {

class channel;
class poller;

class eventloop:noncopyable
{
public:
    eventloop();
    ~eventloop();
    //eventloop的核心函数,用于不断循环,在其中调用poller::poll()用于获取发生的网络事件
    void loop();
    //停止loop()
    void quit();


    //更新channel,实际上调用了poller::updatechannel,更新poller的m_pollfds数组
    void updateChannel(channel* ch);

    //断言,eventloop所属于的线程ID就是当前线程的ID
    void assertInLoopThread();
    //判断是否eventloop所属于的线程ID就是当先线程的ID
    bool isInLoopThread() const;

    //获得当前线程的那个eventloop*
    eventloop* getEventLoopOfCurrentThread();

private:

    //LOG_FATAL
    void abortNotInLoopThread();
    typedef std::vector<channel*> ChannelList;

    bool m_looping;     //eventloop是否正在loop
    bool m_quit;        //是否退出
    const pid_t m_threadId; //eventloop所在的那个线程ID,要求one eventloop one thread
    std::unique_ptr<poller> m_poller;   //用于在loop()中调用poller::poll()
    //在poller::poll()中返回的activeChannels,也就是每个网络事件对应的channel
    ChannelList m_activeChannels;

};

}//namespace net

}//namespace mymuduo



#endif // EVENTLOOP_H

eventloop.cpp

#include "eventloop.h"

#include"base/currentthread.h"
#include"base/logging.h"
#include"net/poller.h"
#include"net/channel.h"


#include<poll.h>


namespace mymuduo {

namespace net {

//每个线程都有一个t_loopInThisThread,表示当前线程拥有的那个eventloop*
__thread eventloop* t_loopInThisThread=0;
const int kPollTimeMs = 10*1000;    //默认poller->poll()阻塞时间

//构造函数,初始化成员,设置当前线程的t_loopInThisThread为this
eventloop::eventloop()
    :m_looping(false),m_threadId(currentthread::tid()),
      m_poller(new poller(this)),m_quit(false),m_activeChannels()
{
    LOG_TRACE<<"eventloop created "<<this<<" in thread "<<m_threadId;
    //判断当前线程是否已经存在eventloop了,保证最多只有一个
    if(t_loopInThisThread)  //多创建eventloop报错
        LOG_FATAL<<"another eventloop "<<this<<" exits in this thread "<<m_threadId;
    else
        t_loopInThisThread=this;
}

eventloop::~eventloop()
{
    assert(!m_looping);
    t_loopInThisThread=NULL;    //删除当前线程的那个eventlloop*,指向NULL
}

void eventloop::loop()
{
    assert(!m_looping);
    assertInLoopThread();
    m_looping=true;
    m_quit=false;

    while(!m_quit)
    {
        //poller::poll()得到发生的网络事件,为每个网络事件分配一个channel去处理
        //最后把所有的channel都加入到m_activeChannel中
        m_activeChannels.clear();
        m_poller->poll(kPollTimeMs,&m_activeChannels);

        //对每一个channel,让其调用handleEvent()去处理网络事件
        for(ChannelList::const_iterator it=m_activeChannels.begin();
            it!=m_activeChannels.end();it++)
            (*it)->handleEvent();
    }
    LOG_TRACE<<"eventloop "<<this<<" stop looping";
    m_looping=false;
}

//停止eventloop::loop()
void eventloop::quit()
{
    m_quit=true;
}

//更新channel,实际上调用了poller::updatechannel,更新poller的m_pollfds数组
void eventloop::updateChannel(channel *ch)
{
    m_poller->updateChannel(ch);
}

//断言,当前线程就是eventloop所在的线程
void eventloop::assertInLoopThread()
{
    if(!isInLoopThread())
        abortNotInLoopThread();
}
//...
bool eventloop::isInLoopThread() const
{
    return m_threadId==currentthread::tid();
}

//获取当前线程的那个eventloop*
eventloop* eventloop::getEventLoopOfCurrentThread()
{
    return t_loopInThisThread;
}

//LOG_FATAL,错误:eventloop所属线程不是当前线程
void eventloop::abortNotInLoopThread()
{
    LOG_FATAL << "EventLoop::abortNotInLoopThread - EventLoop " << this
              << " was created in threadId_ = " << m_threadId
              << ", current thread id = " <<  currentthread::tid();
}


}//namespace net

}//namespace mymuduo

时序图(融合上面两个最全的版本):

eventloop::loop()

{

eventloop::poller::poll()

  {

  ::poll();获取发生了网络事件的套接字

  poller::fillActiveChannels();把套接字对应的channel保存到eventloop::m_activeChannels  

  }

channel::handleEvent()

  {

  让eventloop::m_activeChannels中的每个channel都进行handleEvent()

  }

 

 

}

测试:

已经完成了这三个类最基本的功能了,我们就来实现一个最精简的Reactor模型。

#include "net/eventloop.h"
#include"net/channel.h"
#include"base/logging.h"
#include"base/thread.h"

#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include<sys/timerfd.h>


using namespace mymuduo;
using namespace mymuduo::net;

eventloop* g_loop;

void timeout()
{
    printf("timeout! poller::poll()返回\n");
    g_loop->quit(); //停止eventloop::loop()
}

int main()
{
    mymuduo::logger::setLogLevel(mymuduo::logger::TRACE);

    eventloop loop;
    g_loop=&loop;

    //设置一个定时器fd,定时器触发式会在timerfd上发送一个数据
    int timerfd=::timerfd_create(CLOCK_MONOTONIC,TFD_NONBLOCK|TFD_CLOEXEC);
    channel ch(g_loop,timerfd); //把timerfd绑定到ch上
    ch.setReadCallback(timeout);  //处理读事件时调用timeout函数
    ch.enableReading(); //ch注册读事件,并用update把ch更新到poller中的m_pollfds

    //设置定时器fd在5s后触发,此时m_pollfds中有timerfd,
    //之后调用poller::poll(),这个事件会被poll()捕捉到返回,
    //然后调用channel::handleEvent()处理该事件
    //由于是读事件,Channel会调用readCallback也就是timeout来处理
    struct itimerspec howlong;
    bzero(&howlong,sizeof howlong);
    howlong.it_value.tv_sec=5;
    ::timerfd_settime(timerfd,0,&howlong,NULL);

    loop.loop();

    ::close(timerfd);


}

打印结果:

2020年09月01日 星期2 17:50:42.1598953842 105123 TRACE eventloop eventloop created 0x7FFF5DE109C0 in thread 105123 - eventloop.cpp:25
2020年09月01日 星期2 17:50:42.1598953842 105123 TRACE updateChannel fd= 3 events= 3 - poller.cpp:44
2020年09月01日 星期2 17:50:47.1598953847 105123 TRACE poll 1 events happended - poller.cpp:22
timeout! poller::poll()返回
2020年09月01日 星期2 17:50:47.1598953847 105123 TRACE loop eventloop 0x7FFF5DE109C0 stop looping - eventloop.cpp:58

 

posted @ 2020-09-01 19:09  WoodInEast  阅读(342)  评论(0编辑  收藏  举报