muduo源码-EpollPoller.h

Posted perfy576

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了muduo源码-EpollPoller.h相关的知识,希望对你有一定的参考价值。

0 设计

EpollPoller继承自Poller,为的是能够使用epoll和poll两种io函数。(这貌似是策略模式?记不清了)

 

1 源码

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
{

// epoll 继承自poller ,主要是为了能够同时使用epoll和poll
// 这里应该算是策略模式吧
class EPollPoller : public Poller
{
  public:
    EPollPoller(EventLoop *loop);
    virtual ~EPollPoller();

    virtual Timestamp poll(int timeoutMs, ChannelList *activeChannels);
    virtual void updateChannel(Channel *channel);
    virtual void removeChannel(Channel *channel);

  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);



    int epollfd_;

    // 这个将会代替 epoll_event 的数组,使用begin()迭代器传入 wait
    // 是用来存储已经就绪的 epoll_event 的
    typedef std::vector<struct epoll_event> EventList;
    EventList events_;

    //这里是有 channels_ 成员的 是一个map,从基类中继承而来
};
}
}
#endif // MUDUO_NET_POLLER_EPOLLPOLLER_H

 

EpollPoller.cc

#include <muduo/net/poller/EPollPoller.h>

#include <muduo/base/Logging.h>
#include <muduo/net/Channel.h>

#include <boost/static_assert.hpp>

#include <assert.h>
#include <errno.h>
#include <poll.h>
#include <sys/epoll.h>
#include <unistd.h>

using namespace muduo;
using namespace muduo::net;

// boost中的BOOST_STATIC_ASSERT是在编译期的断言,也就是说在编译的时候就可以断言出错误。
// 主要是兼容
BOOST_STATIC_ASSERT(EPOLLIN == POLLIN);
BOOST_STATIC_ASSERT(EPOLLPRI == POLLPRI);
BOOST_STATIC_ASSERT(EPOLLOUT == POLLOUT);
BOOST_STATIC_ASSERT(EPOLLRDHUP == POLLRDHUP);
BOOST_STATIC_ASSERT(EPOLLERR == POLLERR);
BOOST_STATIC_ASSERT(EPOLLHUP == POLLHUP);

namespace
{

// 又是匿名空间
// 这三个值其实是 标识符,用来识别,一个channel 是新添加,还是待修改,还是之前添加过
// 其含义是是否在epoll 中被监听
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_);
}

// 实际的wait函数
// 第一个参数是超时时间,第二个参数是:所有已就绪的channel被填充到activeChannels
// 超时时间 : 0会立即返回,-1将不确定,也有说法说是永久阻塞
Timestamp EPollPoller::poll(int timeoutMs, ChannelList *activeChannels)
{
    LOG_TRACE << "fd total count " << channels_.size();

    // 注意这里, epoll_event 结构体使用的是vector,传入的begin()
    int numEvents = ::epoll_wait(epollfd_,
                                 &*events_.begin(),
                                 static_cast<int>(events_.size()),
                                 timeoutMs);
    int savedErrno = errno;
    // 记录唤醒的时间
    Timestamp now(Timestamp::now());
    // 通过 epoll_wait 返回值来判断是否有文件描述符就绪
    if (numEvents > 0)
    {
        // 当确实有就绪的时候,那么就开始 fillActiveChannels 填充到 activeChannels 
        LOG_TRACE << numEvents << " events happened";
        fillActiveChannels(numEvents, activeChannels);

        // 然后当传入的events 的空间太小的时候,也就是说,还有很多就绪的时间没有被传出来
        // 那么就扩容events
        if (implicit_cast<size_t>(numEvents) == events_.size())
        {
            events_.resize(events_.size() * 2);
        }
    }
    else if (numEvents == 0)
    {
        // 超时以后,没有返回
        LOG_TRACE << "nothing happened";
    }
    else
    {
        // 被信号中断
        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());

    // 遍历每一个就绪的 epoll_event 
    // 要知道 epoll_event 的 data.ptr 指针绑定的是文件描述符被封装的 channel 
    for (int i = 0; i < numEvents; ++i)
    {
        // 这里直接通过 data.ptr 来获得channel即可
        Channel *channel = static_cast<Channel *>(events_[i].data.ptr);

        // 下面的四句是用来判断channel 的正确性的
#ifndef NDEBUG
        int fd = channel->fd();
        ChannelMap::const_iterator it = channels_.find(fd);
        assert(it != channels_.end());
        assert(it->second == channel);
#endif
        // channel 内设置发生的事件,他就等于  epoll_event 的events
        channel->set_revents(events_[i].events);
        activeChannels->push_back(channel);
    }
}

