muduo-net 源码分析(二)——Poller 具体实现

先序

前一节介绍 Poller 类的时候就发现这个类实际上是接口类,具体的函数实现还是要看 muduo/poller 文件中的 EPollPollerPollPoller;这里我们主要详细介绍 PollPoller,也会讲解一下 EPollPoller

这里先给出 Poller 中的成员变量,因为成员变量会被继承下来,并在派生类使用到

EventLoop* ownerLoop_;
typedef std::map<int, Channel*> ChannelMap;   //int 是文件描述符 
ChannelMap channels_;     

PollPoller 源码分析

主要成员变量

typedef std::vector<struct pollfd> PollFdList;    
PollFdList pollfds_;   				//文件描述符数组,用来保存监听的 fd

//以下成员变量是继承父类的
EventLoop* ownerLoop_;
typedef std::map<int, Channel*> ChannelMap;   //int 是文件描述符 
ChannelMap channels_;  

/* pollfd 的结构
struct pollfd {
    int fd;				//文件描述符
    short events;		//注册事件
    short revents;		//实际发生的事件
}
*/

主要成员函数

//构造函数和析构函数
PollPoller(EventLoop* loop);
~PollPoller() override;

//以下四个函数是重点
Timestamp poll(int timeoutMs, ChannelList* activeChannels) override;
void updateChannel(Channel* channel) override;
void removeChannel(Channel* channel) override;
void fillActiveChannels(int numEvents, ChannelList* activeChannels) const;

具体函数实现

PollPoller::PollPoller(EventLoop* loop)
  : Poller(loop)			//调用父类的构造函数
{
}

PollPoller::~PollPoller() = default;

//EvenLoop 的 loop() 其实就是调用 Poller 的 poll()
//timeoutMs 是超时时间
Timestamp PollPoller::poll(int timeoutMs, ChannelList* activeChannels)
{
  //调用 poll 的系统函数,返回活跃事件个数
  int numEvents = ::poll(&*pollfds_.begin(), pollfds_.size(), timeoutMs);
  int savedErrno = errno;

  //获取调用 poll() 的时间戳
  Timestamp now(Timestamp::now());

  //当个数大于 0,调用 fillActiveChannels()
  if (numEvents > 0)
  {
    LOG_TRACE << numEvents << " events happened";
    //fillActiveChannels 实现的功能就是将活跃通道加入到 activeChannels 中返回
    fillActiveChannels(numEvents, activeChannels);
  }
  
  //超时则输出 nothing happened
  else if (numEvents == 0)
  {
    LOG_TRACE << " nothing happened";
  }

 //发生错误
  else
  {
    if (savedErrno != EINTR)
    {
      errno = savedErrno;
      LOG_SYSERR << "PollPoller::poll()";
    }
  }
  return now;
}

void PollPoller::fillActiveChannels(int numEvents, ChannelList* activeChannels) const
{
  //遍历监听函数,这里就是 poll() 的处理过程,这部分知识默认学过,不会去学
  for (PollFdList::const_iterator pfd = pollfds_.begin();
      pfd != pollfds_.end() && numEvents > 0; ++pfd)
  {
  	//当监听套接字的 revents 不为 0,表示有事件发生
    if (pfd->revents > 0)
    {
      --numEvents;
      
      //通过文件描述符找到该通道
      //key 是 fd,value 是 Channel*,channels_ 中保存的是需要监听的套接字对象
      ChannelMap::const_iterator ch = channels_.find(pfd->fd);
      assert(ch != channels_.end());
      
      Channel* channel = ch->second;
      assert(channel->fd() == pfd->fd);
      
      //pollfd对象的 revent 改变了,但通道没有
      //所以要设置 channel 的事件,以便 EvenPol::poll() 中的 handleEvent() 根据 revents 处理事件
      channel->set_revents(pfd->revents);
     
      //将活跃通道加入到 activeChannels 中
      activeChannels->push_back(channel);
    }
  }
}

