1. 程式人生 > >Linux I/O復用中select poll epoll模型的介紹及其優缺點的比較

Linux I/O復用中select poll epoll模型的介紹及其優缺點的比較

創建 等待 歸類 好的 第一個 class ews tor client

關於I/O多路復用:

I/O多路復用(又被稱為“事件驅動”),首先要理解的是。操作系統為你提供了一個功能。當你的某個socket可讀或者可寫的時候。它能夠給你一個通知。這樣當配合非堵塞的socket使用時,僅僅有當系統通知我哪個描寫敘述符可讀了,我才去運行read操作。能夠保證每次read都能讀到有效數據而不做純返回-1和EAGAIN的無用功。寫操作相似。操作系統的這個功能通過select/poll/epoll之類的系統調用來實現。這些函數都能夠同一時候監視多個描寫敘述符的讀寫就緒狀況,這樣。**多個描寫敘述符的I/O操作都能在一個線程內並發交替地順序完畢,這就叫I/O多路復用,這裏的“復用”指的是復用同一個線程。

一、I/O復用之select

1、介紹:
select系統調用的目的是:在一段指定時間內。監聽用戶感興趣的文件描寫敘述符上的可讀、可寫和異常事件。poll和select應該被歸類為這種系統調用,它們能夠堵塞地同一時候探測一組支持非堵塞的IO設備,直至某一個設備觸發了事件或者超過了指定的等待時間——也就是說它們的職責不是做IO,而是幫助調用者尋找當前就緒的設備。
以下是select的原理圖:
技術分享圖片

2、select系統調用API例如以下:

#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

fd_set結構體是文件描寫敘述符集,該結構體實際上是一個整型數組,數組中的每一個元素的每一位標記一個文件描寫敘述符。fd_set能容納的文件描寫敘述符數量由FD_SETSIZE指定。普通情況下,FD_SETSIZE等於1024,這就限制了select能同一時候處理的文件描寫敘述符的總量。

3、以下介紹一下各個參數的含義:
1)nfds參數指定被監聽的文件描寫敘述符的總數。

通常被設置為select監聽的全部文件描寫敘述符中最大值加1;
2)readfds、writefds、exceptfds分別指向可讀、可寫和異常等事件相應的文件描寫敘述符集合。這三個參數都是傳入傳出型參數,指的是在調用select之前,用戶把關心的可讀、可寫、或異常的文件描寫敘述符通過FD_SET(以下介紹)函數分別加入進readfds、writefds、exceptfds文件描寫敘述符集,select將對這些文件描寫敘述符集中的文件描寫敘述符進行監聽,假設有就緒文件描寫敘述符,select會重置readfds、writefds、exceptfds文件描寫敘述符集來通知應用程序哪些文件描寫敘述符就緒。這個特性將導致select函數返回後。再次調用select之前,必須重置我們關心的文件描寫敘述符

,也就是三個文件描寫敘述符集已經不是我們之前傳入 的了。
3)timeout參數用來指定select函數的超時時間(以下講select返回值時還會談及)。

struct timeval
{
    long tv_sec;        //秒數
    long tv_usec;       //微秒數
};

4、以下幾個函數(宏實現)用來操縱文件描寫敘述符集:

void FD_SET(int fd, fd_set *set);   //在set中設置文件描寫敘述符fd
void FD_CLR(int fd, fd_set *set);   //清除set中的fd位
int  FD_ISSET(int fd, fd_set *set); //推斷set中是否設置了文件描寫敘述符fd
void FD_ZERO(fd_set *set);          //清空set中的全部位(在使用文件描寫敘述符集前。應該先清空一下)
    //(註意FD_CLR和FD_ZERO的差別,一個是清除某一位,一個是清除全部位)

