I/O多路複用 ---- select、poll、epoll

一、什麼是I/O多路複用

當我們需要同時處理多個描述符時,使用單一描述符讀寫循環阻塞方式,會造成另一個描述符無法讀寫,造成大量數據無法處理丟失的狀況。這時候就需要採用一些方法來處理多個描述符。

  • 方法一:多進程
    設置多個進程,每個進程處理一個描述的通路數據。這樣則每個描述符都可以單獨進行讀寫阻塞,並且不會對其他描述符讀寫造成影響。
    但缺點是,事實上處理的描述符是不同的,但是事務卻是同一事務,進程也只是父子進程關係,當該事務的一個部分出現問題或者需要終止時,需要與其他親子進程進行通信,同時終止,程序實現較爲複雜。
  • 方法二:非阻塞I/O
    仍然使用單一進程,但是調用非阻塞I/O讀取數據,將多個描述符都設爲非阻塞,例如對第一個描述符進行read,該描述符上如果有數據,那麼進行read,如果沒有數據,則read立即返回,並對下一個描述符進行處理,知道遍歷完成所有描述符,等待若干時間在進行下一次循環遍歷,這種循環機制也被稱作輪訓機制。
    這種方法的缺點顯而易見是會造成CPU時間浪費,因爲每個描述符可能很少會有數據,而卻要不停地去循環讀取,造成CPU大量空轉。並且,循環一遍的時間也並不容易確定,可能需要根據實際需求去指定這個時間,也難以避免會產生阻塞時間碎片,還是浪費時間。
  • 方法三:異步I/O
    該方法的思想是,當一個描述符準備好進行I/O時,那麼發送一個信號告知內核,我準備好了,快調用我。
    該方法的問題有兩個,一是並不是所有系統都支持該方法。其次計算系統支持,每個進程都只對應一個該信號,當該信號發出時,內核也很難去確定到底是哪個描述符準備好進行I/O了。
  • 方法四:I/O多路複用
    I/O複用的基本思想是,先構造一張有關描述符的表,然後調用一個函數,該函數會根據需要去遍歷這張表,知道這些描述符中有一個描述符準備好進行I/O時,它才返回,並且會告訴進程,是哪一個描述符準備好進行I/O了。
    該方法用了兩個重要的函數select和poll。

二、select函數

1.select函數的工作機制

首先傳向select的參數會告訴內核:

  • 我們所關心的描述符集
  • 對於每個描述符的關心條件(是否讀、寫、異常)
  • 希望等待多長時間
    從select返回時,我們會得到:
  • 已經準備好的描述符的數量
  • 哪一個描述符已經準備好我們所關心的條件
    使用返回值,我們就可以對返回的描述符進行I/O操作,並且確認該函數不會阻塞。

2.select函數的原型

#include <sys/types.h>/* fd_set data type */
#include <sys/time.h> /* struct timeval */
#include <unistd.h> /* function prototype might be here */
int select (int maxfdp1, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *tvptr)
  • 第一個參數maxfdp1的意思是“最大fd加1(max fd plus 1)”。在三個描述符集中找出最高描述符編號值,然後加 1,這就是第一個參數值。也可將第一個參數設置爲FD_SETSIZE,這是一個< sys/types.h>中的常數,它說明了最大的描述符數(經常是256或1024)。但是對大多數應用程序而言,此值太大了。確實,大多數應用程序只應用3 ~ 10個描述符。如果將第三個參數設置爲最高描述符編號值加 1,內核就只需在此範圍內尋找打開的位,而不必在數百位的大範圍內搜索。
  • 中間三個參數readfds、writefds和exceptfds是指向描述符集的指針。這三個描述符集說明了我們關心的可讀、可寫或處於異常條件的各個描述符。每個描述符集存放在一個fd_set數據類型中。
    在這裏插入圖片描述
    關於fd_set類型變量有四個重要的宏:
FD_ZERO(fd_set, *fdset);			//給所有描述符位清零
FD_SET(int fd, fd_set *fdset);		//設置爲我們關心的描述符
FD_CLR(int fd, fd_set *fdset);		//關閉之前的關心設置
FD_ISSET(int fd, fd_set *fdset);	//測試指定描述符位是否依舊爲關心
  • 最後一個參數,它指的是願意等待的時間。
struct timeval {
	long tv_sec;
	long tv_usec;
}

當tvptr == NULL時,表示永遠等待。當所有指定的描述符中有一個準備好,或者捕捉到一箇中斷信號時返回。如果是信號返回,則函數返回值爲-1,errno設置爲EINTR。

當tvptr->tv_sec == 0 && tvptr->tv_usec == 0 時,表示完全不等待。測試所有指定的描述符並立即返回。這是得到多個描述符的狀態而不阻塞select函數的輪詢方法。

當tvptr->tv_sec != 0 || tvptr->tv_usec != 0 時,表示等待指定的秒數或者微秒數,這種等待也可能捕捉到中斷信號。

三、poll函數

poll函數和select類似,但是調用形式有所不同。

#include <stropts.h>
#include <poll.h>
int poll(struct pollfd fdarray[],unsigned long nfds,int timeout);

與select不同,poll不是爲每個條件構造一個描述符集,而是構造一個pollfd結構數組,每個數組元素指定一個描述符編號以及對其所關心的條件。

struct pollfd {
int fd; /* file descriptor to check, or < 0 to ignore */
short events; /* events of interest on fd */
short revents ; /* events that occurred on fd */
}

nfds表示描述符數組fdarray的元素數量。
將events成員設置爲下表中所示值的一個或幾個。通過這些值告訴內核我們對該描述符關心的是什麼。返回時,內核設置revents成員,以說明對該描述符發生了什麼事件。(注意,poll沒有更改events成員,這與select不同,select修改其參數以指示哪一個描述符已準備好了。)
在這裏插入圖片描述表中頭四行測試可讀性,接着三行測試可寫性,最後三行則是異常條件。最後三行是由內核在返回時設置的。即使在events字段中沒有指定這三個值,如果相應條件發生,則在revents中也返回它們。
當一個描述符被掛斷後(POLLHUP),就不能再寫向該描述符。但是仍可能從該描述符讀取到數據。

poll的最後一個參數說明我們想要等待多少時間。如同select一樣,有三種不同的情形:
- timeout == INFTIM,表示永遠等待;
- timeout == 0,表示不等待;
- timeout > 0,表示等待多少毫秒;

四、epoll函數

待更新。。。

參考材料《UNIX環境高級編程》

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