- 英文小冊原文位址:beej.us/guide/bgnet…
- 作者:Beej
- 中文翻譯位址:www.chanmufeng.com/posts/netwo…
我假設你已經讀過
poll()
的用法了,是以直接進入主題。
select()
可以同時監聽多個socket,當有你感興趣的(多個)事件中的任何一個發生,核心才會喚醒
select()
。如果你真的想知道的話,
select()
會告訴你哪些socket是可以讀取的,哪些是可以寫入的,哪些引發了異常。
警告:随着連接配接數越來越多, select()
函數會變得巨慢!這種情況下,推薦你使用libevent這樣的事件庫。它會嘗試使用你系統上可用的最快方法,獲得更好的性能。
看一下
select()
的文法:
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int numfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
select()
函數監聽多種類型檔案描述符的集合,尤其是
readfd
、
writefd
和
exceptfd
。如果你想要知道你是否能從标準輸入(standard input)及某個socket descriptor(用
sockfd
表示)中進行讀取,隻要将标準輸入的檔案描述符表——
0
與
sockfd
新增到
readfds
中。參數
numfds
應設定為最高檔案描述符的值加1。在本例中,它應該設定為
sockfd+1
,因為它肯定高于标準輸入——
0
。
當
select()
傳回時,
readfds
将被修改,來反映你選擇的哪些file descriptor可以讀取。你可以使用下面的宏
FD_ISSET()
測試它們。
在進一步讨論之前,我先說一下如何操作這些file descriptor集合,每個集合都是
fd_set
類型,下面的宏在此類型上運作:
函數 | 描述 |
FD_SET(int fd, fd_set *set); | 将fd加入到set |
FD_CLR(int fd, fd_set *set); | 從set種移除fd |
FD_ISSET(int fd, fd_set *set); | 若fd在set中,傳回true |
FD_ZERO(fd_set *set); | 清空set |
最後,這個奇怪的
struct timeval
是什麼?
有時候,你不想永遠一直等着别人給你發送資料。也許每隔一段時間你就想在終端上列印“Still Going…”,即使什麼都沒有發生。這個struct允許你指定逾時時間段。如果超過了時間,
select()
仍然沒有找到任何就緒的file descriptor,它将傳回以便你可以繼續進行處理。
struct timeval
長這樣:
struct timeval {
int tv_sec; // seconds
int tv_usec; // microseconds
};
隻需将
tv_sec
設定為等待的秒數,将
tv_usec
設定成等待的微秒數。你沒看錯,這是_micro_seconds,而不是毫秒。一毫秒有1000微秒,一秒鐘有1000毫秒。是以,每秒有1000000微秒。為什麼是“usec”?“u”應該看起來像我們用來表示“micro”的希臘字母μ(Mu)。
此外,當函數傳回時,可能會更新
timeout
以顯示剩餘時間。這取決于你正在運作的Unix類型。
哇!我們有一個微秒級别的計時器!好吧,别指望它。無論你将
struct timeval
設定得多麼小,你可能還是要等待一小段的 standard Unix timeslice(标準 Unix 時間片段)。
另一件有意思的事情是:如果将
struct timeval
中的字段設定為0,
select()
會在輪詢過 sets 中的每個 file descriptor 之後立即timeout。如果将參數
timeout
設定為
NULL
,它将永遠不會timeout,而是陷入等待狀态,直到至少一個file descriptor已經就緒。如果你不在乎等待時間,可以在
select()
中将其設定為
NULL
。
下面的代碼段等待2.5秒,等待标準輸入中出現某些内容:
/*
** select.c -- a select() demo
*/
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#define STDIN 0 // file descriptor for standard input
int main(void)
{
struct timeval tv;
fd_set readfds;
tv.tv_sec = 2;
tv.tv_usec = 500000;
FD_ZERO(&readfds);
FD_SET(STDIN, &readfds);
// don't care about writefds and exceptfds:
select(STDIN+1, &readfds, NULL, NULL, &tv);
if (FD_ISSET(STDIN, &readfds))
printf("A key was pressed!\n");
else
printf("Timed out.\n");
return 0;
}
如果你用的是行緩沖(line buffered)的終端,那麼你從鍵盤輸入資料後應該要盡快按下 Enter,否則程式就會發生 timeout。
行緩存:标準輸出流遇到換行符\n時沖刷緩存。
你現在可能在想,這個方法用在需要等待資料的 datagram socket 上應該會很棒,你是對的:這可能确實是個不錯的方法。有些Unix可以以這種方式使用
select()
,有些則不能。如果你想嘗試的話,你應該參考一下你系統上的man手冊上是怎麼寫的。