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/】