EpollPoller,是muduo库对I/O复用机制epoll的封装,不过默认使用的是EpollPoller。在EventLoop中初始化构造poller_,调用newDefaultPoller(this),构造默认的poller。
EventLoop::EventLoop()
: looping_(false), //表示还未循环
quit_(false),
eventHandling_(false),
callingPendingFunctors_(false),
iteration_(0),
threadId_(CurrentThread::tid()), //赋值真实id
poller_(Poller::newDefaultPoller(this)), //构造了一个实际的poller对象
timerQueue_(new TimerQueue(this)),
wakeupFd_(createEventfd()),
wakeupChannel_(new Channel(this, wakeupFd_)),
currentActiveChannel_(NULL)
newDefaultPoller内部是这样的:
Poller* Poller::newDefaultPoller(EventLoop* loop)
{
if (::getenv("MUDUO_USE_POLL")) //看环境变量,如果没有MUDUO_UER_POLL,默认epoll
{
return new PollPoller(loop);
}
else
{
return new EPollPoller(loop);
}
}
也就是说,如果我们没有主动设置环境变量为MUDUO_USE_POLL,我们默认选择为构造EpollPoller。
EpollPoller重写了父类的三个函数:
virtual Timestamp poll(int timeoutMs, ChannelList* activeChannels);
virtual void updateChannel(Channel* channel);
virtual void removeChannel(Channel* channel);
EpollPoller的私有成员有:
private:
static const int kInitEventListSize = 16; //默认事件数组大小,是用来装epoll_wait()返回的可读或可写事件的
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参数
其中kInitEventListSize是epoll模式中用来盛装返回的发生事件的数组大小,就是events_,它在构造函数中会被初始化,看下面:
EPollPoller::EPollPoller(EventLoop* loop)
: Poller(loop),
epollfd_(::epoll_create1(EPOLL_CLOEXEC)), //创建epollfd,使用带1的版本
events_(kInitEventListSize) //vector这样用时初始化kInitEventListSize个大小空间
{
if (epollfd_ < 0) //在构造函数中判断,<0就abort()
{
LOG_SYSFATAL << "EPollPoller::EPollPoller";
}
}
有一点需要注意,就是vector的初始化方式,这样可以预分配大小。至于我们以后不断向epoll中注册事件,然后导致返回的发生事件可能越来越多,events_装不下了,muduo也有自己的处理,是在EpollPoller的核心函数poll()函数中处理的:
if (implicit_cast<size_t>(numEvents) == events_.size()) //如果返回的事件数目等于当前事件数组大小,就分配2倍空间
{
events_.resize(events_.size()*2);
}
所以不必担心vector的大小问题了,后续会以乘以2倍的方式分配,这也是内存分配的常见做法。
我们再来回忆一下事件处理流程,先看一下事件注册图:
当某个客端,比如TCPConnection,或者我们自己写的代码调用Channel的enableReading()函数,就会引发Channel的Update()函数的调用,然后会调用EventLoop的UpdateChannel()函数,实际上会调用到Poller的updateChannel()函数。比如采用EpollPoller,那就会将事件注册到epoll的内核事件表中去,然后开启epoll_wait(),就是我们正常的epoll流程了。
//注册或修改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) //说明该channel未注册到epollfd中,index是在poll中是下标,在epoll中是三种状态,上面有三个常量
{
// a new one, add with EPOLL_CTL_ADD
int fd = channel->fd();
if (index == kNew)
{
assert(channels_.find(fd) == channels_.end());
channels_[fd] = channel; //kNew表示std::map<int,channel*> channels_也没有,添加进去。
}
else // index == kDeleted
{
assert(channels_.find(fd) != channels_.end());
assert(channels_[fd] == channel);
}
channel->set_index(kAdded);
update(EPOLL_CTL_ADD, channel); //注册到epollfd
}
else //已注册 kAdded。
{
// 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); //确保目前它存在
//poll是把文件描述符变为相反数减1,这里是使用EPOLL_CTL_DEL从内核事件表中删除
if (channel->isNoneEvent()) //不监听事件了
{
update(EPOLL_CTL_DEL, channel);//从epollfd中删除
channel->set_index(kDeleted);
}
else //已注册的channel,也许是更改了一些监听事件类型
{
update(EPOLL_CTL_MOD, channel);//修改该事件。
}
}
}
//从epollfd和std::map<int,channel*>中都删除channel,用assert来检查
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);
}
//封装epoll_ctl。
void EPollPoller::update(int operation, Channel* channel)
{
struct epoll_event event;
bzero(&event, sizeof event);
event.events = channel->events();//这两行代码也能反应channel跟struct epoll_event的映射。
event.data.ptr = channel;
int fd = channel->fd();
LOG_TRACE << "epoll_ctl op = " << operationToString(operation)
<< " fd = " << fd << " event = { " << channel->eventsToString() << " }";
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;
}
}
}
//这个是转出string,没什么好说的。
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";
}
}
namespace
{
/*
这3个const变量是来设置Channel类的index变量。
用来指示Channel事件在EPollPoller的注册情况。
*/
const int kNew = -1;
const int kAdded = 1;
const int kDeleted = 2;
}
kNew kDeleted kAdder
某个channel的index变量标明EPollPoler类的ChannelMap是否注册了epollfd_
kNew 未存有 未监听kDeleted 存有 未监听
kAdded 存有 已监听
然后看一下事件发生后处理的时序,先看图:
如图,EventLoop调用poll()是上面事件注册的过程。目前我们处于Poller位置,当Poller中事件发生后,会调用fillActiveChannels()函数,把发生的事件填充给活跃事件通道表中,具体实现是下面的Poller的成员函数fillActiveChannels函数:
/***poll函数:
poll监听在epollfd上注册的描述符,其中activeChannels变量是EventLoop中传递过来的
(EventLoop类只记载了活跃的事件,注册了什么事件交给了其成员变量poller),通过
fillActiveChannels函数来完成写入到activeChannels。
***/
Timestamp EPollPoller::poll(int timeoutMs, ChannelList* activeChannels)
{
LOG_TRACE << "fd total count " << channels_.size();
//调用epoll_wait,活跃的事件则放在events中。
int numEvents = ::epoll_wait(epollfd_,
&*events_.begin(),
static_cast<int>(events_.size()),
timeoutMs);
//保存errno
int savedErrno = errno;
//这里层层调用下去会发现其实就是调用了gettimeofday记录了下时间。
Timestamp now(Timestamp::now());
//对epoll_wait返回值判断。
if (numEvents > 0)//有活跃事件
{
LOG_TRACE << numEvents << " events happended";
/*
将记录了活跃事件成员events塞到activeChannels中,因为poll函数主要
由EventLoop类调用,传递参数,activeChannels是EventLoop类的成员变量。
*/
fillActiveChannels(numEvents, activeChannels);
if (implicit_cast<size_t>(numEvents) == events_.size())
{
events_.resize(events_.size()*2);
}
}
else if (numEvents == 0) //无活跃事件
{
LOG_TRACE << "nothing happended";
}
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());//确定它的大小小于events_的大小,因为events_是预留的事件vector
for (int i = 0; i < numEvents; ++i) //挨个处理发生的numEvents个事件,epoll模式返回的events_数组中都是已经发生额事件,这有别于select和poll
{
Channel* channel = static_cast<Channel*>(events_[i].data.ptr);
/*
这是epoll模式epoll_event事件的数据结构,其中data不仅可以保存fd,也可以保存一个void*类型的指针。
typedef union epoll_data {
void *ptr; //指向channel类变量
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; // Epoll events,//对应channel类中的revent变量
epoll_data_t data; //User data variable
};
*/
channel->set_revents(events_[i].events); //把已发生的事件传给channel,写到通道当中
activeChannels->push_back(channel); //并且push_back进activeChannels
}
}