I/O多路轉接之select、poll、epoll

一、select

    1.select簡介

    系統提供select函數來實現多路複用輸入/輸出模型。select系統調用是用來讓我們的程序監視多個文件句柄的狀態變化的。程序會停在select這裏等待,直到被監視的文件句柄有一個或多個發生了狀態改變。

    文件句柄,其實就是一個整數,我們最熟悉的句柄是0、1、2三 個,0是標準輸入,1是標準輸出,2是標準錯誤輸出。0、1、2是整數表示的,對應的FILE * 結構的表示就是stdin、stdout、stderr。

     2.select函數

wKiom1ecbonDX992AAB19BbJvGg304.jpg-wh_50

    (1)參數nfds是需要監視的最大的文件描述符值+1; 

    (2)rdset,wrset,exset分別對應於需要檢測的可讀文件描述符的集合,可寫文件描述符的集合及異常文件描述符的集合。 

    (3)參數timeout爲結構timeval,用來設置select()的等待時間。

    struct timeval結構用於描述一段時間長度,如果在這個時間內,需要監視的描述符沒有事件發生則函數返回,返回值爲0。

    wKioL1ecb8qzk0_PAAAV56zazIQ213.jpg-wh_50

     (4)FD_CLR(inr fd,fd_set* set);用來清除描述詞組set中相關fd 的位。 

         FD_ISSET(int fd,fd_set *set);用來測試描述詞組set中相關fd 的位是否爲真。

         FD_SET(int fd,fd_set*set);用來設置描述詞組set中相關fd的位。 

         FD_ZERO(fd_set *set);用來清除描述詞組set的全部位。 

二、poll

    不同與select使用三個位圖來表示三個fdset的方式,poll使用一個 pollfd的指針實現。 

    wKioL1eccpLgPAJMAAB5fY5gmoI219.jpg-wh_50

    pollfd結構包含了要監視的event和發生的event,不再使用select“參數-值”傳遞的方式。同時, pollfd並沒有最大數量限制(但是數量過大後性能也是會下降)。和select函數一樣,poll返回後,需要輪詢pollfd來獲取就緒的描述符。

三、epoll

    epoll是爲處理大批量句柄而作了改進的poll。當然,這不是 2.6內核纔有的,它是在2.5.44內核中被引進的(epoll(4) is a new API introduced in Linux kernel 2.5.44),它幾乎具備了之前所說的一切優點,被公認爲Linux2.6下性能最好的多路I/O就緒通知方法。  

    epoll只有epoll_create,epoll_ctl,epoll_wait 3個系統調用。  

    1.int epoll_create(int size); 

    創建一個epoll的句柄。自從linux2.6.8之後,size參數是被忽略的。需要注意的是,當創建好epoll句柄後,它就是會佔用一個fd值,在linux下如果查看/proc/進程id/fd/,是能夠看到這個fd的,所以在使用完epoll後,必須調用close()關閉,否則可能導致fd被耗盡。 

    2.int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

    epoll的事件註冊函數,它不同於select()是在監聽事件時告訴內核要監聽什麼類型的事件,而是在這裏先註冊要監聽的事件類型。

    (1)第一個參數是epoll_create()的返回值。 

    (2)第二個參數表示動作,用三個宏來表示: 

    EPOLL_CTL_ADD:註冊新的fd到epfd中;

    EPOLL_CTL_MOD:修改已經註冊的fd的監聽事;

    EPOLL_CTL_DEL:從epfd中刪除一個fd; 

    (3)第三個參數是需要監聽的fd。

    (4) 第四個參數是告訴內核需要監聽什麼事,struct epoll_event結構如下:

    wKioL1ecdsHC0wi5AAA0Ku1Oe_E425.jpg-wh_50

    EPOLLIN :表示對應的文件描述符可以讀(包括對端SOCKET正常關閉); 

    EPOLLOUT:表示對應的文件描述符可以寫; 

    EPOLLPRI:表示對應的文件描述符有緊急的數據可讀(這裏應該表示有帶外數據到來); 

    EPOLLERR:表示對應的文件描述符發生錯誤; 

    EPOLLHUP:表示對應的文件描述符被掛斷; 

    EPOLLET: 將EPOLL設爲邊緣觸發(Edge Triggered)模式,這是相對於水平觸發(Level Triggered)來說的;

    在將RFD添加到epoll描述符的時候使用了EPOLLET標誌,那麼在調用 epoll_wait之後將有可能會掛起,因爲剩餘的數據還存在於文件的輸入緩衝區內,而且數據發出端還在等待一個針對已經發出數據的反饋信息。只有在監視的文件句柄上發生了某個事件的時候 ET工作模式纔會彙報事件。因此在調用epoll_wait之後,調用者可能會放棄等待仍在存在於文件輸入緩衝區內的剩餘數據。epoll工作在ET模式的時候,必須使用非阻塞套接口,以避免由於一個文件 句柄的阻塞讀/阻塞寫操作把處理多個文件描述符的任務餓死。

    以LT方式調用epoll接口的時候,它就相當於一個速度比較快的poll,並且無論後面的數據是否被使用,因此他們具有同樣的職能。因爲即使使用ET模式的epoll,在收到多 個chunk的數據的時候仍然會產生多個事件。調用者可以設定EPOLLONESHOT標誌,在 epoll_wait收到事件後epoll會與事件關聯的文件句柄從epoll描述符中禁止掉。因此當 EPOLLONESHOT設定後,使用帶有 EPOLL_CTL_MOD標誌的epoll_ctl處理文件句柄就 成爲調用者必須作的事情。  

    EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之後,如果還需要繼續監聽這個 socket的話,需要再次把這個socket加入到EPOLL隊列裏。

    3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);     

    收集在epoll監控的事件中已經發送的事件。參數events是分配好的epoll_event結構體數組,epoll將會把發生的事件賦值到events數組中(events不可以是空指針,內核只負責把數據復 制到這個events數組中,不會去幫助我們在用戶態中分配內存)。maxevents告之內核這個 events有多大,這個 maxevents的值不能大於創建epoll_create()時的size,參數timeout是超時 時間(毫秒,0會立即返回,-1將不確定,也有說法說是永久阻塞)。如果函數調用成功,返回對應I/O上已準備好的文件描述符數目,如返回0表示已超時。     

四、select、poll、epoll的比較

    每次調用select,都需要把fd集合從用戶態拷貝到內核態,這個開銷在fd很多時會很大;同時每次調用select都需要在內核遍歷傳遞進來的所有fd,這個開銷在fd很多時也很大;select支持的文件描述符數量太小了,默認是1024。 

    select和poll都需要在返回後,通過遍歷文件描述符來獲取已經就緒的socket。事實上,同時連接的大量客戶端在一時刻可能只有很少的處於就緒狀態,因此隨着監視的描 述符數量的增長,其效率也會線性下降。

    epoll支持一個進程打開大數目的socket描述符(FD);.IO效率不隨FD數目增加而線性下降;.使用mmap加速內核與用戶空間的消息傳遞。

    select/poll每次調用時都要傳遞。你所要監控的所有socket給select/poll系統調用,這意味着需要將用戶態的socket列表copy到內核態,如果以萬計的句柄會導致每次都要copy幾十幾百KB的內存到內核態,非常低效。而我們調用epoll_wait時就相當於以往調用select/poll,但是這時卻不用傳遞socket句柄給內核,因爲內核已經在epoll_ctl中拿到了要監控的句柄列表。 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章