//该函数只能在 IO 线程中调用
//Channel 中的 update() 就是调用这里的 updateChannel()
void PollPoller::updateChannel(Channel* channel)
{
  //断言在 IO 线程中
  Poller::assertInLoopThread();
  LOG_TRACE << "fd = " << channel->fd() << " events = " << channel->events();
  
  //前一节说 index 很重要,这里就体现出来了,默认 index 的值为 -1
  if (channel->index() < 0)
  {
    //在 channels_ 中查找,默认是找不到的,所以 index 默认是添加通道到监听数组中
    assert(channels_.find(channel->fd()) == channels_.end());
    
    //构造 pollfd 对象
    struct pollfd pfd;
    pfd.fd = channel->fd();
    pfd.events = static_cast<short>(channel->events());
    pfd.revents = 0;
    
    //将对象放到数组中
    pollfds_.push_back(pfd);

	//修改 index 的值为数组下标
    int idx = static_cast<int>(pollfds_.size())-1;
    channel->set_index(idx);

    //加入到 channels_ 中
    channels_[pfd.fd] = channel;
  }

  //当 index 大于等于 0 时,index 表示数组下标,这里就是修改监听事件
  else
  {
    assert(channels_.find(channel->fd()) != channels_.end());
    assert(channels_[channel->fd()] == channel);
    
    //获取 channel 对应 fd 在数组中的下标
	int idx = channel->index();
    assert(0 <= idx && idx < static_cast<int>(pollfds_.size()));
   
    //修改监听事件
    struct pollfd& pfd = pollfds_[idx];
    //这里 -channel->fd() - 1 是什么 ?
    //根据下面可以知道,之前可能将通道设置为闲置,但可能并没有删除,突然想重新使用
    assert(pfd.fd == channel->fd() || pfd.fd == -channel->fd()-1);
    pfd.fd = channel->fd();
    pfd.events = static_cast<short>(channel->events());
    pfd.revents = 0;
    
    //这个函数的功能是注册和修改,并不会删除通道
    //这里将 fd 变为 -channel->fd()-1 是为了避免后面删除通道时产生错误
    //并且 fd 不能为负数,如果事件相应就会报错
    if (channel->isNoneEvent())
    {
      // ignore this pollfd
      pfd.fd = -channel->fd()-1;    //其实这里可以写成 -1
    }
  }
}


//该函数只能在 IO 线程中调用
//Channel 中的 remove() 就是调用这里的 removeChannel()
void PollPoller::removeChannel(Channel* channel)
{
  Poller::assertInLoopThread();
  LOG_TRACE << "fd = " << channel->fd();
  assert(channels_.find(channel->fd()) != channels_.end());
  assert(channels_[channel->fd()] == channel);
  
  //只有事件闲置才能被修改
  //这也是为什么上一节我说调用 remove 之前要调用 disableAll() 的原因
  assert(channel->isNoneEvent());
   
  //获取在 channels_ 中的下标
  int idx = channel->index();
  assert(0 <= idx && idx < static_cast<int>(pollfds_.size()));
  const struct pollfd& pfd = pollfds_[idx]; (void)pfd;
  
  //只有空闲的事件可以被删除,而空闲事件在 pollfds_ 中的 fd 为 -channel->fd()-1
  assert(pfd.fd == -channel->fd()-1 && pfd.events == channel->events());
  //通过 key 将键值对从 map 中移除
  size_t n = channels_.erase(channel->fd());
  assert(n == 1); (void)n;
  
  //将 pollfd 从 pollfds_ 中移除
  //移除的方法有两种,如果在末尾,直接删除
  if (implicit_cast<size_t>(idx) == pollfds_.size()-1)
  {
    pollfds_.pop_back();
  }
  //如果不在最后一个,我们将最后一个元素和他进行交换,然后将最后一个删除
  else
  {
    //最后一个元素
    int channelAtEnd = pollfds_.back().fd;
    iter_swap(pollfds_.begin()+idx, pollfds_.end()-1);
    //防止最后一个元素为空闲事件,通过 channels_ 的 key 找到通道,并修改通道中的 index
    if (channelAtEnd < 0)
    {
      channelAtEnd = -channelAtEnd-1;
    }
    channels_[channelAtEnd]->set_index(idx);
    //从尾部删除
    pollfds_.pop_back();
  }
}

总结

  1. PollPoller 中继承了父类的 loop_channels_loop_ 的作用在于确定函数是在 IO 线程中调用的;而 channels_ 的作用是快速查找 Channel 是不是已经存在与 loop__ 中,以及通过 fd 快速找到对应的 channel 对象
  2. PollPoller 中新创建了 pollfds_ 变量,用来保存监听对象;
  3. updateChannel() 通过传入的参数 channelindex 决定该通道是加入到 pollfds_ 中,还是修改在 pollfds_ 中的事件;注意对空闲事件的处理;
  4. removeChannel() 将传入的 channel 对象从监听数组 pollfds_channels_ 中移除;注意移除的处理;
  5. poll() 函数的处理过程就是调用多路 IO 复用的 poll() ,来获取活跃的通道,并传递给上层 loop_loop();注意 pollfds_ 中的事件返回后,需要改变通道中的 revents

