muduo库学习之设计与实现02——Reactor的关键结构

东阳的学习笔记

本文讲 Reactor 最核心的事件分发机制,即将 IO multiplexing 拿到的 IO 事件分发给各个文件描述符(fd)的事件处理函数。

一、Channel class

Channel class 的功能有一点类似 Java NIO 的 SelectableChannel 和 Selection-Key的组合:

  • 每个 Channel 对象自始自终只属于一个 EventLoop,因此每个 Channel 对象都只属于一个 IO 线程
  • 每个 Channel 对象自始自终只负责一个文件描述符的 IO 事件分发。
    • 它并不拥有这个 fd,也不会在析构的时候关闭这个 fd。
  • Channel 会把不同的IO事件分发为不同的事件回调,而且回调使用 boost::function 表示,用户无须继承 Channel,Channel 并不是基类
  • muduo 的用户一般不会直接使用 Channel ,而是使用更上层的封装,如 TcpConnection
  • Channel 的成员函数都只能在 IO 线程调用因此更新数据成员不用加锁

下面是 Channel 的 public interface:

class EventLoop;

///
/// A selectable I/O channel.
///
/// This class doesn't own the file descriptor.
/// The file descriptor could be a socket,
/// an eventfd, a timerfd, or a signalfd
class Channel : boost::noncopyable
{
public:
typedef boost::function<void()> EventCallback;

Channel(EventLoop* loop, int fd);

void handleEvent();
void setReadCallback(const EventCallback& cb)
{ readCallback_ = cb; }
void setWriteCallback(const EventCallback& cb)
{ writeCallback_ = cb; }
void setErrorCallback(const EventCallback& cb)
{ errorCallback_ = cb; }

int fd() const { return fd_; }
int events() const { return events_; }
void set_revents(int revt) { revents_ = revt; }
bool isNoneEvent() const { return events_ == kNoneEvent; }

void enableReading() { events_ |= kReadEvent; update(); }
// void enableWriting() { events_ |= kWriteEvent; update(); }
// void disableWriting() { events_ &= ~kWriteEvent; update(); }
// void disableAll() { events_ = kNoneEvent; update(); }

// for Poller
int index() { return index_; }
void set_index(int idx) { index_ = idx; }

EventLoop* ownerLoop() { return loop_; }

1.1 Channel class 的数据成员

  • events_:是它关心的IO事件,由用户设置
  • revents_:目前活动的事件,由 EventLoop/Poller设置
  • 这两个字段都是 bit pattern他们的名字来自 poll(2) 的 struct pollfd
    它们的名字来自 poll(2)的struct pollfd
 private:
 void update();

 static const int kNoneEvent;
 static const int kReadEvent;
 static const int kWriteEvent;

 EventLoop* loop_;
 const int  fd_;
 int        events_;
 int        revents_;
 int        index_; // used by Poller.

 EventCallback readCallback_;
 EventCallback writeCallback_;
 EventCallback errorCallback_;
};

1.2 构造函数

  • 注意到 Channel.h 没有包含任何 POSIX 头文件,因此 kReadEvent 和 kWriteEvent 等常量定义要放到 Channel.cc 文件中。
const int Channel::kNoneEvent = 0;
const int Channel::kReadEvent = POLLIN | POLLPRI;
const int Channel::kWriteEvent = POLLOUT;

Channel::Channel(EventLoop* loop, int fdArg)
 : loop_(loop),
   fd_(fdArg),
   events_(0),
   revents_(0),
   index_(-1)
{
}

1.3 update

  • Channel::update() 会调用 EventLoop::updateChannel(),后者会转而调用 POller::updateChannel。
void Channel::update()
{
 loop_->updateChannel(this);
}

1.4 handleEvent

Channel::handleEvent()是Channel的核心,它由 EventLoop::loop()调用,它的功能是根据 revents_ 的值分别调用不同的用户回调。

void Channel::handleEvent()
{
 if (revents_ & POLLNVAL) {
   LOG_WARN << "Channel::handle_event() POLLNVAL";
 }

 if (revents_ & (POLLERR | POLLNVAL)) {
   if (errorCallback_) errorCallback_();
 }
 if (revents_ & (POLLIN | POLLPRI | POLLRDHUP)) {
   if (readCallback_) readCallback_();
 }
 if (revents_ & POLLOUT) {
   if (writeCallback_) writeCallback_();
 }
}

