本篇博客针对Poller类及其2个派生类PollPoller、EPollPoller做下小结。
博客代码来自于陈硕的muduo网络库,github地址https://github.com/chenshuo/muduo
工作原理:
muduo网络库中Poller类是一个抽象类,用户使用PollPoller或者EPollPoller类。Poller类管理着一个文件描述符fd和Channel指针的映射表channels_,同一个IO线程内所有用户注册的文件描述符都保存在这里,包括定时器或socketfd等。该类的派生类对象作为EventLoop类的一个实例化对象,IO线程在loop()会调用poller_->poll()阻塞当前线程,直到channels_中某个监听事件得到响应或超过监听时间才返回,当某个文件描述符fd监听事件得到响应时,可以通过映射表找到对应的channel地址,调用fillActiveChannels()获取得到响应的channel集合,在loop()中依次调用各channel的回调函数即完成一次事件循环。
学习笔记:
Poller类是IO mutiplexing的封装,他的2个派生类PollPoller和EPollPoller分别支持poll和epoll的IO multiplexing机制,每个EventLoop类都有一个Poller派生类的实例化对象,该对象的所有操作都在同一个IO线程完成的,不存在多线程抢占的问题,只需要判断当前执行线程是否在EventLoop对象的创建线程即可。此外Poller并不拥有Channel,Channel在析构前必须自己remove。
poll和epoll都是IO多路复用的机制。I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的。
既然有了poll,为什么还需要epoll?epoll作为poll的升级,解决了poll存在的以下问题:
1、poll中fd数组在每次调用poll()时被整体拷贝于用户态和内核地址空间之间,而epoll只在epoll_ctl拷贝一次;
2,poll中对fd采用轮询的方法,而在epoll中,只有被唤醒的fd才会返回,当fd总数很多但活跃数占比很小时poll效率较低;
3、poll只支持水平触发,而epoll支持水平触发和边缘触发两种方式。
支持poll类型的PollPoller.h
#ifndef MUDUO_NET_POLLER_POLLPOLLER_H
#define MUDUO_NET_POLLER_POLLPOLLER_H
#include "muduo/net/Poller.h"
#include <vector>
struct pollfd;
namespace muduo
{
namespace net
{
///
/// IO Multiplexing with poll(2).
///
class PollPoller : public Poller
{
public:
PollPoller(EventLoop* loop);
~PollPoller() override;
//调用::poll(),阻塞等待监听事件返回
Timestamp poll(int timeoutMs, ChannelList* activeChannels) override;
//更新(增、删、改)监听的channel
void updateChannel(Channel* channel) override;
//channel销毁清除其在PollPoller中映射表保存的指针,避免悬垂指针
void removeChannel(Channel* channel) override;
private:
//根据poll返回的fd从channels_获取对应的channel保存到activeChannels,
void fillActiveChannels(int numEvents,
ChannelList* activeChannels) const;
typedef std::vector<struct pollfd> PollFdList;
PollFdList pollfds_;//需要监听的所有文件描述符
};
} // namespace net
} // namespace muduo
#endif // MUDUO_NET_POLLER_POLLPOLLER_H
支持poll类型的PollPoller.cpp
#include "muduo/net/poller/PollPoller.h"
#include "muduo/base/Logging.h"
#include "muduo/base/Types.h"
#include "muduo/net/Channel.h"
#include <assert.h>
#include <errno.h>
#include <poll.h>
using namespace muduo;
using namespace muduo::net;
PollPoller::PollPoller(EventLoop* loop)
: Poller(loop)
{
}
PollPoller::~PollPoller() = default;
Timestamp PollPoller::poll(int timeoutMs, ChannelList* activeChannels)
{
// XXX pollfds_ shouldn't change
//当timeoutMs=-1,阻塞等待监听事件,否则在timeoutMs时间内等待监听事件
int numEvents = ::poll(&*pollfds_.begin(), pollfds_.size(), timeoutMs);
int savedErrno = errno;
Timestamp now(Timestamp::now());
if (numEvents > 0)
{
//numEvents表示响应的事件个数,但具体是哪些事件需要用户去pollfds_挨个遍历
LOG_TRACE << numEvents << " events happened";
fillActiveChannels(numEvents, activeChannels);
}
else if (numEvents == 0)
{
//表示poll()在监听时间内没有任何监听事件返回
LOG_TRACE << " nothing happened";
}
else
{
//poll()返回错误
if (savedErrno != EINTR)
{
errno = savedErrno;
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)
{
//revents>0表示监听的可读、可写、错误等事件中至少有一个得到响应
//--numEvents可以在某种程度上减少遍历次数
--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)
{
// 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_保存新增的文件描述符
pollfds_.push_back(pfd);
int idx = static_cast<int>(pollfds_.size())-1;
channel->set_index(idx);
//channels_映射表保存pfd和channel指针的pair
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 == -channel->fd()-1);
pfd.fd = channel->fd();
pfd.events = static_cast<short>(channel->events());
pfd.revents = 0;
//删除channel前会把events_设置为kNoneEvent
if (channel->isNoneEvent())
{
// 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)
{
//若待删除的fd正好在pollfds_的末尾,直接pop_back()
pollfds_.pop_back();
}
else
{
int channelAtEnd = pollfds_.back().fd;
//若待删除的fd不在pollfds_的末尾,交换当前fd与末尾fd的位置再pop_back()
iter_swap(pollfds_.begin()+idx, pollfds_.end()-1);
if (channelAtEnd < 0)
{
//为什么这里会小于0呢?
channelAtEnd = -channelAtEnd-1;
}
channels_[channelAtEnd]->set_index(idx);
pollfds_.pop_back();
}
}
支持epoll类型的EPollPoller.h
#ifndef MUDUO_NET_POLLER_EPOLLPOLLER_H
#define MUDUO_NET_POLLER_EPOLLPOLLER_H
#include "muduo/net/Poller.h"
#include <vector>
struct epoll_event;
namespace muduo
{
namespace net
{
///
/// IO Multiplexing with epoll(4).
///
class EPollPoller : public Poller
{
public:
EPollPoller(EventLoop* loop);
~EPollPoller() override;
Timestamp poll(int timeoutMs, ChannelList* activeChannels) override;
void updateChannel(Channel* channel) override;
void removeChannel(Channel* channel) override;
private:
static const int kInitEventListSize = 16;//初始化监听事件个数
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()返回的活跃监听事件
};
} // namespace net
} // namespace muduo
#endif // MUDUO_NET_POLLER_EPOLLPOLLER_H
支持epoll类型的EPollPoller.cpp
#include "muduo/net/poller/EPollPoller.h"
#include "muduo/base/Logging.h"
#include "muduo/net/Channel.h"
#include <assert.h>
#include <errno.h>
#include <poll.h>
#include <sys/epoll.h>
#include <unistd.h>
using namespace muduo;
using namespace muduo::net;
// On Linux, the constants of poll(2) and epoll(4)
// are expected to be the same.
static_assert(EPOLLIN == POLLIN, "epoll uses same flag values as poll");
static_assert(EPOLLPRI == POLLPRI, "epoll uses same flag values as poll");
static_assert(EPOLLOUT == POLLOUT, "epoll uses same flag values as poll");
static_assert(EPOLLRDHUP == POLLRDHUP, "epoll uses same flag values as poll");
static_assert(EPOLLERR == POLLERR, "epoll uses same flag values as poll");
static_assert(EPOLLHUP == POLLHUP, "epoll uses same flag values as poll");
namespace
{
const int kNew = -1;
const int kAdded = 1;
const int kDeleted = 2;
}
EPollPoller::EPollPoller(EventLoop* loop)
: Poller(loop),
epollfd_(::epoll_create1(EPOLL_CLOEXEC)),
events_(kInitEventListSize)
{
if (epollfd_ < 0)
{
LOG_SYSFATAL << "EPollPoller::EPollPoller";
}
}
EPollPoller::~EPollPoller()
{
::close(epollfd_);
}
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);
//返回的numEvents即为被唤醒的channel个数,对应信息保存在events_数组中
int savedErrno = errno;
Timestamp now(Timestamp::now());
if (numEvents > 0)
{
LOG_TRACE << numEvents << " events happened";
//把得到响应的时间保存到activeChannels中
fillActiveChannels(numEvents, activeChannels);
if (implicit_cast<size_t>(numEvents) == events_.size())
{
//events_容量不够,进行扩容
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)
{
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
//将epoll_wait()返回的事件类型(可读、可写、错误等写入到channel
channel->set_revents(events_[i].events);
activeChannels->push_back(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)
{
// a new one, add with EPOLL_CTL_ADD
int fd = channel->fd();
if (index == kNew)
{
//对于新增事件,添加到channels_映射表
assert(channels_.find(fd) == channels_.end());
channels_[fd] = channel;
}
else // index == kDeleted
{
assert(channels_.find(fd) != channels_.end());
assert(channels_[fd] == channel);
//对于已删除的channel,EPOLL_CTL_ADD重新加入即可
}
channel->set_index(kAdded);
update(EPOLL_CTL_ADD, channel);
}
else
{
// 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);
if (channel->isNoneEvent())
{
//删除channel前会把监听事件设置成kNoneEvent
update(EPOLL_CTL_DEL, channel);
channel->set_index(kDeleted);
}
else
{
update(EPOLL_CTL_MOD, channel);
}
}
}
//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);
}
void EPollPoller::update(int operation, Channel* channel)
{
struct epoll_event event;
memZero(&event, sizeof event);
//event保存channel监听事件即channel地址
event.events = channel->events();
event.data.ptr = channel;
int fd = channel->fd();
LOG_TRACE << "epoll_ctl op = " << operationToString(operation)
<< " fd = " << fd << " event = { " << channel->eventsToString() << " }";
//根据传入的operation对事件进行增、删、改操作
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;
}
}
}
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";
}
}