5、select的返回情況:
1)假設指定timeout為NULL,select會永遠等待下去,直到有一個文件描寫敘述符就緒,select返回。
2)假設timeout的指定時間為0,select根本不等待,馬上返回;
3)假設指定一段固定時間,則在這一段時間內,假設有指定的文件描寫敘述符就緒,select函數返回,假設超過指定時間,select同樣返回。
4)返回值情況:
a)超時時間內,假設文件描寫敘述符就緒,select返回就緒的文件描寫敘述符總數(包含可讀、可寫和異常),假設沒有文件描寫敘述符就緒,select返回0;
b)select調用失敗時,返回 -1並設置errno。假設收到信號。select返回 -1並設置errno為EINTR。

6、文件描寫敘述符的就緒條件:
在網絡編程中,
1)下列情況下socket可讀:
a) socket內核接收緩沖區的字節數大於或等於其低水位標記SO_RCVLOWAT;
b) socket通信的對方關閉連接,此時該socket可讀,可是一旦讀該socket。會馬上返回0(能夠用這種方法推斷client端是否斷開連接);
c) 監聽socket上有新的連接請求。
d) socket上有未處理的錯誤。


2)下列情況下socket可寫:
a) socket內核發送緩沖區的可用字節數大於或等於其低水位標記SO_SNDLOWAT;
b) socket的讀端關閉。此時該socket可寫。一旦對該socket進行操作。該進程會收到SIGPIPE信號。
c) socket使用connect連接成功之後;
d) socket上有未處理的錯誤。

二、I/O復用之poll

1、poll系統調用的原理與原型和select基本相似。也是在指定時間內輪詢一定數量的文件描寫敘述符。以測試當中是否有就緒者。

2、poll系統調用API例如以下:

#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

3、以下介紹一下各個參數的含義:
1)第一個參數是指向一個結構數組的第一個元素的指針,每一個元素都是一個pollfd結構,用於指定測試某個給定描寫敘述符的條件。

struct pollfd
{
    int fd;             //指定要監聽的文件描寫敘述符
    short events;       //指定監聽fd上的什麽事件
    short revents;      //fd上事件就緒後,用於保存實際發生的時間
}。

待監聽的事件由events成員指定,函數在相應的revents成員中返回該描寫敘述符的狀態(每一個文件描寫敘述符都有兩個事件,一個是傳入型的events,一個是傳出型的revents。從而避免使用傳入傳出型參數。註意與select的差別),從而告知應用程序fd上實際發生了哪些事件。events和revents都能夠是多個事件的按位或。
2)第二個參數是要監聽的文件描寫敘述符的個數,也就是數組fds的元素個數;
3)第三個參數意義與select同樣。

4、poll的事件類型:
技術分享圖片
在使用POLLRDHUP時,要在代碼開始處定義_GNU_SOURCE

5、poll的返回情況:
與select同樣。

三、I/O復用之epoll

1、介紹:
epoll 與select和poll在使用和實現上有非常大差別。

首先,epoll使用一組函數來完畢,而不是單獨的一個函數。其次。epoll把用戶關心的文件描寫敘述符上的事件放在內核裏的一個事件表中。無須向select和poll那樣每次調用都要反復傳入文件描寫敘述符集合事件集。

2、創建一個文件描寫敘述符,指定內核中的事件表:

#include<sys/epoll.h>
int epoll_create(int size);
    //調用成功返回一個文件描寫敘述符。失敗返回-1並設置errno。

size參數並不起作用。僅僅是給內核一個提示。告訴它事件表須要多大。

該函數返回的文件描寫敘述符指定要訪問的內核事件表,是其它全部epoll系統調用的句柄。

3、操作內核事件表:

#include<sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    //調用成功返回0,調用失敗返回-1並設置errno。

epfd是epoll_create返回的文件句柄。標識事件表。op指定操作類型。

操作類型有以下3種:

a)EPOLL_CTL_ADD, 往事件表中註冊fd上的事件;
b)EPOLL_CTL_MOD, 改動fd上註冊的事件;
c)EPOLL_CTL_DEL, 刪除fd上註冊的事件。

event參數指定事件,epoll_event的定義例如以下:

