东阳的学习笔记
文章目录
本文讲 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 同时支持
poll
和epoll
- Poller 是 EventLoop的间接成员。只供其
owner EventLoop
在 IO 线程调用,因此无须加锁
。- 其生命期与 EventLoop 相等。
- Poller 并不拥有 Channel,Channel 在析构之前必须自己 unsigister (
EventLoop::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); }