select、poll、epoll的原理

一、select
select函數
int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);

參數和返回值

    maxfd:監視對象文件描述符數量。
    readset: 將所有關注“是否存在待讀取數據”的文件描述符註冊到fd_set變量,並傳遞其地址值。
    writeset: 將所有關注“是否可傳輸無阻塞數據”的文件描述符註冊到fd_set變量,並傳遞其地址值。
    exceptset: 將所有關注“是否發生異常”的文件描述符註冊到fd_set變量,並傳遞其地址值。
    timeout: 調用select後,爲防止陷入無限阻塞狀態,傳遞超時信息。
    返回值:錯誤返回-1,超時返回0。因關注的事件返回時,返回大於0的值,該值是發生事件的文件描述符數。
 
四個宏定義
    FD_ZERO(fd_set* fdset): 將fd_set變量的所有位初始化爲0。
    FD_SET(int fd, fd_set* fdset):在參數fdset指向的變量中註冊文件描述符fd的信息。
    FD_CLR(int fd, fd_set* fdset):參數fdset指向的變量中清除文件描述符fd的信息。
    FD_ISSET(int fd, fd_set* fdset): 若參數fdset指向的變量中包含文件描述符fd的信息,則返回真。

select方法底層

我們可以把三個參數都看成三個數組:讀事件數組,寫事件數組,異常事件數組
我們需要維護這三個數組,這三個數組存的都是fd,socket的文件描述符
在timeout時間裏,當有其中一個文件描述符有讀寫的io事件發生,內核就會把這三個數組都返回給我們用戶
並且用戶必須無差別輪詢判斷,到底哪個io發生了讀寫事件,再去進行對應的操作(accpet從數組中添加fd,disconnected從數組中刪除fd等...)
然後我們再去把三個數組交給內核去管理
缺點
  1. 每次都要把這三個數組從用戶態拷貝到內核態
  2. 每次都要無差別輪詢這三個數組。我們如果有500個連接,那麼就得輪詢500次
  3. 支持最多1024個連接。
  4. 只有水平觸法一種方式
 

 
二、poll
poll函數
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
 
參數與返回值
   1)第一個參數:一個結構數組,struct pollfd結構如下:
  struct pollfd{
      int fd;              //文件描述符
      short events;    //請求的事件
      short revents;   //返回的事件
  };
    
    2)第二個參數, 要監視的描述符的數目
    3)第三個參數,超時時間
    4)返回值,返回的事件個數,=0超時,-1失敗
 
與select相同,我們需要管理 fds鏈表,這個結構體裏面存着我們感興趣的事件和我們的對應的文件描述符
同樣我們需要無差別地輪詢 fds鏈表。但是沒有最大鏈接數限制了
缺點:
  1. 與select不同的是不受最大連接數限制了
  2. 還是需要去無差別輪詢,從內核態拷貝到用戶態,再從內核態到用戶態。效率低

三、epoll
 
int epoll_create(int size);
參數返回值:
1.size:最大連接數,即內核最大監聽數目
2.返回值:epoll_fd epoll文件描述符
 
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
參數返回值:
1.epfd:epoll_create獲取到的文件描述符
2.op:操作,有三個宏定義
    EPOLL_CTL_ADD:註冊新的fd到epfd中;(添加到紅黑樹上)
    EPOLL_CTL_MOD:修改已經註冊的fd的監聽事件;(譬如讀/寫事件修改)
    EPOLL_CTL_DEL:從epfd中刪除一個fd;(從紅黑樹上剔除)
3.fd:要操作的文件描述符(server_fd和accpet_fd)
4.event:
    struct epoll_event {
        __uint32_t events; /* Epoll events */            epoll事件
        epoll_data_t data; /* User data variable */  用戶傳參
    };
    
    typedef union epoll_data {
        void *ptr;    // 用戶自定義數據結構
        int fd;        // 文件描述符
        uint32_t u32;
        uint64_t u64;
    } epoll_data_t;
5.返回值:0成功 -1失敗
 
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
參數返回值
1.返回值:本次通知返回的事件數目
2.epfd:epoll_create返回值
3.events:返回的所有事件
4.maxevents:用戶設置內核最大事件返回數目
5.timeout:超時,-1不超時
 
與前兩個io多路複用不同的是,所有的用戶要交給內核管理的文件描述符,都存在於內核,
當文件描述符有事件發生時,內核哪個文件描述符發生的events事件給用戶,而不是將所有的文件描述符的所有events事件都交給用戶
好處是:
  1. 用戶不需要再無差別輪詢,遍歷的events數組都是發生了事件的
  2. 不需要頻繁地從用戶態拷貝大量文件描述符到內核態,再從內核態拷貝到用戶態
  3. epoll底層內核用的是紅黑樹,效率極高
  4. epoll提供水平模式(用戶不處理events會不斷觸發事件)和邊緣觸發模式(用戶不處理本次事件,本次事件內核不再提醒)
  5. select和poll每次要拷貝數組到內核中。而epoll,所有的event都存在於內核的紅黑樹上,用戶通過epoll_ctl接口來操作紅黑樹上的節點
 
用戶通過epoll_create獲取ep_fd,然後用戶通過epoll_ctl來操作,用戶填充epoll_event結構體,結構體中由epoll_data中的void* ptr給用戶提供傳參(一般傳入一個結構體,結構體中有回調方法)。
epoll_ctl提供三種操作:註冊/修改/刪除
epoll_wait等待內核返回events事件
 
    
 
內核的epoll數據結構:紅黑樹+雙向鏈表
每當我們用epoll_ctl註冊時,都會在紅黑樹上添加一個event節點(紅黑樹極快定位哪個節點發生了讀寫事件)
當一個紅黑樹上的節點發生了事件,會被添加到雙向鏈表中,拷貝給用戶。
 
總結:
1)select和poll每次都要用戶無差別輪詢到底哪個發生了讀寫事件,內核只管通知,不會告訴用戶到底哪個事件就緒了。無差別輪詢:fd越多,遍歷次數越多,效率越低
2)select和poll每次都要將數組從用戶拷貝到內核中,用戶態/內核態來回切換,內存拷貝,效率極低。
3)poll用的是鏈表形式,而select則用的是數組形式,select的監聽數目受限制,最大1024,而poll不受到限制
4)epoll的所有fd都交給內核去管理,用戶通過epoll_ctl接口去操作紅黑樹上的節點,內核返回的events都是就緒的events。效率因爲充斥大量未就緒事件受影響
5)epoll由於所有fd都交給了內核去管理,少了用戶態向內核態數據的拷貝,效率提高不少
 
雖然在epoll的性能最好,但是在連接數非常少並且連接都十分活躍的情況下,select/poll的性能可能比epoll高。因爲epoll的通知機制需要很多函數的回調
 
最後附上一張epoll調用原理
 
 
 
 
 
 
 
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章