Muduo:EventLoop“循环”什么?
来源:互联网 发布:windows10下载不了软件 编辑:程序博客网 时间:2024/05/22 15:31
涉及的类:EPollPoller EventLoop Channel TimeQueue
其中错误,欢迎指出!
- 关系
EventLoop类会指派其成员变量poller_监听3种类型的事件,一种是由Channel类描述的读写事件,一种是TimeQueue类描述的定时事件(然而也是通过Channel来注册到poller上),还有一种则是自身线程唤醒事件。
你问EPollPoller类是干啥的?poller_变量则是由EPollPoller类描述的。
这个EventLoop可以说是一个指挥官,具体的细节他不知道,它只知道你这个类会干这事,所以代码中你会见到EventLoop很喜欢这么干,你这个成员变量,去你的类中用你实现的那个方法。你说让poller_监听事件吧,好的。让poller_自个去调用它类中实现的poll方法。这里面有很多代码,下面会提到。
监听到了会干嘛呢?没错,EventLoop也不知道监听了要干嘛,因为这事情他不爱记,他只记录了监听到的事件activeChannels_,而这些事件是由Channel类描述的,那里会记载着该干嘛。EventLoop就会调用其中设置的回调函数。
//EventLoop.hboost::scoped_ptr<Poller> poller_;ChannelList activeChannels_; //监听到的事件
首先先了解EPollPoller与Channel,这两个类是有联系的,看代码的时候建议一起看。
- EPollPoller与Channel
首先EPollPoller类是继承了Poller类的,由于Poller类实在太短,所以我就拿了EPollPoller类说。
(epoll:http://blog.csdn.net/ljx0305/article/details/4065058)
成员变量如下:
int epollfd_; //epoll句柄,以后要监听什么事件注册到这里 typedef std::vector<struct epoll_event> EventList; EventList events_; //监听到的活动的事件,作为epoll_wait参数 typedef std::map<int, Channel*> ChannelMap; ChannelMap channels_; //描述符与Channel(事件)的映射,这里的事件,不一定是正在监听的事件(因为有可能是之前监听但后来删掉不过仍留在ChannelMap中的映射) EventLoop* ownerLoop_;//指向EventLoop的指针。
还是直接在代码中注释好了。上面是介绍了EPollPoller类的成员变量,以下是成员方法的注释。
#include <muduo/net/poller/EPollPoller.h>#include <muduo/base/Logging.h>#include <muduo/net/Channel.h>#include <boost/static_assert.hpp>#include <assert.h>#include <errno.h>#include <poll.h>#include <sys/epoll.h>using namespace muduo;using namespace muduo::net;// On Linux, the constants of poll(2) and epoll(4)// are expected to be the same.BOOST_STATIC_ASSERT(EPOLLIN == POLLIN);BOOST_STATIC_ASSERT(EPOLLPRI == POLLPRI);BOOST_STATIC_ASSERT(EPOLLOUT == POLLOUT);BOOST_STATIC_ASSERT(EPOLLRDHUP == POLLRDHUP);BOOST_STATIC_ASSERT(EPOLLERR == POLLERR);BOOST_STATIC_ASSERT(EPOLLHUP == POLLHUP);namespace{/*这3个const变量是来设置Channel类的index变量。用来指示Channel事件在EPollPoller的注册情况。具体分析见该段代码下的列表中。。*/const int kNew = -1; const int kAdded = 1;const int kDeleted = 2;}//构造函数:调用epoll_create1创建了epoll句柄。初始化了其他成员变量。EPollPoller::EPollPoller(EventLoop* loop) : Poller(loop), epollfd_(::epoll_create1(EPOLL_CLOEXEC)), events_(kInitEventListSize){ if (epollfd_ < 0) { LOG_SYSFATAL << "EPollPoller::EPollPoller"; }}//析构函数:关闭了epollfdEPollPoller::~EPollPoller(){ ::close(epollfd_);}/***poll函数:poll监听在epollfd上注册的描述符,其中activeChannels变量是EventLoop中传递过来的(EventLoop类只记载了活跃的事件,注册了什么事件交给了其成员变量poller),通过fillActiveChannels函数来完成写入到activeChannels。***/Timestamp EPollPoller::poll(int timeoutMs, ChannelList* activeChannels){ LOG_TRACE << "fd total count " << channels_.size();//调用epoll_wait,活跃的事件则放在events中。 int numEvents = ::epoll_wait(epollfd_, &*events_.begin(), static_cast<int>(events_.size()), timeoutMs); //保存errno int savedErrno = errno;//这里层层调用下去会发现其实就是调用了gettimeofday记录了下时间。 Timestamp now(Timestamp::now());//对epoll_wait返回值判断。 if (numEvents > 0)//有活跃事件 { LOG_TRACE << numEvents << " events happended"; /* 将记录了活跃事件成员events塞到activeChannels中,因为poll函数主要 由EventLoop类调用,传递参数,activeChannels是EventLoop类的成员变量。 */ fillActiveChannels(numEvents, activeChannels); if (implicit_cast<size_t>(numEvents) == events_.size()) { events_.resize(events_.size()*2); } } else if (numEvents == 0) //无活跃事件 { LOG_TRACE << "nothing happended"; } else //出错 { // error happens, log uncommon ones if (savedErrno != EINTR) { errno = savedErrno; LOG_SYSERR << "EPollPoller::poll()"; } } return now;}/**fillActiveChannels创建一个临时channel,用events来设置其中的变量,尤其是是fd,revents,具体与events中变量的对应见下。应该是类Channel与结构体struct epoll_event events的对应。创建好再将其push_back到activeChannels中。**/void EPollPoller::fillActiveChannels(int numEvents, ChannelList* activeChannels) const{ assert(implicit_cast<size_t>(numEvents) <= events_.size()); for (int i = 0; i < numEvents; ++i) { Channel* channel = static_cast<Channel*>(events_[i].data.ptr);#ifndef NDEBUG int fd = channel->fd(); ChannelMap::const_iterator it = channels_.find(fd); assert(it != channels_.end()); assert(it->second == channel);#endif channel->set_revents(events_[i].events); activeChannels->push_back(channel); }}//注册或修改channel。对照kNew kDeleted kAdded来食用更佳void EPollPoller::updateChannel(Channel* channel){ Poller::assertInLoopThread(); const int index = channel->index(); LOG_TRACE << "fd = " << channel->fd() << " events = " << channel->events() << " index = " << index; if (index == kNew || index == kDeleted) //说明该channel未注册到epollfd中 { // a new one, add with EPOLL_CTL_ADD int fd = channel->fd(); if (index == kNew) { assert(channels_.find(fd) == channels_.end()); channels_[fd] = channel; //kNew表示std::map<int,channel*> channels_也没有,添加进去。 } else // index == kDeleted { assert(channels_.find(fd) != channels_.end()); assert(channels_[fd] == channel); } channel->set_index(kAdded); update(EPOLL_CTL_ADD, channel); //注册到epollfd } else //已注册 kAdded。 { // update existing one with EPOLL_CTL_MOD/DEL int fd = channel->fd(); (void)fd; assert(channels_.find(fd) != channels_.end()); assert(channels_[fd] == channel); assert(index == kAdded); if (channel->isNoneEvent()) //不监听事件了 { update(EPOLL_CTL_DEL, channel);//从epollfd中删除 channel->set_index(kDeleted); } else //已注册的channel,也许是更改了一些监听事件类型 { update(EPOLL_CTL_MOD, channel);//修改该事件。 } }}//从epollfd和std::map<int,channel*>中都删除channel,代码很简单。喜欢用assert来检查void EPollPoller::removeChannel(Channel* channel){ Poller::assertInLoopThread(); int fd = channel->fd(); LOG_TRACE << "fd = " << fd; assert(channels_.find(fd) != channels_.end()); assert(channels_[fd] == channel); assert(channel->isNoneEvent()); int index = channel->index(); assert(index == kAdded || index == kDeleted); size_t n = channels_.erase(fd); (void)n; assert(n == 1); if (index == kAdded) { update(EPOLL_CTL_DEL, channel); } channel->set_index(kNew);}//封装epoll_ctl。void EPollPoller::update(int operation, Channel* channel){ struct epoll_event event; bzero(&event, sizeof event); event.events = channel->events();//这两行代码也能反应channel跟struct epoll_event的映射。 event.data.ptr = channel; int fd = channel->fd(); LOG_TRACE << "epoll_ctl op = " << operationToString(operation) << " fd = " << fd << " event = { " << channel->eventsToString() << " }"; if (::epoll_ctl(epollfd_, operation, fd, &event) < 0) { if (operation == EPOLL_CTL_DEL) { LOG_SYSERR << "epoll_ctl op =" << operationToString(operation) << " fd =" << fd; } else { LOG_SYSFATAL << "epoll_ctl op =" << operationToString(operation) << " fd =" << fd; } }}//这个是转出string,没什么好说的。const char* EPollPoller::operationToString(int op){ switch (op) { case EPOLL_CTL_ADD: return "ADD"; case EPOLL_CTL_DEL: return "DEL"; case EPOLL_CTL_MOD: return "MOD"; default: assert(false && "ERROR op"); return "Unknown Operation"; }}
- kNew kDeleted kAdder
- struct epoll_event与Channel的对应。
typedef union epoll_data { void *ptr; //指向channel类变量 int fd; __uint32_t u32; __uint64_t u64;} epoll_data_t;struct epoll_event { __uint32_t events; //对应channel类中的revent变量 epoll_data_t data; //data.ptr};
对应的代码:
//在fillActiveChannels函数中的代码体现 Channel* channel = static_cast<Channel*>(events_[i].data.ptr); channel->set_revents(events_[i].events);//在update函数中的代码体现 event.events = channel->events(); event.data.ptr = channel;
EPollPoller类和Channel类的交互。是通过Channel类中的index和revent进行交互的。index用来告知EPollPoller注册情况。revent告知Channel啥类型的事件活跃。
Channel类的成员变量
EventLoop* loop_; const int fd_; //描述符 int events_; //监听的事件类型 int revents_; // it's the received event types of epoll or poll int index_; // used by Poller. bool logHup_; boost::weak_ptr<void> tie_; //锁? bool tied_; bool eventHandling_; //是否正在执行回调函数? bool addedToLoop_; //是否加入到时间循环? typedef boost::function<void()> EventCallback; typedef boost::function<void(Timestamp)> ReadEventCallback; ReadEventCallback readCallback_;//读回调 EventCallback writeCallback_; //写回调 EventCallback closeCallback_;//关闭回调 EventCallback errorCallback_;//出错回调
然后是介绍成员方法:
//先介绍代码少的内联函数。在channel.h头文件中。 int fd() const { return fd_; } int events() const { return events_; } void set_revents(int revt) { revents_ = revt; } // used by pollers // int revents() const { return revents_; } bool isNoneEvent() const { return events_ == kNoneEvent; }//关于kReadEvent|kWriteEvent|kNoneEvent,见channel.cc文件。下面也会列出。//update函数其实层层深入会发现其实就是调用了EPollPoller类的updateChannel方法。注册或修改。 void enableReading() { events_ |= kReadEvent; update(); } void disableReading() { events_ &= ~kReadEvent; update(); } void enableWriting() { events_ |= kWriteEvent; update(); } void disableWriting() { events_ &= ~kWriteEvent; update(); } void disableAll() { events_ = kNoneEvent; update(); } bool isWriting() const { return events_ & kWriteEvent; } bool isReading() const { return events_ & kReadEvent; } // for Poller int index() { return index_; } void set_index(int idx) { index_ = idx; }
//下面这个是channel.cc文件上的。
#include <muduo/base/Logging.h>#include <muduo/net/Channel.h>#include <muduo/net/EventLoop.h>#include <sstream>#include <poll.h>using namespace muduo;using namespace muduo::net;const int Channel::kNoneEvent = 0;const int Channel::kReadEvent = POLLIN | POLLPRI;const int Channel::kWriteEvent = POLLOUT;//构造函数:初始化变量,index赋值为-1,即kNew,表示还未加入epollfd和std::map<int,channel*> channels_。Channel::Channel(EventLoop* loop, int fd__) : loop_(loop), fd_(fd__), events_(0), revents_(0), index_(-1), logHup_(true), tied_(false), eventHandling_(false), addedToLoop_(false){}Channel::~Channel(){ assert(!eventHandling_); assert(!addedToLoop_); if (loop_->isInLoopThread()) { assert(!loop_->hasChannel(this)); }}void Channel::tie(const boost::shared_ptr<void>& obj){ tie_ = obj; //对锁的赋值 tied_ = true; //有锁标志}//这里update调用了EventLoop中的updateChannel,其实追根究底还是在调用EPollPoller中updateChannel方法。void Channel::update(){ addedToLoop_ = true; loop_->updateChannel(this);}//与update同理,层层调用去看还是在调用EPollPoller中removeChannel方法void Channel::remove(){ assert(isNoneEvent()); addedToLoop_ = false; loop_->removeChannel(this);}/*handleEvent:作用:供EventLoop类调用,因为EventLoop类只会发现你这个channel有事件 ,至于是什么事件需要channel类自己查看revent变量(EPollPoller传递的), 然后根据其值来调用相关的事件回调函数。(这也是handleEventWithGuard的逻辑)逻辑:判断这个channel是否有锁,一般该事件涉及多事件调用才需要用到锁。若有锁,则获得锁后调用handleEventWithGuard若无锁,直接调用handleEventWithGuard。*/void Channel::handleEvent(Timestamp receiveTime){ boost::shared_ptr<void> guard; if (tied_) //判断是否有锁。有锁。 { guard = tie_.lock();//锁上! if (guard) //是否锁上了。 { handleEventWithGuard(receiveTime); } } else { handleEventWithGuard(receiveTime); }}/*handleEventWithGuard:根据revent值来调用相关的事件回调函数。man 2 poll 可见每个宏定义POLLXXX的含义。*/void Channel::handleEventWithGuard(Timestamp receiveTime){ eventHandling_ = true; LOG_TRACE << reventsToString(); if ((revents_ & POLLHUP) && !(revents_ & POLLIN)) { if (logHup_) { LOG_WARN << "fd = " << fd_ << " Channel::handle_event() POLLHUP"; } if (closeCallback_) closeCallback_(); } if (revents_ & POLLNVAL) //fd未打开。。。见man 2 poll { LOG_WARN << "fd = " << fd_ << " Channel::handle_event() POLLNVAL"; } if (revents_ & (POLLERR | POLLNVAL)) //出错 { if (errorCallback_) errorCallback_(); } if (revents_ & (POLLIN | POLLPRI | POLLRDHUP)) //收到 { if (readCallback_) readCallback_(receiveTime); } if (revents_ & POLLOUT) //写 { if (writeCallback_) writeCallback_(); } eventHandling_ = false;}//event事件类型转字符串string Channel::reventsToString() const{ return eventsToString(fd_, revents_);}string Channel::eventsToString() const{ return eventsToString(fd_, events_);}//string Channel::eventsToString(int fd, int ev){ std::ostringstream oss; oss << fd << ": "; if (ev & POLLIN) oss << "IN "; if (ev & POLLPRI) oss << "PRI "; if (ev & POLLOUT) oss << "OUT "; if (ev & POLLHUP) oss << "HUP "; if (ev & POLLRDHUP) oss << "RDHUP "; if (ev & POLLERR) oss << "ERR "; if (ev & POLLNVAL) oss << "NVAL "; return oss.str().c_str();}
Channel跟EPollPoller逻辑一般是这样:
创建个channel,比如设置读事件,先设置回调函数,然后enableReading(),这里就非常关键了,它会先设置events变量的事件类型,然后update,这个update会让该事件所在loop来调用loop上的EPollPoller的updateChannel。具体代码如下:
//Channel.ccvoid Channel::update(){ addedToLoop_ = true; loop_->updateChannel(this); }
//EventLoop.ccvoid EventLoop::updateChannel(Channel* channel){ assert(channel->ownerLoop() == this); assertInLoopThread(); poller_->updateChannel(channel);}
以上是设置事件并注册到epollfd中的过程。删除事件,以及channel.h中的disableXXX同理。
而监听到事件的代码也是非常清晰的。首先在EventLoop的loop函数中监听到。然后装进activeChannels_,再由channel类调用本身的handleEvent。
//EventLoop.ccloop(……){……pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_);……for (ChannelList::iterator it = activeChannels_.begin(); it != activeChannels_.end(); ++it) { currentActiveChannel_ = *it; currentActiveChannel_->handleEvent(pollReturnTime_); }……}
Channel一般为读写事件。
1、一般有这个场景,当前有个EventLoop loopA在时间循环中。
2、我们这时候有个读事件想在loopA中监听,好,先创建一个channel,弄好描述符,还有loop_也指向loopA,设置好感兴趣的事件类型,还有回调函数。其中index为kNew,用来等会告诉EPollPoller说,这个channel注册情况是一片空白,既不在epollfd上监听,也没再那个map上。然后enableXXX,里面就会update一下,调用了loopA中的updatechannel方法。
3、loopA一看,什么?要我帮你update,这里可能是注册,也可能是del啥的,具体干嘛他不知道,但他知道他的手下—-成员变量poller_是干这类事情的,他会吩咐poller说,这事情交给你去办。而channel交给loopA时候把“登记材料”(index、events)也准备好了,所以在epollfd上登记这个读事件就会在EPollPoller上实际进行。
4、登记完后,过了会,读事件来了,这时候最先知道这件事情的是epollfd,于是他把这事情告诉了loopA手下activeChannels,让他转告loopA,让他拿主意,loopA听到后,就吩咐这事件所属的channel说,你们的读事件来了,快去解决一下。
5、然后channel就检测revent,是epollfd上检测出的事件的类型,然后再调用相应的读或写或出错回调函数。
——————————————————————————————————————————————————————
写到这里,有点倦了
就把整个大致说一遍,以后要是忘了还能找回其中的逻辑。真要写完写清楚对每条代码都解释清楚,要写好久。
1、定时事件muduo也是使用了描述符,用TimerQueue类来描述,在构造这个定时事件的时候封装成channel,让poll来监听其读事件。(对TimerQueue还不算太清楚)
2、EventLoop自身有个唤醒自身线程的描述符,在创建的时候就注册到了epollfd上。
void EventLoop::runInLoop(const Functor& cb){ if (isInLoopThread()) { cb(); } else { queueInLoop(cb); }}void EventLoop::queueInLoop(const Functor& cb){ { MutexLockGuard lock(mutex_); pendingFunctors_.push_back(cb); } if (!isInLoopThread() || callingPendingFunctors_) { wakeup();//唤醒。往唤醒描述符上写…… }}
runInLoop函数会催着loop所在线程去执行cb函数。
3、还有一类事件就是channel。在上面说的也算详细了。
- Muduo:EventLoop“循环”什么?
- muduo网络库学习(四)事件驱动循环EventLoop
- muduo::EventLoop分析
- muduo网络库学习之EventLoop(一):事件循环类图简介和muduo 定时器TimeQueue
- muduo库阅读(29)——Net部分:Reactor(EventLoop事件循环)
- Muduo网络库源码分析(一) EventLoop事件循环(Poller和Channel)
- Muduo网络库源码分析(一) EventLoop事件循环(Poller和Channel)
- muduo源码分析---EventLoop类
- muduo : Reactor(EventLoop Poller Channel)
- muduo源码分析之EventLoop
- muduo库阅读(35)——Net部分:用于执行事件循环(EventLoop)的线程类EventLoopThread
- muduo源码分析--EventLoop 类的实现
- muduo源码分析之EventLoop::runInLoop()函数
- muduo库的EventLoop类剖析
- muduo源码学习(16)-EventLoop简介
- 从epoll构建muduo-6 加入EventLoop和Epoll
- muduo网络库学习之EventLoop(七):TcpClient、Connector
- 从epoll构建muduo-6 加入EventLoop和Epoll
- 贩妖记 > 第四十九章,了断
- part2: maven 工程整合spring+activemq
- eclipse tomcat7 maven3 自动部署到tomcat
- 处理PXC初始化和启动报错三例
- Android的Fragment中onActivityResult不被调用的最终解决方案
- Muduo:EventLoop“循环”什么?
- vitamio官方demo源码分析1——MediaPlayerDemo_Video.java分析
- 过来人系列——五年软件开发的罪与罚
- 学习日记——Linux 2016.05.30
- 易语言 火眼 哈勃 防分析源码
- Android GridView 的使用
- 仿微信表情图片
- 1Roman to Integer 2 Integer to Roman 3 Integer to English Words转化问题
- 第5.6节 按字母顺序对文本行组成的集合进行排序,引入指针处理问题,消除了因移动文本行本身所带来的复杂的存储管理和巨大的开销这两个孪生问题