Mudu分析及总结(三)Poller、PollPoller

Poller是基类,PollPoller和EPollPoller是子类,本文只介绍Poller和PollPoller。

  • Poller操作Channel对象,但是不拥有Channel对象。
  • PollPoller是基于poll实现,EPollPoller是基于epoll实现。

一、类关系图

在这里插入图片描述

二、工作时序图

在这里插入图片描述

三、poll方法基本概念

1、poll()
int poll (struct pollfd *fds, size_t nfds , int timeout);

参数解释:

  • fds:指向struct pollfd数组
  • nfds:struct pollfd数组元素的个数,表示要检测几个pollfd。
  • timeout:表示poll函数的超时时间,单位是毫秒。

函数返回值:

  • 大于0:表示监听的文件描述符中就绪可操作的文件描述符个数。
  • 等于0:表示无活跃的文件描述符,超时返回。
  • 小于0:表示出错。
2、struct pollfd
struct pollfd {
int fd;        /* 文件描述符 */
short events; /* 告诉操作系统要监听的事件(可以多个事件) */
short revents; /* 操作系统返回的实际发生事件 */
};

三、Poller

Poller是基类,没什么实现。

1、Poller.h
class Poller : boost::noncopyable
{
 public:
  typedef std::vector<Channel*> ChannelList; //活跃的通道

  Poller(EventLoop* loop);
  virtual ~Poller();

  //
  /// Polls the I/O events.
  /// Must be called in the loop thread.
  virtual Timestamp poll(int timeoutMs, ChannelList* activeChannels) = 0;

  /// Changes the interested I/O events.
  /// Must be called in the loop thread.
  virtual void updateChannel(Channel* channel) = 0;

  /// Remove the channel, when it destructs.
  /// Must be called in the loop thread.
  virtual void removeChannel(Channel* channel) = 0;

  static Poller* newDefaultPoller(EventLoop* loop);

  void assertInLoopThread()
  {
    ownerLoop_->assertInLoopThread();
  }

 private:
  EventLoop* ownerLoop_; //Poller的拥有者EventLoop
};
四、PollPoller.h
class PollPoller : public Poller
{
 public:

  PollPoller(EventLoop* loop);
  virtual ~PollPoller();

  //查询出活跃的通道
  virtual Timestamp poll(int timeoutMs, ChannelList* activeChannels);
  
  //更新通道
  virtual void updateChannel(Channel* channel);
  
  //移除通道,一般在调用此方法之前,都会调用Channel->disableAll()
  virtual void removeChannel(Channel* channel);

 private:
  void fillActiveChannels(int numEvents,
                          ChannelList* activeChannels) const;

  typedef std::vector<struct pollfd> PollFdList;
  typedef std::map<int, Channel*> ChannelMap;
  PollFdList pollfds_; //fd的集合
  ChannelMap channels_; //fd与Channel的映射
};

五、PollPoller.cc

PollPoller::PollPoller(EventLoop* loop)
  : Poller(loop)
{
}

PollPoller::~PollPoller()
{
}

Timestamp PollPoller::poll(int timeoutMs, ChannelList* activeChannels)
{
  //通过poll查询是否有活跃的文件描述符
  // XXX pollfds_ shouldn't change
  int numEvents = ::poll(&*pollfds_.begin(), pollfds_.size(), timeoutMs);
  Timestamp now(Timestamp::now());
  if (numEvents > 0)
  {
    //返回值大于0表示对应个数活跃的文件描述符
    //调用函数,获取活跃的文件描述符。
    LOG_TRACE << numEvents << " events happended";
    fillActiveChannels(numEvents, activeChannels);
  }
  else if (numEvents == 0)
  {
    //无任何活跃的文件描述符
    LOG_TRACE << " nothing happended";
  }
  else
  {
    //出错
    LOG_SYSERR << "PollPoller::poll()";
  }
  return now;
}

void PollPoller::fillActiveChannels(int numEvents,ChannelList* activeChannels) const
{
  for (PollFdList::const_iterator pfd = pollfds_.begin();
      pfd != pollfds_.end() && numEvents > 0; ++pfd)
  {
    //循环取值
    if (pfd->revents > 0)
    {  
      //有事件发生时,操作系统会给struct pollfd的revents赋值,值大于0
      --numEvents;
      ChannelMap::const_iterator ch = channels_.find(pfd->fd);
      assert(ch != channels_.end());
      Channel* channel = ch->second;
      assert(channel->fd() == pfd->fd);
      channel->set_revents(pfd->revents);
      // pfd->revents = 0;
      activeChannels->push_back(channel);
    }
  }
}

