muduo库的EpollPoller剖析


EpollPoller,是muduo库对I/O复用机制epoll的封装,不过默认使用的是EpollPoller。在EventLoop中初始化构造poller_,调用newDefaultPoller(this),构造默认的poller。

EventLoop::EventLoop()  
  : looping_(false),  //表示还未循环  
    quit_(false),  
    eventHandling_(false),  
    callingPendingFunctors_(false),  
    iteration_(0),  
    threadId_(CurrentThread::tid()),   //赋值真实id  
    poller_(Poller::newDefaultPoller(this)),   //构造了一个实际的poller对象  
    timerQueue_(new TimerQueue(this)),  
    wakeupFd_(createEventfd()),  
    wakeupChannel_(new Channel(this, wakeupFd_)),  
    currentActiveChannel_(NULL)  

newDefaultPoller内部是这样的:

Poller* Poller::newDefaultPoller(EventLoop* loop)  
{  
  if (::getenv("MUDUO_USE_POLL"))   //看环境变量,如果没有MUDUO_UER_POLL,默认epoll  
  {  
    return new PollPoller(loop);  
  }  
  else  
  {  
    return new EPollPoller(loop);  
  }  
}  
也就是说,如果我们没有主动设置环境变量为MUDUO_USE_POLL,我们默认选择为构造EpollPoller。

EpollPoller重写了父类的三个函数:

virtual Timestamp poll(int timeoutMs, ChannelList* activeChannels);  
virtual void updateChannel(Channel* channel);  
virtual void removeChannel(Channel* channel);  

EpollPoller的私有成员有:

private:  
  static const int kInitEventListSize = 16;   //默认事件数组大小,是用来装epoll_wait()返回的可读或可写事件的  
  static const char* operationToString(int op);  
  void fillActiveChannels(int numEvents,  
                          ChannelList* activeChannels) const;  
  void update(int operation, Channel* channel);  
  
  typedef std::vector<struct epoll_event> EventList;  
  int epollfd_;       //epoll句柄,以后要监听什么事件注册到这里  
  EventList events_;  //监听到的活动的事件,作为epoll_wait参数

其中kInitEventListSize是epoll模式中用来盛装返回的发生事件的数组大小,就是events_,它在构造函数中会被初始化,看下面:

EPollPoller::EPollPoller(EventLoop* loop)  
  : Poller(loop),  
    epollfd_(::epoll_create1(EPOLL_CLOEXEC)),   //创建epollfd,使用带1的版本  
    events_(kInitEventListSize)    //vector这样用时初始化kInitEventListSize个大小空间  
{  
  if (epollfd_ < 0)   //在构造函数中判断,<0就abort()  
  {  
    LOG_SYSFATAL << "EPollPoller::EPollPoller";  
  }  
}  

有一点需要注意,就是vector的初始化方式,这样可以预分配大小。至于我们以后不断向epoll中注册事件,然后导致返回的发生事件可能越来越多,events_装不下了,muduo也有自己的处理,是在EpollPoller的核心函数poll()函数中处理的:

if (implicit_cast<size_t>(numEvents) == events_.size())  //如果返回的事件数目等于当前事件数组大小,就分配2倍空间  
{                                                               
    events_.resize(events_.size()*2);     
}  
所以不必担心vector的大小问题了,后续会以乘以2倍的方式分配,这也是内存分配的常见做法。

我们再来回忆一下事件处理流程,先看一下事件注册图:


当某个客端,比如TCPConnection,或者我们自己写的代码调用Channel的enableReading()函数,就会引发Channel的Update()函数的调用,然后会调用EventLoop的UpdateChannel()函数,实际上会调用到Poller的updateChannel()函数。比如采用EpollPoller,那就会将事件注册到epoll的内核事件表中去,然后开启epoll_wait(),就是我们正常的epoll流程了。

//注册或修改channel。
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中,index是在poll中是下标,在epoll中是三种状态,上面有三个常量 
  {
    // 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);        //确保目前它存在  
   //poll是把文件描述符变为相反数减1,这里是使用EPOLL_CTL_DEL从内核事件表中删除  
    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";
  }
}
namespace
{
/*
这3个const变量是来设置Channel类的index变量。
用来指示Channel事件在EPollPoller的注册情况。
*/
const int kNew = -1;    
const int kAdded = 1;
const int kDeleted = 2;
}

kNew kDeleted kAdder

某个channel的index变量标明EPollPoler类的ChannelMap是否注册了epollfd_

kNew 未存有 未监听
kDeleted 存有         未监听
kAdded 存有         已监听

然后看一下事件发生后处理的时序,先看图:


如图,EventLoop调用poll()是上面事件注册的过程。目前我们处于Poller位置,当Poller中事件发生后,会调用fillActiveChannels()函数,把发生的事件填充给活跃事件通道表中,具体实现是下面的Poller的成员函数fillActiveChannels函数:

/***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;
}

void EPollPoller::fillActiveChannels(int numEvents,  
                                     ChannelList* activeChannels) const  
{  
  assert(implicit_cast<size_t>(numEvents) <= events_.size());//确定它的大小小于events_的大小,因为events_是预留的事件vector                                                                                      
    
  for (int i = 0; i < numEvents; ++i)   //挨个处理发生的numEvents个事件,epoll模式返回的events_数组中都是已经发生额事件,这有别于select和poll  
  {  
    Channel* channel = static_cast<Channel*>(events_[i].data.ptr);  
/* 
这是epoll模式epoll_event事件的数据结构,其中data不仅可以保存fd,也可以保存一个void*类型的指针。 
typedef union epoll_data { 
               void    *ptr;   //指向channel类变量
               int      fd; 
               uint32_t u32; 
               uint64_t u64; 
           } epoll_data_t; 
 
           struct epoll_event { 
               uint32_t     events;    // Epoll events,//对应channel类中的revent变量
               epoll_data_t data;      //User data variable  
           }; 
*/  
    channel->set_revents(events_[i].events);   //把已发生的事件传给channel,写到通道当中  
    activeChannels->push_back(channel);    //并且push_back进activeChannels  
  }  
}  


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

X-Programer

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值