struct epoll_event
{
    __int32_t events;       //epoll事件
    epoll_data_t data;      //用戶數據
};

typedef union epoll_data
{
    void *ptr;
    int  fd;
    uint32_t u32;
    uint64_t u64;
}epoll_data;

在使用epoll_ctl時,是把fd加入、改動到內核事件表中,或從內核事件表中刪除fd的事件。

假設是加入事件到事件表中,能夠往data中的fd上加入事件events。或者不用data中的fd,而把fd放到用戶數據ptr所指的內存中(由於epoll_data是一個聯合體。僅僅能使用當中一個數據),再設置events。

3、epoll_wait函數
epoll系統調用的最關鍵的一個函數epoll_wait,它在一段時間內等待一個組文件描寫敘述符上的事件。

#include<sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
    //函數調用成功返回就緒文件描寫敘述符個數,失敗返回-1並設置errno。

timeout參數和select與poll同樣。指定一個超時時間;maxevents指定最多監聽多少個事件。events是一個傳出型參數。epoll_wait函數假設檢測到事件就緒,就將全部就緒的事件從內核事件表(epfd所指的文件)中復制到events指定的數組中。

這個數組用來輸出epoll_wait檢測到的就緒事件,而不像select與poll那樣。這也是epoll與前者最大的差別,下文在比較三者之間的差別時還會說到。

四、三組I/O復用函數的比較

同樣點:
1)三者都須要在fd上註冊用戶關心的事件。
2)三者都要一個timeout參數指定超時時間。
不同點:
1)select:
a)select指定三個文件描寫敘述符集,各自是可讀、可寫和異常事件,所以不能更加仔細地區分全部可能發生的事件。
b)select假設檢測到就緒事件,會在原來的文件描寫敘述符上改動,以告知應用程序,文件描寫敘述符上發生了什麽時間,所以再次調用select時,必須先重置文件描寫敘述符
c)select採用對全部註冊的文件描寫敘述符集輪詢的方式,會返回整個用戶註冊的事件集合,所以應用程序索引就緒文件的時間復雜度為O(n)。
d)select同意監聽的最大文件描寫敘述符個數通常有限制。通常是1024。假設大於1024,select的性能會急劇下降;
e)僅僅能工作在LT模式。

2)poll:
a)poll把文件描寫敘述符和事件綁定,事件不但能夠單獨指定。並且能夠是多個事件的按位或。這樣更加細化了事件的註冊,並且poll單獨採用一個元素用來保存就緒返回時的結果,這樣在下次調用poll時。就不用重置之前註冊的事件;
b)poll採用對全部註冊的文件描寫敘述符集輪詢的方式。會返回整個用戶註冊的事件集合。所以應用程序索引就緒文件的時間復雜度為O(n)。
c)poll用nfds參數指定最多監聽多少個文件描寫敘述符和事件,這個數能達到系統同意打開的最大文件描寫敘述符數目。即65535。
d)僅僅能工作在LT模式。

3)epoll:
a)epoll把用戶註冊的文件描寫敘述符和事件放到內核當中的事件表中。提供了一個獨立的系統調用epoll_ctl來管理用戶的事件,並且epoll採用回調的方式。一旦有註冊的文件描寫敘述符就緒,講觸發回調函數,該回調函數將就緒的文件描寫敘述符和事件復制到用戶空間events所管理的內存。這樣應用程序索引就緒文件的時間復雜度達到O(1)。
b)epoll_wait使用maxevents來制定最多監聽多少個文件描寫敘述符和事件,這個數能達到系統同意打開的最大文件描寫敘述符數目,即65535。
c)不僅能工作在LT模式,並且還支持ET高效模式(即EPOLLONESHOT事件,讀者能夠自己查一下這個事件類型,對於epoll的線程安全有非常好的幫助)。

select/poll/epoll總結:
技術分享圖片

Linux I/O復用中select poll epoll模型的介紹及其優缺點的比較