void PollPoller::updateChannel(Channel* channel)
{
  Poller::assertInLoopThread();
  LOG_TRACE << "fd = " << channel->fd() << " events = " << channel->events();
  if (channel->index() < 0)
  {
    //index小于0表示是一个新的Channel,
    //添加到管理队列
    // a new one, add to pollfds_
    assert(channels_.find(channel->fd()) == channels_.end());
    struct pollfd pfd;
    pfd.fd = channel->fd();
    pfd.events = static_cast<short>(channel->events());
    pfd.revents = 0;
    pollfds_.push_back(pfd);
    int idx = static_cast<int>(pollfds_.size())-1;
    channel->set_index(idx);
    channels_[pfd.fd] = channel;
  }
  else
  {
    //index大于0表示队列中存在此通道,所以进行更新。
    // update existing one
    assert(channels_.find(channel->fd()) != channels_.end()); //校验
    assert(channels_[channel->fd()] == channel);//校验
    int idx = channel->index();
    assert(0 <= idx && idx < static_cast<int>(pollfds_.size()));
    struct pollfd& pfd = pollfds_[idx];
    assert(pfd.fd == channel->fd() || pfd.fd == -channel->fd()-1);
    pfd.events = static_cast<short>(channel->events());//用户请求事件保存
    pfd.revents = 0;
    if (channel->isNoneEvent())
    {
      //如果通过被设置为无任何事件,则将fd设置为负数,
      //至于为什么至于设置,下文会解释
      // ignore this pollfd
      pfd.fd = -channel->fd()-1;
    }
  }
}

void PollPoller::removeChannel(Channel* channel)
{
  //移除通道
  Poller::assertInLoopThread();
  LOG_TRACE << "fd = " << channel->fd();
  assert(channels_.find(channel->fd()) != channels_.end());
  assert(channels_[channel->fd()] == channel);
  assert(channel->isNoneEvent());
  int idx = channel->index();
  assert(0 <= idx && idx < static_cast<int>(pollfds_.size()));
  const struct pollfd& pfd = pollfds_[idx]; (void)pfd;
  assert(pfd.fd == -channel->fd()-1 && pfd.events == channel->events());
  size_t n = channels_.erase(channel->fd());
  assert(n == 1); (void)n;
  if (implicit_cast<size_t>(idx) == pollfds_.size()-1)
  {
    //如果index正好是列表的结尾,则直接pop释放。
    pollfds_.pop_back();
  }
  else
  {
    //如果删除的struct pollfd不是列表的结尾,
    //出于效率考虑,直接将要删除的pollfd与尾部的
    //pollfd交换,然后释放
    int channelAtEnd = pollfds_.back().fd;
    iter_swap(pollfds_.begin()+idx, pollfds_.end()-1);
    if (channelAtEnd < 0)
    {
      //因为在updateChannel时,是将通道进行取反-1,
      //所以这里原样取回。
      channelAtEnd = -channelAtEnd-1;
    }
    channels_[channelAtEnd]->set_index(idx);
    pollfds_.pop_back();
  }
}

六、关键点

1、pfd.fd = -channel->fd()-1;
  • 如果某一个channel暂时不关心任何事件,那么可以吧pollfd.fd设置为负数,这样poll会忽略此文件描述符。不能将pollfd.events设置为0,因为无法屏蔽POLLERR事件。
  • pfd.fd再次进行减一是为了解决fd可能为0的情况,-0还是0,所以减1。
  • updateChannel()更新时若不关心任何事件,一开始是把fd设置为-1的,但是为了配合removeChannel()进行高效的删除struct pollfd,就直接去fd的负数了。因为删除时还需要用到fd。
2、fd置位负数,未置位正数问题

当进行updateChannel时,若通道不再关系任何事件,则将struct pollfd对应的fd设置为负数,一开始我还在找这里设置为了负数。当通道再次关注事件时应该设置为正数,将fd还原回来,但Muduo实现中却没有,只有在进行removeChannel的时候对fd进行取正。
通过查看源码调用

void TcpConnection::connectDestroyed()
{
  loop_->assertInLoopThread();
  assert(state_ == kConnected || state_ == kDisconnecting);
  setState(kDisconnected);
  channel_->disableAll();
  connectionCallback_(shared_from_this());

  loop_->removeChannel(get_pointer(channel_));
}

channel->disableAll()与removeChannel()是成对出现的,channel->disableAll()将通道设置为不关心事件通道,也就是将fd设置为负数,然后removeChannel()中就移除掉此通道,所以说我上面疑问的将fd设置为负数,而没其他地方再将fd设置为正数。因为已经没这个必要了,fd设置为负数,接下来就是移除此通道。

3、删除fd小技巧

删除channel时,由于pollfds_使用的是vector来存储channel的,可以根据channle本身的index_来确定该channel在vector中所在的位置。如果直接删除该位置的话,那么后面的channel都需要上移一个位置,导致后面的channel都需要更新自己的index_,这样效率会很低。这里有一个小技巧,就是直接将vector最后的元素A与该位置进行呼唤,只需要修改A的index_就好,其他的channel不受影响

4、fillActiveChannels设计

之所以需要返回activateChannels,而不是在遍历发生事件的文件描述符的同时执行对应的channel中的handleEvent函数,是因为handleEvent函数有可能会对pollfds_进行修改,如删除channel,使得在迭代期间pollfds_大小发生改变,这是一件很危险的事情。另一个原因就是简化Poller的职责,Poller只负责监听,不负责事件的分发与处理。


备注:
参考链接【http://liubigbin.github.io/2016/07/20/muduo中的Channel和Poller/】

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值