2012年2月28日 星期二

API Select怎麼用


前言
常在寫網路程式的人對select()這個API應該不莫生才對,或常寫伺服器端程式也應該很熟悉才對,它可以同時監看多個檔案的讀寫允許,而且檔案包含各種週邊元件以及網路的socket,這裏我將會介紹它經常被用的應用,以及我個人經驗中覺有點特別的用法分享給其他人參考參考。
Select函式原型
int select(int nfds, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict errorfds, struct timeval *restrict timeout);
這個函數主要是用來同時監看數個檔案(包含socket),其中的事件在一定時間內是否有發生,而其中的事件有是否有資料可取得,是否可以寫入,以及其它錯誤事件,若在一段時間內沒有任何事件產生,則會回覆一個timeout的訊息,這個函數好用在它可以同時監看很多個檔案,以及時間,如此一來可以讓電腦更有效率以及可以同時處理多個檔案。
其中輸入的參數說明如下:
int nfds
對所要監看的所有file handle中最大的file handle號碼加1
fd_set *restrict readfds
相對監看該檔案是否有資料已收到或者已有資料可讀,只是確認有資可讀,但不知有多少資料可讀。可以使用FD_SET來設定相對需要確認的檔案,以及使用FD_ISSET來檢查確認的結果。
fd_set *restrict writefds
相對確認該檔案是否可以寫入或者有空間可寫入,只是確認可寫入,但是有多少空間可寫入,並不知道。可以使用FD_SET來設定相對需要確認的檔案,以及使用FD_ISSET來檢查確認的結果。
fd_set *restrict errorfds
相對確認該檔案是否有任何錯誤產生,可以使用FD_SET來設定相對需要確認的檔案,以及使用FD_ISSET來檢查確認的結果。
struct timeval *restrict timeout
這是設定timeout時間,若為NULL則是無限制時間監看及確認,其宣告如下:
struct timeval {
time_t tv_sec;
suseconds_t tv_usec;
}
以上最小時間單位為usec,但是在Linux中系統tick設定通常為10ms之下,通常最小時間可細分也只能到10ms,比這個小的時間其實是無法達到的。
其回傳值如下:
> 0 表示所以監看的檔案中有幾個檔案已符合條件。
= 0 表示未任何事件發生並且已經timeout。
< 0 表示有錯誤產生。
其它會用到的巨集指令
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);
清除所有檔案的監看及設定。
正常使用
在Linux中任何週邊都是使用檔案來使用,所以在select中的所使用file handle可以是網路socket open來的,也可以是open device node所得來的,也可以是真正的檔案,所以以下為一個正常使用的範例:
int fd1=soekcet(…)
int fd2=open(“device-node-name”, …);
int maxfd;
fd_set readfds, writerfds;
struct timeval timev;
FD_ZERO(&readfds);
FD_ZERO(&writefds);
if ( fd1 > fd2 )
maxfd = fd1;
else
maxfd = fd2;
FD_SET(fd1, &readfds);
FD_SET(fd2, &readfds);
FD_SET(fd1, &writefds);
FD_SET(fd2, &writefds);
timev.tv_sec = 3;
timev.tv_usec = 0;
if ( select(maxfd, &readfds, &writefds, NULL, &timev) ) {
if ( FD_ISSET(fd1, &readfds) ) {
// fd1有資料可讀
}
if ( FD_ISSET(fd2, &readfds) ) {
// fd2 有資料可讀
}
if ( FD_ISSET(fd1, &writefds) ) {
// fd1 可寫入
}
if (FD_ISSET(fd2, &writefds) ) {
// fd2 可寫入
}
} else {
// timeout or error 
}
以上的寫法在應用有許多好處,一是可以同時處理許多檔案,並且可以隨時加入或退出,二是使用事作觸發讓系統的負載不會過重,並且也可兼顧時效性。
短時間休眠用
前面所提是一般正常的使用方式,有一種另外的使用場合,那是因為在Linux的sleep function中只有秒級的單位,若我們想要實現msec的sleep則無法做到(注:現在在real time中可以有usleep),則可以利用select的timeout機制來作sleep的msec等級,其寫法如下:
struct timeval timev;
timev.tv_sec = 0;
timev.tv_usec = 10000; // 10msec
select(1, NULL, NULL, NULL, &timev);