1. 程式人生 > >select/poll/epoll對比分析

select/poll/epoll對比分析

select/poll/epoll都是IO多路複用機制,可以同時監控多個描述符,當某個描述符就緒(讀或寫就緒),則立刻通知相應程式進行讀或寫操作。本質上select/poll/epoll都是同步I/O,即讀寫是阻塞的。

一、select

原型:

int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

從select函式監控3類檔案描述符:writefds、readfds、exceptfds。呼叫select函式後會阻塞,直到描述符準備就緒(有資料可讀、可寫、或者出現異常)或者超時,函式便返回。當select函式返回後,可以通過遍歷描述符集合,找到就緒的描述符。

select缺點

  • 單程序能夠監控的檔案描述符的數量存在最大限制,在Linux上一般為1024,可以通過修改巨集定義增大上限,但同樣存在效率低的弱勢;
  • IO效隨著監視的描述符數量的增長,其效率也會線性下降;

二、poll

原型:

int poll (struct pollfd *fds, unsigned int nfds, int timeout);

其中pollfd表示監視的描述符集合,如下

struct pollfd {
    int fd; //檔案描述符
    short events; //監視的請求事件
    short revents; //已發生的事件
};

pollfd結構包含了要監視的event和發生的event,並且pollfd並沒有最大數量限制(但數量過大同樣會導致性下降)。 和select函式一樣,當poll函式返回後,可以通過遍歷描述符集合,找到就緒的描述符。

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

三、epoll

epoll是在2.6核心中提出的,是select和poll的增強版。相對於select和poll來說,epoll更加靈活,沒有描述符數量限制。epoll使用一個檔案描述符管理多個描述符,將使用者空間的檔案描述符的事件存放到核心的一個事件表中,這樣在使用者空間和核心空間的copy只需一次。epoll機制是Linux最高效的I/O複用機制,在一處等待多個檔案控制代碼的I/O事件。

select/poll都只有一個方法,而epoll的操作過程有3個方法,分別是epoll_create(), epoll_ctl()epoll_wait()

3.1 epoll_create()

int epoll_create(int size);

用於建立一個epoll的控制代碼,size是指監聽的描述符個數, 現在核心支援動態擴充套件,該值的意義僅僅是初次分配的fd個數,後面空間不夠時會動態擴容。 當建立完epoll控制代碼後,佔用一個fd值.

ls /proc/<pid>/fd/  //可通過終端執行,看到該fd

使用完epoll後,必須呼叫close()關閉,否則可能導致fd被耗盡。

3.2 epoll_ctl()

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

用於對需要監聽的檔案描述符(fd)執行op操作,比如將fd加入到epoll控制代碼。

  • epfd:是epoll_create()的返回值;
  • op:表示op操作,用三個巨集來表示,分別代表新增、刪除和修改對fd的監聽事件;
    • EPOLL_CTL_ADD(新增)
    • EPOLL_CTL_DEL(刪除)
    • EPOLL_CTL_MOD(修改)
  • fd:需要監聽的檔案描述符;
  • epoll_event:需要監聽的事件,struct epoll_event結構如下:

      struct epoll_event {
        __uint32_t events;  /* Epoll事件 */
        epoll_data_t data;  /*使用者可用資料*/
      };
    

    events可取值:(表示對應的檔案描述符的操作)

    • EPOLLIN :可讀(包括對端SOCKET正常關閉);
    • EPOLLOUT:可寫;
    • EPOLLERR:錯誤;
    • EPOLLHUP:中斷;
    • EPOLLPRI:高優先順序的可讀(這裡應該表示有帶外資料到來);
    • EPOLLET: 將EPOLL設為邊緣觸發模式,這是相對於水平觸發來說的。
    • EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之後就不再監聽該事件

3.3 epoll_wait()

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
  • epfd:等待epfd上的io事件,最多返回maxevents個事件;
  • events:用來從核心得到事件的集合;
  • maxevents:events數量,該maxevents值不能大於建立epoll_create()時的size;
  • timeout:超時時間(毫秒,0會立即返回)。

該函式返回需要處理的事件數目,如返回0表示已超時。

四、對比

在 select/poll中,程序只有在呼叫一定的方法後,核心才對所有監視的檔案描述符進行掃描,而epoll事先通過epoll_ctl()來註冊一個檔案描述符,一旦基於某個檔案描述符就緒時,核心會採用類似callback的回撥機制,迅速啟用這個檔案描述符,當程序呼叫epoll_wait() 時便得到通知。(此處去掉了遍歷檔案描述符,而是通過監聽回撥的的機制。這正是epoll的魅力所在。)

epoll優勢

  1. 監視的描述符數量不受限制,所支援的FD上限是最大可以開啟檔案的數目,具體數目可以cat /proc/sys/fs/file-max檢視,一般來說這個數目和系統記憶體關係很大,以3G的手機來說這個值為20-30萬。

  2. IO效率不會隨著監視fd的數量增長而下降。epoll不同於select和poll輪詢的方式,而是通過每個fd定義的回撥函式來實現的,只有就緒的fd才會執行回撥函式。

如果沒有大量的idle-connection或者dead-connection,epoll的效率並不會比select/poll高很多,但是當遇到大量的idle-connection,就會發現epoll的效率大大高於select/poll。