muduo记录2:muduo EPollPoller源码学习

muduo EPollPoller源码学习

一 Channel怎么update到epollfd

EPollPoller::update(),这个方法的层层调用关系:

假设acceptor或者TcpConnection对象刚刚创建,第一次调用了自己的channel成员的enableReading();channel因此更新表示关注事件的events_,然后update();

Channel::update()转而执行EventLoop::updateChannel();

EventLoop::updateChannel()转而执行EpollPoller::updateChannel();

EpollPoller::updateChannel()转而执行EpollPoller::update(int operation, Channel *channel)

在上面的一层层调用中,由于对应的channel刚刚创建没加入poller中,所以首先到了EpollPoller::updateChannel里面会先把channel的fd,指针插入当前poller的channel表里,更新channel的index状态为kAdded,表示已注册,紧接着以EPOLL_CTL_ADD作为operation的实参调用update()。在update里面,既然是enableReading()那么根据channel的events_,终于在这里把EPOLLIN|EPOLLPRI事件,channel的fd,channel指针正式注册进epollfd_。

void EpollPoller::update(int operation, Channel *channel)
{
    epoll_event event;
    bzero(&event, sizeof event);

    int fd = channel->fd();

    event.events = channel->events();
    event.data.fd = fd;
    event.data.ptr = channel;

    if (::epoll_ctl(epollfd_, operation, fd, &event) < 0)
    {
        if (operation == EPOLL_CTL_DEL)
        {
            LOG_ERROR << "epoll_ctl del error:" << errno;
        }
        else
        {
            LOG_FATAL << "epoll_ctl add/mod error:" << errno;
        }
    }
}

二 EpollPoller对epoll_wait的封装

muduo的EPollPoller封装了epoll的相关方法,包括epoll的动态扩容,其中poll用于调用epoll_wait(),当事件到来,poll可以通过epoll_wait()更新已发生事件的channel列表activeChannels,这个activeChannels在EPollPoller中被封装为vector<Channel*>的数组。

//EPollPoller::poll()中调用epoll_wait的语句
int numEvents = ::epoll_wait(epollfd_, &*events_.begin(), static_cast<int>(events_.size()), timeoutMs);

//man page中epoll_wait的函数原型
 int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);

&*events_.begin()是传出参数,是epoll_event类型的vector数组,通过数组大小来轮询检查事件

以下为epoll_event在内核中的定义

typedef union epoll_data
{
  void *ptr;
  int fd;
  uint32_t u32;
  uint64_t u64;
} epoll_data_t;

struct epoll_event
{
  uint32_t events;	/* Epoll events */
  epoll_data_t data;	/* User data variable */
} __EPOLL_PACKED;

可以看到epoll_event是一个结构体,内部的信息记录了感兴趣的事件和发生待处理的事件,这是一个用于传出的参数。当事件来了,epoll_wait()把发生事件的fd和指向对应channel的指针包装进来,而后通过fillActiveChannels方法把上述events_数组的相关信息逐个填入activeChannels,当loop()函数里poll()返回时,所有已发生事件都在activeChannels里,loop()中依次对这些事件调用相关回调函数就好了。

Timestamp EpollPoller::poll(int timeoutMs, ChannelList *activeChannels)
{
    LOG_DEBUG << "func=" << __FUNCTION__ << "=> fd total count:" << channels_.size();

    int numEvents = ::epoll_wait(epollfd_, &*events_.begin(), static_cast<int>(events_.size()), timeoutMs);
    int saveErrno = errno;
    Timestamp now(Timestamp::now());

    if (numEvents > 0)
    {
        LOG_DEBUG << "events happened" << numEvents;
        fillActiveChannels(numEvents, activeChannels);
        if (numEvents == events_.size())
        {
            events_.resize(events_.size() * 2); // events_做成vector,所以可以动态扩容
        }
    }
    else if (numEvents == 0)
    {
        LOG_DEBUG << "func=" << __FUNCTION__ << "timeout!";
    }
    else
    {
        if (saveErrno != EINTR)
        {
            errno = saveErrno;
            LOG_ERROR << "EPollPoller::poll() error:" << errno;
        }
    }
    return now;
}
上一篇:史上最牛mysql-11 (增删改)


下一篇:SpringMVC整合