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