先序
前一节介绍 Poller 类的时候就发现这个类实际上是接口类,具体的函数实现还是要看 muduo/poller 文件中的 EPollPoller 和 PollPoller;这里我们主要详细介绍 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();
}
}
总结
- PollPoller 中继承了父类的 loop_ 和 channels_,loop_ 的作用在于确定函数是在 IO 线程中调用的;而 channels_ 的作用是快速查找 Channel 是不是已经存在与 loop__ 中,以及通过 fd 快速找到对应的 channel 对象;
- PollPoller 中新创建了 pollfds_ 变量,用来保存监听对象;
- updateChannel() 通过传入的参数 channel 的 index 决定该通道是加入到 pollfds_ 中,还是修改在 pollfds_ 中的事件;注意对空闲事件的处理;
- removeChannel() 将传入的 channel 对象从监听数组 pollfds_ 和 channels_ 中移除;注意移除的处理;
- 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 中实现;这两部分是接下来讲解的重点;