二、Poller class

  • Poller class 是 IO multiplexing 的封装。
  • 它现在是个具体类,而在 muduo 中是个抽象基类,因为 muduo 同时支持 pollepoll
  • Poller 是 EventLoop的间接成员。只供其 owner EventLoop 在 IO 线程调用,因此无须加锁
  • 其生命期与 EventLoop 相等
  • Poller 并不拥有 Channel,Channel 在析构之前必须自己 unsigisterEventLoop::removeChannel()),避免空悬指针。

下面是 Poll class 的 interface:

  • Poller.h 并没有 include <poll.h>,而是自己前向声明了 struct pollfd,这并不妨碍我们定义 vector 成员。
  • Poller 供 EventLoop 调用的函数有两个,poll() 和 updateChannel(),Poller::poll() 不会在每次调用 poll(2) 之前临时构造 pollfd 数组,而是把它缓存起来(pollfds_)。
struct pollfd;

namespace muduo
{

class Channel;

///
/// IO Multiplexing with poll(2).
///
/// This class doesn't own the Channel objects.
class Poller : boost::noncopyable
{
public:
typedef std::vector<Channel*> ChannelList;

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

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

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

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

Q:缓存 pollfd 数组会有什么问题吗?

2.1 Poller class 的数据成员

ChannelMap:从 fd 到 Channel* 的映射

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

 typedef std::vector<struct pollfd> PollFdList;
 typedef std::map<int, Channel*> ChannelMap;

 EventLoop* ownerLoop_;
 PollFdList pollfds_;
 ChannelMap channels_;
};

2.2 构造/析构函数

Poller的构造函数和析构函数都很简单,因其成员都是标准库容器。

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

Poller::~Poller()
{
}

2.3 Poller::poll()

  • Poller::poll()是Poller的核心功能。它调用 poll(2)获得当前活动的IO事件,然后填入调用方传入的 activeChannels,并返回 poll(2) return 的时刻
  • 直接将 vector pollfds_ 作为参数传给 poll(2)。因为 C++ 标准保证 std::vector 的元素排列跟数组一样。
  • &4中的 &*pollfds_.begin() 是获得元素的首地址,这个表达式的类型为 pollfds_*,符合poll(2)的要求(C++11中可写为 pollfds_.data(),g++4.4 的 STL 也支持这种写法。)
Timestamp Poller::poll(int timeoutMs, ChannelList* activeChannels)
{
 // XXX pollfds_ shouldn't change
 int numEvents = ::poll(&*pollfds_.begin(), pollfds_.size(), timeoutMs);
 Timestamp now(Timestamp::now());
 if (numEvents > 0) {
   LOG_TRACE << numEvents << " events happended";
   fillActiveChannels(numEvents, activeChannels);
 } else if (numEvents == 0) {
   LOG_TRACE << " nothing happended";
 } else {
   LOG_SYSERR << "Poller::poll()";
 }
 return now;
}

2.4 fillActiveChannels

fillActiveChannels()遍历 pollfds_,找出有活动事件的 fd,把它对应的 Channel 填入 activeChannels。这个函数的复杂度是O(N),其中 N 是 pollfds_的长度,即文件描述符数目。

  • 为了提前结束循环,每找到一个活动 fd 就递减 numEvents。
  • 当 numEvents == 0 时,表示活动 fd 都找完了,不必做无用功。
  • 当前活动事件 revents 会保存在 Channel 中,供Channel::handleEvent()使用。

注意:不能一边遍历 pollfds_,一边调用Channel::handleEvent(),因为:

  • 其一、后者会添加/删除Channel,在遍历期间增删元素是很危险的
  • 其二、简化 Poller 的职责,它只负责 IO multiplexing,不负责事件分发
  • 其三、便于将来替换为其他更高效的 IO multiplexing 机制,如 epoll(4)。
void Poller::fillActiveChannels(int numEvents,
                               ChannelList* activeChannels) const
{
 for (PollFdList::const_iterator pfd = pollfds_.begin();
     pfd != pollfds_.end() && numEvents > 0; ++pfd)
 {
   if (pfd->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);
   }
 }
}

