Poller class 是IO multiplexing的封裝。在muduo中它是一個抽象類,由於muduo同時支持poll和epoll兩種IO multiplexing機制。Poller是EventLoop的間接成員,只供其owner EventLoop在IO線程中調用,所以無需加鎖。其生命週期和EvenLoop相等。Poller並不擁有Channel,Channel在析構前必須本身unregister(EventLoop::removeChannel()),避免懸空指針。api
Poller.h只是一個簡單的抽象類,簡單分析一下源碼數組
// Copyright 2010, Shuo Chen. All rights reserved. // http://code.google.com/p/muduo/ // // Use of this source code is governed by a BSD-style license // that can be found in the License file. // Author: Shuo Chen (chenshuo at chenshuo dot com) // // This is an internal header file, you should not include this. #ifndef MUDUO_NET_POLLER_H #define MUDUO_NET_POLLER_H #include <vector> #include <boost/noncopyable.hpp> #include <muduo/base/Timestamp.h> #include <muduo/net/EventLoop.h> namespace muduo { namespace net { class Channel; /// /// Base class for IO Multiplexing /// /// This class doesn't own the Channel objects. //這個Poller類只是一個抽象類主要實如今EpollPoller和PollPoller中 class Poller : boost::noncopyable { public: typedef std::vector<Channel *> ChannelList; Poller(EventLoop *loop); virtual ~Poller(); /// Polls the I/O events. /// Must be called in the loop thread. virtual Timestamp poll(int timeoutMs, ChannelList *activeChannels) = 0; // poll函數 /// Changes the interested I/O events. /// Must be called in the loop thread. virtual void updateChannel(Channel *channel) = 0;// 更新Channel, Channel是一個對文件描述符封裝後的類 /// Remove the channel, when it destructs. /// Must be called in the loop thread. virtual void removeChannel(Channel *channel) = 0; // 移除Channel static Poller *newDefaultPoller(EventLoop *loop);// 在這裏會選擇epoll或者poll void assertInLoopThread() {// 確保全部的操做都在事件循環的線程中 ownerLoop_->assertInLoopThread(); } private: EventLoop *ownerLoop_; // Poller所屬EventLoop }; } } #endif // MUDUO_NET_POLLER_H
這裏是一個選擇器, 根據系統環境不一樣而選擇epoll或者poll. 由於如今的Linux環境基本都支持epoll, 因此咱們在此只關注Epollpoller的實現。(主要是由於我只用用epoll,沒有使用過poll)數據結構
// Copyright 2010, Shuo Chen. All rights reserved. // http://code.google.com/p/muduo/ // // Use of this source code is governed by a BSD-style license // that can be found in the License file. // Author: Shuo Chen (chenshuo at chenshuo dot com) /*動態生成一個PollPoller類或者EPollPoller類變量*/ #include <muduo/net/Poller.h> #include <muduo/net/poller/PollPoller.h> #include <muduo/net/poller/EPollPoller.h> #include <stdlib.h> using namespace muduo::net; Poller *Poller::newDefaultPoller(EventLoop *loop) { if (::getenv("MUDUO_USE_POLL"))//若是在環境變量中找到MUDUO_USE_POLL這一項,就返回PollPoller類,不然返回EPollPoller類 { return new PollPoller(loop); } else { return new EPollPoller(loop); } }
涉及到IO多路複用就順便講講原理,就當複習一遍。併發
由於poll原理和select基本同樣只是用鏈表存儲,在這裏就直接分析select原理。app
調用select時,會發生如下事情:socket
1. 從用戶空間拷貝fd_set到內核空間;函數
2. 註冊回調函數__pollwait;高併發
3. 遍歷全部fd,對所有指定設備作一次poll(這裏的poll是一個文件操做,它有兩個參數,一個是文件fd自己,一個是當設備還沒有就緒時調用的回調函數__pollwait,這個函數把設備本身特有的等待隊列傳給內核,讓內核把當前的進程掛載到其中);oop
4. 當設備就緒時,設備就會喚醒在本身特有等待隊列中的【全部】節點,因而當前進程就獲取到了完成的信號。poll文件操做返回的是一組標準的掩碼,其中的各個位指示當前的不一樣的就緒狀態(全0爲沒有任何事件觸發),根據mask可對fd_set賦值;ui
5. 若是全部設備返回的掩碼都沒有顯示任何的事件觸發,就去掉回調函數的函數指針,進入有限時的睡眠狀態,再恢復和不斷作poll,再做有限時的睡眠,直到其中一個設備有事件觸發爲止。
6. 只要有事件觸發,系統調用返回,將fd_set從內核空間拷貝到用戶空間,回到用戶態,用戶就能夠對相關的fd做進一步的讀或者寫操做了。
調用epoll_create時,作了如下事情:
內核幫咱們在epoll文件系統裏建了個file結點;
在內核cache裏建了個紅黑樹用於存儲之後epoll_ctl傳來的socket;
創建一個list鏈表,用於存儲準備就緒的事件。
調用epoll_ctl時,作了如下事情:
把socket放到epoll文件系統裏file對象對應的紅黑樹上;
給內核中斷處理程序註冊一個回調函數,告訴內核,若是這個句柄的中斷到了,就把它放到準備就緒list鏈表裏。
調用epoll_wait時,作了如下事情:
觀察list鏈表裏有沒有數據。有數據就返回,沒有數據就sleep,等到timeout時間到後即便鏈表沒數據也返回。並且,一般狀況下即便咱們要監控百萬計的句柄,大多一次也只返回不多量的準備就緒句柄而已,因此,epoll_wait僅須要從內核態copy少許的句柄到用戶態而已。
總結以下:
一顆紅黑樹,一張準備就緒句柄鏈表,少許的內核cache,解決了大併發下的socket處理問題。
執行epoll_create時,建立了紅黑樹和就緒鏈表;
執行epoll_ctl時,若是增長socket句柄,則檢查在紅黑樹中是否存在,存在當即返回,不存在則添加到樹幹上,而後向內核註冊回調函數,用於當中斷事件來臨時向準備就緒鏈表中插入數據;
執行epoll_wait時馬上返回準備就緒鏈表裏的數據便可。
兩種模式的區別:
LT模式下,只要一個句柄上的事件一次沒有處理完,會在之後調用epoll_wait時重複返回這個句柄,而ET模式僅在第一次返回。
兩種模式的實現:
若是是ET模式,當一個socket句柄上有事件時,內核會把該句柄插入上面所說的準備就緒list鏈表,這時咱們調用epoll_wait,會把準備就緒的socket拷貝到用戶態內存,而後清空準備就緒list鏈表,最後,epoll_wait檢查這些socket,若是是LT模式,而且這些socket上確實有未處理的事件時,又把該句柄放回到剛剛清空的準備就緒鏈表。因此,LT模式的句柄,只要它上面還有事件,epoll_wait每次都會返回。
select缺點:
最大併發數限制:使用32個整數的32位,即32*32=1024來標識fd,雖然可修改,可是有如下第二點的瓶頸;
效率低:每次都會線性掃描整個fd_set,集合越大速度越慢;
內核/用戶空間內存拷貝問題。
epoll的提高:
自己沒有最大併發鏈接的限制,僅受系統中進程能打開的最大文件數目限制;
效率提高:只有活躍的socket纔會主動的去調用callback函數;
網上不少博客說epoll使用了共享內存,這個是徹底錯誤的 ,能夠閱讀源碼,會發現徹底沒有使用共享內存的任何api,而是 使用了copy_from_user跟__put_user進行內核跟用戶虛擬空間數據交互.
固然,以上的優缺點僅僅是特定場景下的狀況:高併發,且任一時間只有少數socket是活躍的。
若是在併發量低,socket都比較活躍的狀況下,select就不見得比epoll慢了(就像咱們經常說快排比插入排序快,可是在特定狀況下這並不成立)。
1.這個類主要利用epoll函數,封裝了epoll三個函數,
2.其中epoll_event.data是一個指向channel類的指針,這裏能夠等價理解爲channel就是epoll_event,用於在epoll隊列中註冊,刪除,更改的結構體。由於文件描述符fd,Channel,以及epoll_event結構體(只有須要添加到epoll上時纔有epoll_event結構體)三個都是一一對應的關係Channel.fd應該等於fd,epoll_event.data應該等於&Channel。若是不添加到epoll隊列中,Channel和fd一一對應,就沒有epoll_event結構體了
3.從epoll隊列中刪除有兩種刪除方法,
第一種暫時刪除,就是從epoll隊列中刪除,而且把標誌位置爲kDeleted,可是並不從ChannelMap channels_中刪除
第二種是徹底刪除,從epoll隊列中刪除,而且從ChannelMap channels_中也刪除,最後把標誌位置kNew。
能夠理解爲ChannelMap channels_的做用就是:暫時不須要的,就從epoll隊列中刪除,可是在channels_中保留信息,相似與掛起,這樣下次再使用這個channel時,只須要添加到epoll隊列中便可。而徹底刪除,就把channels_中也刪除。
下面的源碼有詳細的註釋
// Copyright 2010, Shuo Chen. All rights reserved. // http://code.google.com/p/muduo/ // // Use of this source code is governed by a BSD-style license // that can be found in the License file. // Author: Shuo Chen (chenshuo at chenshuo dot com) // // This is an internal header file, you should not include this. /*1.這個類主要利用epoll函數,封裝了epoll三個函數, *2.其中epoll_event.data是一個指向channel類的指針 *這裏能夠等價理解爲channel就是epoll_event,用於在epoll隊列中註冊,刪除,更改的結構體 *由於文件描述符fd,Channel,以及epoll_event結構體(只有須要添加到epoll上時纔有epoll_event結構體) *三個都是一一對應的關係Channel.fd應該等於fd,epoll_event.data應該等於&Channel *若是不添加到epoll隊列中,Channel和fd一一對應,就沒有epoll_event結構體了 *3.從epoll隊列中刪除有兩種刪除方法, *第一種暫時刪除,就是從epoll隊列中刪除,而且把標誌位置爲kDeleted,可是並不從ChannelMap channels_中刪除 *第二種是徹底刪除,從epoll隊列中刪除,而且從ChannelMap channels_中也刪除,最後把標誌位置kNew *能夠理解爲ChannelMap channels_的做用就是:暫時不須要的,就從epoll隊列中刪除,可是在channels_中保留信息,相似與掛起,這樣 *下次再使用這個channel時,只須要添加到epoll隊列中便可。而徹底刪除,就把channels_中也刪除。 */ #ifndef MUDUO_NET_POLLER_EPOLLPOLLER_H #define MUDUO_NET_POLLER_EPOLLPOLLER_H #include <muduo/net/Poller.h> #include <map> #include <vector> struct epoll_event; namespace muduo { namespace net { /// /// IO Multiplexing with epoll(4). /// 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; //默認事件數組大小,是用來裝epoll_wait()返回的可讀或可寫事件的 void fillActiveChannels(int numEvents, ChannelList *activeChannels) const; void update(int operation, Channel *channel); typedef std::vector<struct epoll_event> EventList; typedef std::map<int, Channel *> ChannelMap; int epollfd_;//epoll監視的文件描述符 EventList events_;//用來存儲活躍文件描述符的epoll_event結構體數組 ChannelMap channels_;//記錄標誌符是kAdded或者kDeleted的channel和fd }; } } #endif // MUDUO_NET_POLLER_EPOLLPOLLER_H
主要是一些EPollPoller類的具體實現,註釋很詳細。
// Copyright 2010, Shuo Chen. All rights reserved. // http://code.google.com/p/muduo/ // // Use of this source code is governed by a BSD-style license // that can be found in the License file. // Author: Shuo Chen (chenshuo at chenshuo dot com) #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> using namespace muduo; using namespace muduo::net; // On Linux, the constants of poll(2) and epoll(4) // are expected to be the same. 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 { const int kNew = -1;//表明不在epoll隊列中,也不在ChannelMap channels_中 const int kAdded = 1;//表明正在epoll隊列當中 const int kDeleted = 2;//表明曾經在epoll隊列當中過,可是被刪除了,如今不在了,可是仍是在ChannelMap channels_中的 } EPollPoller::EPollPoller(EventLoop *loop) : Poller(loop),//所屬的EventLoop epollfd_(::epoll_create1(EPOLL_CLOEXEC)),//建立一個epoll文件描述符,用來監聽全部註冊的了事件 events_(kInitEventListSize) {//vector這樣用時初始化kInitEventListSize個大小空間 if (epollfd_ < 0) { LOG_SYSFATAL << "EPollPoller::EPollPoller"; } } EPollPoller::~EPollPoller()//關閉epoll文件描述符 { ::close(epollfd_); } Timestamp EPollPoller::poll(int timeoutMs, ChannelList *activeChannels)//阻塞等待事件的發生,而且在發生後進行相關的處理 { int numEvents = ::epoll_wait(epollfd_, &*events_.begin(),//等價於&events[0],就是傳入一個vecotr<struct epoll_event>的首指針進去 static_cast<int>(events_.size()), timeoutMs);//numEvents是活躍的文件描述符個數,就是待處理的文件描述符 Timestamp now(Timestamp::now()); if (numEvents > 0) { LOG_TRACE << numEvents << " events happended"; fillActiveChannels(numEvents, activeChannels); //若是返回的事件數目等於當前事件數組大小,就分配2倍空間, // 沒必要擔憂vector的大小問題了,後續會以乘以2倍的方式分配,這也是內存分配的常見作法。 if (implicit_cast<size_t>(numEvents) == events_.size())//若是活躍的文件符個數和存儲活躍文件描述符的容量同樣,就擴充events_ { events_.resize(events_.size() * 2); } } else if (numEvents == 0)//若是timeoutMs設置的是大於0的數,也就是超時時間有效的話,那麼過了超時時間而且沒有事件發生,就會出現這種狀況 { LOG_TRACE << " nothing happended"; } else { LOG_SYSERR << "EPollPoller::poll()"; } return now;//返回的是事件發生時的時間 } void EPollPoller::fillActiveChannels(int numEvents, ChannelList *activeChannels) const//就是把須要處理的channel放到一個活躍channel列表中 { assert(implicit_cast<size_t>(numEvents) <= events_.size());//若是活躍的文件描述符個數大於活躍的文件描述符的容器個數,說明出錯了,因此終止 for (int i = 0; i < numEvents; ++i)//將全部的活躍channel放到activeChannels列表中 { Channel *channel = static_cast<Channel *>(events_[i].data.ptr);//把產生事件的channel變量拿出來 /* 這是epoll模式epoll_event事件的數據結構,其中data不只能夠保存fd,也能夠保存一個void*類型的指針。 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 }; */ #ifndef NDEBUG//在調試時會執行下面的代碼,不然就直接忽視 int fd = channel->fd(); ChannelMap::const_iterator it = channels_.find(fd); assert(it != channels_.end()); assert(it->second == channel);//判斷ChannelMap中key和value的對應關係是否準確 #endif channel->set_revents(events_[i].events);//把已經觸發的事件寫入channel中 activeChannels->push_back(channel);//把channel放入要處理的channel列表中 } } void EPollPoller::updateChannel(Channel *channel)//根據channel的序號在epoll隊列中來刪除,增長channel或者改變channel { Poller::assertInLoopThread();//負責epoll_wait的線程和建立eventloop的線程爲同一個 LOG_TRACE << "fd = " << channel->fd() << " events = " << channel->events(); const int index = channel->index(); if (index == kNew || index == kDeleted)//若是是徹底沒在或者曾經在epoll隊列中的,就添加到epoll隊列中 { // a new one, add with EPOLL_CTL_ADD int fd = channel->fd(); if (index == kNew) {//徹底沒在epoll隊列中 assert(channels_.find(fd) == channels_.end());//確保這個channel的文件描述符不在channels_中 channels_[fd] = channel;//將新添加的fd和channel添加到channels_中 } else // index == kDeleted 曾經在epoll隊列中 { assert(channels_.find(fd) != channels_.end());//確保這個channel的文件描述符在channels_中 assert(channels_[fd] == channel);//確保在epoll隊列中channel和fd一致 } channel->set_index(kAdded);//修改index爲已在隊列中 update(EPOLL_CTL_ADD, channel); } else//若是是如今就在epoll隊列中的,若是沒有關注事件了,就暫時刪除,若是有關注事件,就修改 { // update existing one with EPOLL_CTL_MOD/DEL int fd = channel->fd(); (void) fd; assert(channels_.find(fd) != channels_.end());//channels_中是否有這個文件描述符 assert(channels_[fd] == channel);//channels_中channel和fd是否一致 assert(index == kAdded);//標誌位是否正在隊列中 if (channel->isNoneEvent()) { update(EPOLL_CTL_DEL, channel); channel->set_index(kDeleted); } else { update(EPOLL_CTL_MOD, channel); } } } void EPollPoller::removeChannel(Channel *channel)//徹底刪除channel { Poller::assertInLoopThread();//???暫時不明白爲何要這麼判斷,也就是負責epoll管理的線程和建立eventloop的線程爲同一個 int fd = channel->fd(); LOG_TRACE << "fd = " << fd; assert(channels_.find(fd) != channels_.end());//channels_中是否有這個文件描述符 assert(channels_[fd] == channel);//channels_中channel和fd是否一致 assert(channel->isNoneEvent());//channel中要關注的事件是否爲空 int index = channel->index(); assert(index == kAdded || index == kDeleted);//標誌位必須是kAdded或者kDeleted size_t n = channels_.erase(fd); (void) n; assert(n == 1); if (index == kAdded) { update(EPOLL_CTL_DEL, channel);//從epoll隊列中刪除這個channel } channel->set_index(kNew);//設置標誌位是kNew,至關於徹底刪除 } void EPollPoller::update(int operation, Channel *channel)//主要執行epoll_ctl函數 { struct epoll_event event; bzero(&event, sizeof event); event.events = channel->events(); event.data.ptr = channel;//設置epoll_event結構體 int fd = channel->fd(); if (::epoll_ctl(epollfd_, operation, fd, &event) < 0) { if (operation == EPOLL_CTL_DEL) { LOG_SYSERR << "epoll_ctl op=" << operation << " fd=" << fd; } else { LOG_SYSFATAL << "epoll_ctl op=" << operation << " fd=" << fd; } } }