muduo库-I/O复用
I/O复用使得程序能同时监听多个文件描述符,能有效提高程序性能。Linux下,实现I/O复用的系统调用主要有3个:
select
poll
epoll
muduo采用了2和3,分别用PollPoller/EPollPoller对poll/epoll进行了封装,基类Poller主要用于提供统一的接口。
Poller类
先来看看基类Poller定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 class Poller : noncopyable {public : typedef std::vector<Channel*> ChannelList; explicit Poller (EventLoop* loop) ; virtual ~Poller (); virtual Timestamp poll (int timeoutMs, ChannelList* activeChannels) = 0 ; virtual void updateChannel (Channel* channel) = 0 ; virtual void removeChannel (Channel* channel) = 0 ; virtual bool hasChannel (Channel* channel) const ; static Poller* newDefaultPoller (EventLoop* loop) ; void assertInLoopThread () const { ownerLoop_->assertInLoopThread (); }protected : typedef std::map<int , Channel*> ChannelMap; ChannelMap channels_;private : EventLoop* ownerLoop_; };
每个EventLoop类都有一个Poller派生类的实例化对象,该对象的所有操作都在同一个IO线程完成的,不存在多线程抢占的问题,只有拥有EventLoop的IO线程,才能调用EventLoop所拥有的Poller对象的接口。因此只需要判断当前执行线程是否在EventLoop对象的创建线程即可,所以考虑Poller的线程安全不是必要的。
一个Channel对应一个fd(文件描述符),一个fd有三种事件状态:空事件(kNoneEvent),读事件(kReadEvent,即POLLIN
|
POLLPRI),写事件(kWriteEvent,即POLLOUT)。只有后2个,poll/epoll才会进行监听。
EventLoop会根据Poller::newDefaultPoller(),Poller对象。实际策略是根据是否设置了环境变量,来选择创建PollPoller,还是EPollPoller。
1 2 3 4 5 6 7 8 9 10 11 12 Poller *Poller::newDefaultPoller (EventLoop *loop) { 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中管理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class EPollPoller : public Poller {public : EPollPoller (EventLoop* loop); ~EPollPoller () override ; Timestamp poll (int timeoutMs, ChannelList* activeChannels) override ; void updateChannel (Channel* channel) override ; void removeChannel (Channel* channel) override ;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) ; typedef std::vector<struct epoll_event> EventList; int epollfd_; 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接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class PollPoller : public Poller {public : PollPoller (EventLoop* loop); ~PollPoller () override ; Timestamp poll (int timeoutMs, ChannelList* activeChannels) override ; void updateChannel (Channel* channel) override ; void removeChannel (Channel* channel) override ;private : void fillActiveChannels (int numEvents, ChannelList* activeChannels) const ; typedef std::vector<struct pollfd> PollFdList; PollFdList pollfds_; };
在PollPoller中的index_
表示一个事件在poll事件数组(pollfds_)中的索引:如果值为-1,表明该事件尚未在事件数组中;如果值>=0,表明该事件已经在事件数组中。可以用来对Channel对应事件做标记,便于判断Channel是否已经位于事件数组,从而决定后续是执行添加、修改,还是删除操作。
参考
转载自muduo笔记
网络库(二)I/O复用封装Poller