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
某个channel的index变量 EPollPoler类的ChannelMap 是否注册了epollfd_ kNew 未存有 未监听 kDeleted 存有 未监听 kAdded 存有 已监听
  • 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。在上面说的也算详细了。

1 0
原创粉丝点击