2.5 Poller::updateChannel()

  • 负责维护和更新 pollfds_ 数组(将传入的Channel 更新到 pollfds_ 数组中)
  • 添加新 Channel 的复杂度是O(logN),更新已有的 Channel 的复杂度是 O(1),因为 Channel 记住了自己在 pollfds_ 数组中的下标,因此可以快速定位。
  • 这里用了大量的 assert 来检查 invariant。
  • 倒数第四行:如果某个 Channel 暂时不关心任何事件,就把 pollfd.fd 设为 -1 ,让 poll(2) 忽略此项(&90)。这里不能将其设为 0:
    • 因为这样无法屏蔽 POLLERR 事件
    • 改进的做法是把 pollfd,fd 设为 Channel->fd()的相反数减一,这样可以进一步检查 invariant。
void Poller::updateChannel(Channel* channel)
{
 assertInLoopThread();        // 只能运行在 IO 线程内
 LOG_TRACE << "fd = " << channel->fd() << " events = " << channel->events();
 if (channel->index() < 0) {
   // 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());  // 对文件描述符fd上感兴趣的事件
   pfd.revents = 0;                                     // 当前实际发生的事情
   pollfds_.push_back(pfd);
   int idx = static_cast<int>(pollfds_.size())-1;
   channel->set_index(idx);
   channels_[pfd.fd] = channel;
 } else {
   // 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 == -1);
   pfd.events = static_cast<short>(channel->events());
   pfd.revents = 0;
   if (channel->isNoneEvent()) {
     // ignore this pollfd
     pfd.fd = -1;
   }
 }
}

三、 EventLoop的改动

  • 新增了 quit() 成员函数,还加了几个数据成员,并在构造函数里初始化他们。
  • 注意:EventLoop 通过 scoped_ptr 来间接持有 Poller。(因此 EventLoop.h中不必包含 Poller.h,只需前向声明 Poller class)。
    为此,EventLoop 的析构函数必须在 EventLoop.cc 中显示定义。
  void abortNotInLoopThread();

+ typedef std::vector<Channel*> ChannelList;

+ bool looping_; /* atomic */
+ bool quit_; /* atomic */
+ const pid_t threadId_;
+ boost::scoped_ptr<Poller> poller_;
+ ChannelList activeChannels_;
};

3.1 EventLoop::loop()

  • 有了真正的工作内容:调用 Poller::poll() 获得当前活动事件的 Channel 列表,然后依次调用每个 Channel 的 handleEvent() 函数。
void EventLoop::loop()
{
 assert(!looping_);
 assertInLoopThread();
 looping_ = true;
+ quit_ = false;

+  while (!quit_)    // 当quit_为true时退出
+ {
+  activeChannels_.clear();
+   poller_->poll(kPollTimeMs, &activeChannels_);    // 获取活动事件
+  for (ChannelList::iterator it = activeChannels_.begin();
+       it != activeChannels_.end(); ++it)
+   {
+    (*it)->handleEvent();
+ }
+ }

 LOG_TRACE << "EventLoop " << this << " stop looping";
 looping_ = false;
}

以上的几个 Class 尽管简陋,却构成了 Reactor 模式的核心内容。时序图如下:
在这里插入图片描述

3.2 EventLoop::quit()

终止事件循环,只要将 quit_ 设为 true即可,但是 quit() 不是立刻发生的。

  • 它会在 EventLoop::loop() 下一次检查 while (!quit_)的时候起效(3.1 loop() 中)
  • 如果在非当前IO线程调用 quit(),延迟可以长达数杪(将来我们可以唤醒EventLoop以缩小延迟)
  • 但是 quit() 不是中断或 signal ,而是设标志,如果 loop() 正阻塞在某个调用中,qiut()不会立刻生效
 void EventLoop::quit()
 {
 	qiut_ = true;
    // wakeup()
 }

3.3 EventLoop::updateChannel()

更新 Channel 列表, 调用 Poller::updateChannel

 void EventLoop::updateChannel(Channel* channel)
{
 assert(channel->ownerLoop() == this);
 assertInLoopThread();
 poller_->updateChannel(channel);
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

东阳z

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

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

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

打赏作者

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

抵扣说明:

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

余额充值