EpollPoller 源码分析

PollPoller 使用一个数组来保存监听对象;而 EPollPoller 使用文件描述符来监听,但事件发生了返回到 events_

int epollfd_;           //epoll 套接字
EventList events_;      //用于保存 epoll_wait 触发的事件数组,数组最大是 16 个

具体函数实现

namespace
{
const int kNew = -1;
const int kAdded = 1;
const int kDeleted = 2;
}

void EPollPoller::updateChannel(Channel* channel)
{
  Poller::assertInLoopThread();
  //在 PollPoller 中 index 是数组下标,而 EpollEpoller 中 index 表示通道的状态
  //-1 表示新创建,1 表示已添加,2 表示已删除,注意这里的已删除是指从 epollfd_ 的监听列表中删除
  const int index = channel->index();
  LOG_TRACE << "fd = " << channel->fd()
    << " events = " << channel->events() << " index = " << index;

  //kDeleted 指从 epollfd_ 的监听列表中删除,但通道本身还存在,只是闲置
  if (index == kNew || index == kDeleted)
  {
    // 如果是新的 channel,添加进 channels_,并且修改 idx
    int fd = channel->fd();
    if (index == kNew)
    {
      assert(channels_.find(fd) == channels_.end());
      channels_[fd] = channel;
    }
    else // index == kDeleted
    {
      assert(channels_.find(fd) != channels_.end());
      assert(channels_[fd] == channel);
    }

    channel->set_index(kAdded);
   	//
    update(EPOLL_CTL_ADD, channel);
  }
  else
  {
    //如果事件不为空,则修改
    //为空则从 epollfd_ 中删除,避免监听
    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);
      channel->set_index(kDeleted);
    }
    else
    {
      update(EPOLL_CTL_MOD, channel);
    }
  }
}

void EPollPoller::update(int operation, Channel* channel)
{
  struct epoll_event event;
  memZero(&event, sizeof event);
  event.events = channel->events();
  //这里很重要,将 channel 通过这种方式传入到监听事件中,但监听事件发生时就可以从 events_[i].data.ptr 中读取通道
  event.data.ptr = channel;
  int fd = channel->fd();
  LOG_TRACE << "epoll_ctl op = " << operationToString(operation)
    << " fd = " << fd << " event = { " << channel->eventsToString() << " }";

  //根据 operator 处理事件的状态
  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;
    }
  }
}


//removeChannel 才真正将 channel 从通道列表中删除
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);
}



Timestamp EPollPoller::poll(int timeoutMs, ChannelList* activeChannels)
{
  LOG_TRACE << "fd total count " << channels_.size();
  int numEvents = ::epoll_wait(epollfd_,
                               &*events_.begin(),
                               static_cast<int>(events_.size()),
                               timeoutMs);
  int savedErrno = errno;
  Timestamp now(Timestamp::now());
  if (numEvents > 0)
  {
    LOG_TRACE << numEvents << " events happened";
    fillActiveChannels(numEvents, activeChannels);
    //如果发生事件太多,每次扩大两倍 
    if (implicit_cast<size_t>(numEvents) == events_.size())
    {
      events_.resize(events_.size()*2);
    }
  }
  else if (numEvents == 0)
  {
    LOG_TRACE << "nothing happened";
  }
  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());
  for (int i = 0; i < numEvents; ++i)
  {
    //这点在 update 中提到过了,这就是是为什么创建 epollfd 时需要传入 channel 的原因
    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);
  }
}

总结

这篇文章和上一篇文章讲解了 EvenLoop 的基本功能和流程,这里的所以函数都只能在 IO 线程中调用,但实际情况下很多函数都需要在其他线程中调用muduo 库也实现了定时器的功能,这部分功能,一些程序会使用信号来处理函数,但是在多线程程序中处理信号是一件很麻烦的事情;所以 muduo 库中将定时器用文件描述符来进行处理,而这一部分的处理也需要在 EvenLoop 中实现;这两部分是接下来讲解的重点;

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值