• 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是否已经位于事件数组,从而决定后续是执行添加、修改,还是删除操作。

  • 相关阅读:
    SpringMVC引入CSS等文件
    idea运行时默认显示的index.jsp修改方法
    Spring 中的 JDBCTemplate
    Spring 错误 cvc-complex-type.2.4.c: The matching wildcard is strict, but no declaration can be found for element 'context:property-placeholder'.
    Spring IOC 三种注入方式(构造器,setter,接口)
    Java中的Object、T(泛型)、?区别
    DBUtils 笔记
    DBCP + C3P0 连接池
    Servlet+JSP 对外访问路径配置
    linux iptables使用
  • 原文地址:https://www.cnblogs.com/fortunely/p/15997621.html
Copyright © 2020-2023  润新知