select函數允許進程指示內核等待多個事件中的任何一個發生,并僅在有一個或多個事件發生或經歷一段指定的時間后才喚醒它。
作為一個例子,我們可以調用select,告知內核僅在下列情況發生時才返回:
(1)集合{1,4,5}中的任何描述字準備好讀,或
(2)集合{2,7}中的任何描述字準備好寫,或
(3)集合{1,4}中的任何描述字有異常條件待處理,或
(4)已經歷了10.2秒。
也就是說,我們調用select告知內核對哪些描述字(就讀、寫或異常條件)感興趣以及等待多長時間。我們感興趣的描述字不局限于套接口;任何描述字都可以使用select來測試。
#include <sys/select.h>#include <sys/time.h>int select(int maxfdpl, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);返回值:就緒描述字的總數目,0——超時,-1——出錯
關于此函數的參數的詳細介紹可參考http://www.CUOXin.com/nufangrensheng/p/3557584.html。重復內容不再贅述,這里只給出一些補充內容。
timeout參數的const限定詞表示它在函數返回時不會被select修改。
但是有些linux版本會修改這個timeval結構。因此從移植性考慮,我們應該假設該timeval結構在select返回時未被定義,因此每次調用select之前都得對它進行初始化。POSIX規定對該結構使用const限定詞。
中間的三個參數readset、writeset和exceptset指定我們要讓內核測試讀、寫和異常條件的描述字。目前支持的異常條件只有兩個:
(1)某個套接口的帶外數據的到達。
(2)某個已置為分組方式的偽終端存在可從其主端讀取的控制狀態信息。
如何給這三個參數的每一個指定一個或多個描述字值是一個設計上的問題。select使用描述字集,典型地是一個整數數組,其中每個整數中的每一位對應一個描述字。舉例來說,假設使用32位整數,那么該數組的第一個元素對應描述字0-31,第二個元素對應描述字32-63,以此類推。所有這些實現細節都與應用程序無關,它們隱藏在名為fd_set的數據類型和以下四個宏中:
/* fd_set macro */void FD_ZERO(fd_set *fdset); /* clear all bits in fdset */void FD_SET(int fd, fd_set *fdset); /* turn on the bit for fd in fdset */void FD_CLR(int fd, fd_set *fdset); /* turn off the bit for fd in fdset */int FD_ISSET(int fd, fd_set *fdset); /* is the bit for fd on in fdset?*/
描述字集的初始化非常重要,因為作為自動變量分配的一個描述符字集如果沒有初始化,那么可能發生不可預期的后果。因此,聲明了一個描述符集后,必須用FD_ZERO清除其所有位,然后在其中設置我們關心的各個位。舉個例子,以下代碼用于定義一個fd_set類型的變量,然后打開描述字1、4和5的對應位:
/* example */fd_set rset;FD_ZERO(&rset); /* initialize the set: all bits off */FD_SET(1, &rset); /* turn on bit for fd 1 */FD_SET(4, &rset); /* turn on bit for fd 4 */FD_SET(5, &rset); /* turn on bit for fd 5 */
select函數的中間三個參數readset、writeset和exceptset中,如果我們對某一個的條件不感興趣,就可以把它設為空指針。事實上,如果這三個指針均為空,我們就有了一個比UNIX的sleep函數更為精確的定時器(sleep睡眠以秒為最小單位)。poll函數提供類似的功能。
maxfdpl參數指定待測試的描述符字個數,它的值是待測試的最大描述符字加1(因此我們把該參數命名為maxfdpl),描述字0、1、2……一直到maxfdpl-1均被測試。
頭文件<sys/select.h>中定義的FD_SETSIZE常值是數據類型fd_set中的描述字總數,其值通常是1024,不過很少有程序用到那么多描述字。
select函數修改由指針readset、writeset和exceptset所指向的描述字集,因而這三個參數都是值-結果參數。調用該函數時,我們指定所關心的描述字的值,該函數返回時,結果指示哪些描述字已就緒。該函數返回后,我們使用FD_ISSET宏來測試fd_set數據類型中的描述字。描述字集中任何與未就緒的描述字對應的位返回時均清成0。為此,每次重新調用select函數時,我們都得再次把所有描述字集中所關心的位均置為1。
使用select時最常見的兩個錯誤:(1)忘了對最大描述字加1;(2)忘了描述字集是值-結果參數。
該函數的返回值表示跨所有描述字集的已就緒的總位數。如果在任何描述字就緒之前定時器時間到,那么返回0。返回-1表示出錯。
1、下列四個條件中的任何一個滿足時,一個套接口準備好讀:
(1)該套接口接收緩沖區中的數據字節數大于等于套接口接收緩沖區低潮標記的當前大小。對這樣的套接口的讀操作將不阻塞并返回一個大于0的值(也就是返回準備好讀入的數據)。我們可以使用SO_RCVLOWAT套接口選項設置該套接口的低潮標記。對于TCP和UDP套接口而言,其缺省值為1。
(2)該連接的讀這一半關閉(也就是接收了FIN的TCP連接)。對這樣的套接口的讀操作將不阻塞并返回0(也就是返回EOF)。
(3)該套接口是一個監聽套接口且已完成的連接數不為0。對這樣的套接口的accept通常不會阻塞。
(4)其上有一個套接口錯誤待處理。對這樣的套接口的讀操作將不阻塞并返回-1(也就是返回一個錯誤),同時把errno設置成確切的錯誤條件。這些待處理的錯誤(pending error)也可以通過指定SO_ERROR套接口選項調用getsockopt獲取并清除。
2、下列四個條件中的任何一個滿足時,一個套接口準備好寫:
(1)該套接口發送緩沖區中的可用空間字節數大于等于套接口發送緩沖區低潮標記的當前大小,并且或者(I)該套接口已連接,或者(II)該套接口不需要連接(例如UDP套接口)。這意味著如果我們把這樣的套接口設置為非阻塞,寫操作將不阻塞并返回一個正值(例如由傳輸層接受的字節數)。我們可以使用SO_SNDLOWAT套接口選項來設置該套接口的低潮標記。對于TCP和UDP而言,其缺省值通常為2048。
(2)該連接的寫這一半關閉。對這樣的套接口的寫操作將產生SIGPipE信號。
(3)該套接字早先使用非阻塞式connect已建立連接,并且連接已經異步建立或者connect已經以失敗告終。
(4)其上有一個套接口錯誤待處理。對這樣的套接口的寫操作將不阻塞并返回-1(也就是返回一個錯誤),同時把errno設置成確切的錯誤條件。這些待處理的錯誤(pending error)也可以通過指定SO_ERROR套接口選項調用getsockopt獲取并清除。
3、如果一個套接口存在帶外數據或者仍處于帶外標記,那么它有異常條件待處理。
注意:當某個套接口上發生錯誤時,它將由select標記為既可讀又可寫。
接收和發送低潮標記的目的在于:允許應用程序控制在select返回可讀或可寫條件之前,有多少數據可讀或有多大空間可用于寫。舉例來說,如果我們知道除非至少存在64字節的數據,否則我們的應用程序沒有任何工作可做,那么可以把接收低潮標記設置為64,以防少于64字節的數據準備好讀時,select就喚醒我們。
任何UDP套接口只要其發送低潮標記小于等于發送緩沖區大?。ㄈ笔摽偸沁@種關系)就總是可寫的,這是因為UDP套接口不需要連接。
新聞熱點
疑難解答