當從一個描述符讀,寫到另一個描述符時,可以在下列形式的循環中使用阻塞IO:
while ((n = read(STDIN_FILENO, buf, BUFSIZ)) > 0)
if (write(STDOUT_FILENO, buf, n) != n)
err_sys("write error");
這種形式的阻塞IO隨處可見,但如果必須從兩個描述符讀呢?這種方式就可能導致長時間阻塞在其中一個描述符上,而另一個描述符雖然有很多數據卻不能及時處理。
一種直觀的解決方法是這樣的:將兩個描述符都設置為非阻塞,對第一個描述符發送read,如果有數據則讀取並處理,如果沒有則立即返回,對第二個描述符做同樣處理。此後等待若干秒,再度去第一個描述符。這種形式的循環稱為輪詢(polling),缺點非常明顯:大部分時間是無數據可讀的,但是仍然花費時間反復執行read
。
異步阻塞IO基於這樣一個想法:先構建一張有關描述符的列表,然後使用統一的阻塞函數,當任何socket有事件通知時跳出阻塞狀態。
select
select
就是這樣一個統一的阻塞函數。
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
select()
可以監視多個文件描述符,直到其中的一個或一些文件描述符的IO操作准備就緒,所謂就緒是指IO操作的請求不會被阻塞。
timeout
參數這個參數表示願意阻塞等待的時間。
tvptr == NULL
永遠等待,如果捕捉到一個信號則中斷,當所指定的描述符中的一個或多個已經准備好或捕捉到一個信號返回,後者返回-1,errno為EINTR
tvptr->tv_sec == 0 && tvptr->tv_usec == 0
完全不等待,退化為輪詢polling
tvptr->tv_sec != 0 || tvptr->tv_usec != 0
等待指定時間,當制定的描述符之一已經准備好,或指定的時間值已經超過則返回,後者返回0。與第一種情況一樣,可以捕捉信號。
readfds writefds exceptfds
這Sanger參數是指向描述符集的指針,說明了我們關心的可讀可寫或處於一場條件的各個描述符。這個描述符集使用位圖的方式存儲每個fd的狀態。
這三個參數任意一個或者全部都可以是空指針,表示對相應狀態不關心。如果都是空指針,則select提供了比sleep更精確的計時器。
FD_CLR FD_ISSET FD_SET FD_ZERO
這些宏或者參數負責對描述符集的結構做一些操作:清除、測試、置位、全部清除。
-1:表示出錯,不修改其中任何一個描述符
0: 描述符沒有准備好,且超時
正數:表示已經准備好的描述符個數,是三個描述符集中已准備好的描述符之和。
select
的阻塞與文件描述符的阻塞注意一個描述符阻塞與否並不影響select
是否阻塞,