muduo笔记 网络库(二)I/O复用封装Poller

I/O复用使得程序能同时监听多个文件描述符,能有效提高程序性能。Linux下,实现I/O复用的系统调用主要有3个:
1)select(2);2)poll(2);3)epoll(7)。

muduo采用了2)和3),分别用PollPoller/EPollPoller对poll/epoll进行了封装,基类Poller主要用于提供统一的接口。


Poller类

先来看看基类Poller定义:

/**
* IO Multiplexing Interface
* Support poll(2), epoll(7)
*
* Only owner EventLoop IO thread can invoke it, so thread safe is not necessary.
*/
/**
* IO复用接口
* 禁止编译器生成copy构造函数和copy assignment
* 支持poll(2), epoll(7)
*/
class Poller : noncopyable
{
public:
    typedef std::vector<Channel*> ChannelList;
    explicit Poller(EventLoop* loop);
    virtual ~Poller();
    /**
     * Polls the I/O events.
     * Must be called in the loop thread.
     * poll(2) for PollPoller, epoll_wait(2) for EPollPoller
     */
    /*
     * 监听函数,根据激活的通道列表,监听指定fd的相应事件
     * 对于PollPoller会调用epoll_wait(2), 对于EPollPoller会调用poll(2)
     *
     * 返回调用完epoll_wait/poll的当前时间(Timestamp对象)
     */
    virtual Timestamp poll(int timeoutMs, ChannelList* activeChannels) = 0;

    /**
     * Update channel listened
     */
    /* 更新监听通道的事件 */
    virtual void updateChannel(Channel* channel) = 0;
    /* 删除监听通道 */
    virtual void removeChannel(Channel* channel) = 0;
    /* 判断当前Poller对象是否持有指定通道 */
    virtual bool hasChannel(Channel* channel) const;

    /* 默认创建Poller对象的类函数 */
    static Poller* newDefaultPoller(EventLoop* loop);
    /*
     * 断言所属EventLoop为当前线程.
     * 如果断言失败,将终止程序(LOG_FATAL)
     */
    void assertInLoopThread() const
    {
        ownerLoop_->assertInLoopThread();
    }
protected:
    /*
     * 该类型保存fd和需要监听的events,以及各种事件回调函数(可读/可写/错误/关闭等)
     */
    typedef std::map<int, Channel*> ChannelMap;
    // Poller don't own the Channel, so the channel must be unregister(EventLoop::removeChannel) before its dtor.
    // std::map used for speeding up to find out a channel by fd
    /* 保存所有事件的Channel,一个Channel绑定一个fd */
    ChannelMap channels_;

private:
    /*
     * 事件驱动循环, 用于调用poll监听fd事件
     */
    EventLoop* ownerLoop_;
};

只有拥有EventLoop的IO线程,才能调用EventLoop所拥有的Poller对象的接口,因此考虑Poller的线程安全不是必要的。

一个Channel对应一个fd(文件描述符),一个fd有三种事件状态:空事件(kNoneEvent),读事件(kReadEvent,即POLLIN | POLLPRI),写事件(kWriteEvent,即POLLOUT)。只有后2个,poll/epoll才会进行监听。

EventLoop会根据Poller::newDefaultPoller(),Poller对象。实际策略是根据是否设置了环境变量,来选择创建PollPoller,还是EPollPoller。

Poller *Poller::newDefaultPoller(EventLoop *loop) // static
{
    if (::getenv("MUDUO_USE_POLL")) // 如果设置了环境变量
    {
        return new PollPoller(loop);
    }
    else
    {
        return new EPollPoller(loop);
    }
    return nullptr;
}

派生类EPollPoller

EPollPoller 以epoll为核心,实现了基类Poller的virtual函数,在其中调用了epoll_create/ctl/wait等接口。poll返回后,会将就绪的fd添加到激活队列activeChannels中管理。

/**
* IO Multiplexing with epoll(7).
*/
class EPollPoller : public Poller
{
public:
    EPollPoller(EventLoop* loop);
    ~EPollPoller() override;
    /* 监听函数, 调用epoll_wait() */
    Timestamp poll(int timeoutMs, ChannelList* activeChannels) override;
    /* ADD/MOD/DEL */
    void updateChannel(Channel* channel) override;
    /* DEL */
    void removeChannel(Channel* channel) override;

private:
    /* events_数组初始大小 */
    static const int kInitEventListSize = 16;
    /* 将op(EPOLL_CTL_Add/MOD/DEL)转换成字符串 */
    static const char* operationToString(int op);
    /* poll返回后将就绪的fd添加到激活通道中activeChannels */
    void fillActiveChannels(int numEvents,
                            ChannelList* activeChannels) const;
    /* 由updateChannel/removeChannel调用,真正执行epoll_ctl()控制epoll的函数 */
    void update(int operation, Channel* channel);

    typedef std::vector<struct epoll_event> EventList;
    /* epoll文件描述符,由epoll_create返回 */
    int epollfd_;
    /* epoll事件数组,为了适配epoll_wait参数要求 */
    EventList events_;
};

muduo在实现时,创建epoll fd,并没有用epoll_create,而是用 epoll_create1。原因在于: epoll_create1在打开epoll文件描述符时,可以直接指定FD_CLOEXEC选项,相当于open时指定O_CLOSEXEC。另外,epoll_create的size参数在Linux2.6.8以后,就已经没用了(>0即可),内核会实现自动增长内部数据结构以描述监听事件。

值得一提的是,在Channel中定义了一个名为index_的成员,由Channel构造初值为0,可通过Channel::index()/set_index()访问,在不同的Poller中有不同的含义:在EPollPoller中,index_用来表示事件类型(kNew/kAdded/kDeleted);在PollPoller中的含义,到PollPoller类解析中再讲。

派生类PollPoller

PollPoller是Poller的另外一个派生类,以poll为核心,实现Poller的virtual函数,在其中调用了poll接口。

/**
* IO Multiplexing with poll(2).
*/
class PollPoller : public Poller
{
public:
    PollPoller(EventLoop* loop);
    ~PollPoller() override;
    /* 监听函数,调用poll() */
    Timestamp poll(int timeoutMs, ChannelList* activeChannels) override;
    /* ADD/MOD */
    void updateChannel(Channel* channel) override;
    /* DEL */
    void removeChannel(Channel* channel) override;

private:
    /* poll返回后将就绪的fd添加到激活通道中activeChannels */
    void fillActiveChannels(int numEvents,
                            ChannelList* activeChannels) const;
    typedef std::vector<struct pollfd> PollFdList;
    /* poll事件数组,适配poll(2)参数要求 */
    PollFdList pollfds_;
};

在PollPoller中的,用Channel::index_表示一个事件在poll事件数组(pollfds_)中的索引:如果值为-1,表明该事件尚未在事件数组中;如果值>=0,表明该事件已经在事件数组中。可以用来对Channel对应事件做标记,便于判断Channel是否已经位于事件数组,从而决定后续是执行添加、修改,还是删除操作。


muduo库其它部分解析参见:muduo库笔记汇总

posted @ 2022-03-12 16:40  明明1109  阅读(633)  评论(0编辑  收藏  举报