1. 程式人生 > >select、poll和epoll

select、poll和epoll

time 應用 使用場合 seconds const 方式 文件描述符 div inux

I/O復用:

  在一個進程或者多個進程的需要多個I/O,不能阻塞在一個I/O上而停止不前,而是用到I/O復用。進程預先告知內核需要哪些I/O描述符,內核一旦發現指定的一個或多個I/O條件就緒,則通知進程進行相應操作,這就是I/O復用。

使用場合:

1、客戶處理多個描述符(交互式輸入和網絡套接字)

2、TCP服務器既處理監聽套接字,又處理連接套接字

3、一服務器既處理TCP又處理UDP

4、一服務器要處理多個服務或多個協議

select函數:

允許進程指示內核等待多個事件中的任何一個發生,且只在有一個或多個事件發生或經歷一段指定的時間後才喚醒它。

其中:maxfdp1表示探測描述符中的最大值加一(因為描述符從0開始,其表示個數),後面三個參數依次表示讀、寫和異常描述符集,最後一個表示等待時間。timeout:NULL 永遠等待;正數,等待一段時間後返回;0,不等待,檢查描述符後立即返回。

#include <sys/select.h>
#include <sys/time.h>
int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);  
                                              返回:若有就緒描述符則為其數目,若超時為0,出錯為-1

struct timeval
{
   long tv_sec;  //seconds
   long tv_usec;      //
microseconds }; void FD_ZERO(fd_set *fdset); //clear all bits in fdset void FD_SET(int fd, fd_set *fdset); //trun on the bit in fdset void FD_CLR(int fd, fd_set *fdset);  //turn off the bit for fd in fdset void FD_ISSET(int fd, fd_set *fdset);  //is the bit for fd on in fdset

poll函數

  與select函數大致相同,不同在於select描述符最大個數FD_SETSIZE,poll可更大。且傳遞的結構不同,poll對每個描述符管理起來,select分別用三個數組管理起來。timeout:INFTIM永遠等待,0立即返回,正數等待指定毫秒數返回。

#include <poll.h>
int poll(struct pollfd *fdarray, unsigned long nfds, int timeout);  //返回:若有就需描述符則為其數目,超時為0,出錯為-1
struct pollfd
{
  int fd;  //descriptor to check
  short events;  //events of interest on fd
  short revents;  //events that pccurred on fd
};

總結:參考http://www.open-open.com/lib/view/open1410403215664.html#articleHeader0

select缺點:1、單進程可監視文件描述符最大限制1024個,可更改。但select采用輪詢方式掃描文件描述符,文件描述符數量越多性能越差(Linux內核中:#define _FD_SETSIZE 1024)

2、內核、用戶空間內存拷貝,select需要賦值大量的句柄數據結構,產生巨大開銷;

3、select返回整個句柄數組,應用程序需要遍歷數組查找就緒文件描述符;

4、select水平觸發,應用程序如果沒有完成對一個已經就緒的文件描述符進行IO操作,那麽之後每次select調用還是會將其桃枝進程

poll:相比select只是數據結構發生變化,用一個結構體數組來表示監視的文件描述符,每一個結構存儲監視的文件描述符和其監視事件,並在其中返回監視結果。其監視文件數量沒有限制。但是其他缺點和select一樣。

例如:服務器需要支持100萬並發連接,在_FD_SETSIZE為1024的情況下,我們至少需要創建1K歌進程才能實現100萬的並發連接,除進程間上下文切換的時間開銷,從內核、用戶空間的內存拷貝,數組輪詢等都是系統難以承受和實現的。因此基於select模型的服務器,要達到10萬級別的並發訪問控制,是很難完成的。

epoll

  就上面例子中,select/poll都是服務器進程每次都把這100萬個連接告訴操作系統(從用戶賦值句柄數據結構到內核),讓操作系統內核查詢這些套接字上是否有事件發生,該過程資源消耗較大,因此select/poll一般只能處理幾千的並發連接。

epoll的設計和實現與select完全不同。epoll通過Linux內核中申請一個建議的文件系統(B+樹),吧原先的select/poll分為:

1、epoll_creat()簡歷一個epoll對象(epoll文件系統中為這個句柄對象分配資源)

2、epoll_ctl向epoll對象中添加監視的描述符;

3、epoll_wait收集發生的事件的連接;

epoll實現思路:

  當某一進程調用epoll_creat方法,Linux內核會創建一個eventpoll結構體,這個結構體中有兩個成員與epoll的使用方式密切相關

struct eventpoll{
    ....
    /*紅黑樹的根節點,這顆樹中存儲著所有添加到epoll中的需要監控的事件*/
    struct rb_root  rbr;
    /*雙鏈表中則存放著將要通過epoll_wait返回給用戶的滿足條件的事件*/
    struct list_head rdlist;
    ....
};

  每一個epoll對象都有一個eventpoll結構體,用於存放通過epoll_ctl方法將epoll對象中添加進來的事件。這些事件都會掛載在紅黑樹中,如此重復添加的事件也可以通過紅黑樹而高效的識別出來。

  所有添加到epoll中的事件都會與設備(網卡)驅動程序簡歷回調關系,當相應的事件發生時會調用這個回調方法。該回調方法在內核中叫ep_poll_callback,它將發生的事件添加到rdlist雙鏈表中。

對於每一個事件都會建立epitem結構體:

struct epitem{
    struct rb_node  rbn;//紅黑樹節點
    struct list_head    rdllink;//雙向鏈表節點
    struct epoll_filefd  ffd;  //事件句柄信息
    struct eventpoll *ep;    //指向其所屬的eventpoll對象
    struct epoll_event event; //期待發生的事件類型
}

  當調用epoll_wait檢查是否有事件發生時,只需要檢查eventpoll對象中的rdlist雙鏈表中是否有epitem元素即可。如果rdlist不為空,則把發生的事件賦值到用戶態,同時將時間數量返回。

select、poll和epoll