// 更新channel 。由channel 发起,被eventloop 转发
void EPollPoller::updateChannel(Channel *channel)
{
    Poller::assertInLoopThread();
    // index 是 channel 的状态,是新添加,还是已经在监听,还是之前间听过,后来移除监听了
    const int index = channel->index();
    LOG_TRACE << "fd = " << channel->fd()
              << " events = " << channel->events() << " index = " << index;

    //   当是新的或是之前监听过,后来移除了监听
    // 两者的区别在于,新的channel 之前没有在epoll 中保存
    // 而 del 的之前在 channels_ 中保存了,但是没有被放入epoll_ctl中监听
    if (index == kNew || index == kDeleted)
    {

        int fd = channel->fd();
        if (index == kNew)
        {
            assert(channels_.find(fd) == channels_.end());
            // channels_ 是一个map 在应用层,保存了所有的 channel 指针,维护一套channel
            channels_[fd] = channel;
        }
        else // index == kDeleted
        {
            // 如果index 是del ,那么 channels_ 中有channel 的记录
            assert(channels_.find(fd) != channels_.end());
            assert(channels_[fd] == channel);
        }
        // 然后都是设置 index 表示已添加到 epoll 中
        channel->set_index(kAdded);
        // 更新个 epoll 去调用 epoll ctl
        update(EPOLL_CTL_ADD, channel);
    }
    else
    {
        // 如果是已经add 了。那么应该是要修改
        // 下面四句是验证channel 的正确性
        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);
        }
    }
}

// 移除channel
void EPollPoller::removeChannel(Channel *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());

    // 继续验证,是判断 index ,也就是channel 要移除一定不是 new,
    // 因为new 还没有添加到  channels_ 中
    int index = channel->index();
    assert(index == kAdded || index == kDeleted);
    size_t n = channels_.erase(fd);
    (void)n;
    assert(n == 1);

    // 开始正式的判断
    // 如果是 add ,那么就从epoll 中删除
    if (index == kAdded)
    {
        update(EPOLL_CTL_DEL, channel);
    }
     // 然后都是设置为new ,表示没有添加在  channels_ 中
    channel->set_index(kNew);
    // 一般这个channel 是要被析构了
}

// 更新
void EPollPoller::update(int operation, Channel *channel)
{
    // 每次都要传入,因此,每次都是新的结构体.
    struct epoll_event event;
    bzero(&event, sizeof event);
    // 读取channel中的要设置的events
    event.events = channel->events();

    // 设置 data_ptr 指向封装的结构体
    event.data.ptr = channel;
    int fd = channel->fd();

    // 打印log
    LOG_TRACE << "epoll_ctl op = " << operationToString(operation)
              << " fd = " << fd << " event = { " << channel->eventsToString() << " }";    
    // 实际的 epoll_ctl 
    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";
    }
}

 

3 channels_ 存在的意思

epoll 没有办法保存已有的所有的在监听的文件描述符,因为epoll采用增量的形式传入epoll_event的,因此之前传入的就传入了,没有办法去获取出来。

因此使用 channels_  来保存所有的 channel 也就是说所有的文件描述符。

用来在新添加和修改删除文件描述符的时候做验证。

 

以上是关于muduo源码-EpollPoller.h的主要内容,如果未能解决你的问题,请参考以下文章

(10)muduo_base库源码分析:Timestamp.cc和Timestamp.h

muduo源码分析之Acceptor

muduo源码-HttpRequest.h

Muduo网络库源码分析 EventLoop事件循环(Poller和Channel)

Muduo源码Poller类 + EpollPoller类详解

muduo源码分析之HttpServer