fd_set 用法
select()函式主要是建立在fd_set型別的基礎上的。fd_set(它比較重要所以先介紹一下)是一組檔案描述字(fd)的集合,它用一位來表示一個fd(下面會仔細介紹),對於fd_set型別通過下面四個巨集來操作:
fd_set set;
FD_ZERO(&set); /* 將set清零使集合中不含任何fd*/
FD_SET(fd, &set); /* 將fd加入set集合 */
FD_CLR(fd, &set); /* 將fd從set集合中清除 */
FD_ISSET(fd, &set); /* 測試fd是否在set集合中*/
過去,一個fd_set通常只能包含<32的fd(檔案描述字),因為fd_set其實只用了一個32位向量來表示fd;現在,UNIX系統通常會在標頭檔案中定義常量FD_SETSIZE,它是資料型別fd_set的描述字數量,其值通常是1024,這樣就能表示<1024的fd。根據fd_set的位向量實現,我們可以重新理解操作fd_set的四個巨集:
fd_set set;
FD_ZERO(&set); /*將set的所有位置0,如set在記憶體中佔8位則將set置為
00000000*/
FD_SET(0, &set); /* 將set的第0位置1,如set原來是00000000,則現在變為10000000,這樣fd==1的檔案描述字就被加進set中了 */
FD_CLR(4, &set); /*將set的第4位置0,如set原來是10001000,則現在變為10000000,這樣fd==4的檔案描述字就被從set中清除了 */
FD_ISSET(5, &set); /* 測試set的第5位是否為1,如果set原來是10000100,則返回非零,表明fd==5的檔案描述字在set中;否則返回0*/
―――――――――――――――――――――――――――――――――――――――
注意fd的最大值必須<fd_setsize。< p="" style="word-wrap: break-word;">
―――――――――――――――――――――――――――――――――――――――
select函式的介面比較簡單:
int select(int nfds, fd_set *readset, fd_set *writeset,
fd_set* exceptset, struct timeval *timeout);
功能:
測試指定的fd可讀?可寫?有異常條件待處理?
引數:
nfds
需要檢查的檔案描述字個數(即檢查到fd_set的第幾位),數值應該比三組fd_set中所含的最大fd值更大,一般設為三組fd_set中所含的最大fd值加1(如在readset,writeset,exceptset中所含最大的fd為5,則nfds=6,因為fd是從0開始的)。設這個值是為提高效率,使函式不必檢查fd_set的所有1024位。
readset
用來檢查可讀性的一組檔案描述字。
writeset
用來檢查可寫性的一組檔案描述字。
exceptset
用來檢查是否有異常條件出現的檔案描述字。(注:錯誤不包括在異常條件之內)
timeout
有三種可能:
1. timeout=NULL(阻塞:直到有一個fd位被置為1函式才返回)
2. timeout所指向的結構設為非零時間(等待固定時間:有一個fd位被置為1或者時間耗盡,函式均返回)
3. timeout所指向的結構,時間設為0(非阻塞:函式檢查完每個fd後立即返回)
返回值:
返回對應位仍然為1的fd的總數。
Remarks:
三組fd_set均將某些fd位置0,只有那些可讀,可寫以及有異常條件待處理的fd位仍然為1。
使用select函式的過程一般是:
先呼叫巨集FD_ZERO將指定的fd_set清零,然後呼叫巨集FD_SET將需要測試的fd加入fd_set,接著呼叫函式select測試fd_set中的所有fd,最後用巨集FD_ISSET檢查某個fd在函式select呼叫後,相應位是否仍然為1。
以下是一個測試單個檔案描述字可讀性的例子:
int isready(int fd)
{
int rc;
fd_set fds;
struct timeval tv;
FD_ZERO(&fds);
FD_SET(fd,&fds);
tv.tv_sec = tv.tv_usec = 0;
rc = select(fd+1, &fds, NULL, NULL, &tv);
if (rc < 0) //error
return -1;
return FD_ISSET(fd,&fds) ? 1 : 0;
}
下面還有一個複雜一些的應用:
//這段程式碼將指定測試Socket的描述字的可讀可寫性,因為Socket使用的也是fd
uint32 SocketWait(TSocket *s,bool rd,bool wr,uint32 timems)
{
fd_set rfds,wfds;
#ifdef _WIN32
TIMEVAL tv;
#else
struct timeval tv;
#endif /* _WIN32 */
FD_ZERO(&rfds);
FD_ZERO(&wfds);
if (rd) //TRUE
FD_SET(*s,&rfds); //新增要測試的描述字
if (wr) //FALSE
FD_SET(*s,&wfds);
tv.tv_sec=timems/1000; //second
tv.tv_usec=timems%1000; //ms
for (;;) //如果errno==EINTR,反覆測試緩衝區的可讀性
switch(select((*s)+1,&rfds,&wfds,NULL,
(timems==TIME_INFINITE?NULL:&tv))) //測試在規定的時間內套介面接收緩衝區中是否有資料可讀
{ //0--超時,-1--出錯
case 0: /* time out */
return 0;
case (-1): /* socket error */
if (SocketError()==EINTR)
break;
return 0; //有錯但不是EINTR
default:
if (FD_ISSET(*s,&rfds)) //如果s是fds中的一員返回非0,否則返回0
return 1;
if (FD_ISSET(*s,&wfds))
return 2;
return 0;
};
}