select系統(tǒng)調(diào)用是用來讓我們的程序監(jiān)視多個文件句柄(file descriptor)的狀態(tài)變化的。程序會停在select這里等待,直到被監(jiān)視的文件句柄有某一個或多個發(fā)生了狀態(tài)改變。 文件在句柄在Linux里很多,如果你man某個函數(shù),在函數(shù)返回值部分說到成功后有一個文件句柄被創(chuàng)建的都是的,如man socket可以看到“On success, a file descriptor for the new socket is returned.”而man 2 open可以看到“open() and creat() return the new file descriptor”,其實文件句柄就是一個整數(shù),看socket函數(shù)的聲明就明白了:
int socket(int domain, int type, int protocol);
當然,我們最熟悉的句柄是0、1、2三個,0是標準輸入,1是標準輸出,2是標準錯誤輸出。0、1、2是整數(shù)表示的,對應(yīng)的FILE *結(jié)構(gòu)的表示就是stdin、stdout、stderr,0就是stdin,1就是stdout,2就是stderr。比如下面這兩段代碼都是從標準輸入讀入9個字節(jié)字符:
    
        
            | #include <stdio.h> #include <unistd.h> #include <string.h> int main(int argc, char ** argv) {            char buf[10] = "";             read(0, buf, 9); /* 從標準輸入 0 讀入字符 */              fprintf(stdout, "%s\n", buf); /* 向標準輸出 stdout 寫字符 */              return 0; } /* **上面和下面的代碼都可以用來從標準輸入讀用戶輸入的9個字符** */ #include <stdio.h> #include <unistd.h> #include <string.h> int main(int argc, char ** argv) {         char buf[10] = "";         fread(buf, 9, 1, stdin); /* 從標準輸入 stdin 讀入字符 */         write(1, buf, strlen(buf));           return 0; } | 
    
繼續(xù)上面說的select,就是用來監(jiān)視某個或某些句柄的狀態(tài)變化的。
select函數(shù)原型如下:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
函 數(shù)的最后一個參數(shù)timeout顯然是一個超時時間值,其類型是struct timeval *,即一個struct timeval結(jié)構(gòu)的變量的指針,所以我們在程序里要申明一個struct timeval tv;然后把變量tv的地址&tv傳遞給select函數(shù)。struct timeval結(jié)構(gòu)如下:
    
        
            | struct timeval {              long    tv_sec;         /* seconds */              long    tv_usec;        /* microseconds */          }; | 
    
第2、 3、4三個參數(shù)是一樣的類型: fd_set *,即我們在程序里要申明幾個fd_set類型的變量,比如rdfds, wtfds, exfds,然后把這個變量的地址&rdfds, &wtfds, &exfds 傳遞給select函數(shù)。
      這三個參數(shù)都是一個句柄的集合,第一個rdfds是用來保存這樣的句柄的:當句柄的狀態(tài)變成可讀的時系統(tǒng)就會告訴select函數(shù)返回,同理第二個wtfds是指有句柄狀態(tài)變成可寫的時系統(tǒng)就會告訴select函數(shù)返回,同理第三個參數(shù)exfds是特殊情況,即句柄上有特殊情況發(fā)生時系統(tǒng)會告訴select函數(shù)返回。特殊情況比如對方通過一個socket句柄發(fā)來了緊急數(shù)據(jù)。如果我們程序里只想檢測某個socket是否有數(shù)據(jù)可讀,我們可以這樣:
    
        
            | fd_set rdfds; /* 先申明一個 fd_set 集合來保存我們要檢測的 socket句柄 */ struct timeval tv; /* 申明一個時間變量來保存時間 */ int ret; /* 保存返回值 */ FD_ZERO(&rdfds); /* 用select函數(shù)之前先把集合清零 */ FD_SET(socket, &rdfds); /* 把要檢測的句柄socket加入到集合里 */   tv.tv_sec = 1;  tv.tv_usec = 500; /* 設(shè)置select等待的最大時間為1秒加500微秒 */    ret = select(socket + 1, &rdfds, NULL, NULL, &tv); /* 檢測我們上面設(shè)置到集合rdfds里的句柄是否有可讀信息 */ if(ret < 0)     perror("select");/* 這說明select函數(shù)出錯 */ else if(ret == 0)    printf("超時\n"); /* 說明在我們設(shè)定的時間值1秒加500毫秒的時間內(nèi),socket的狀態(tài)沒有發(fā)生變化 */ else { /* 說明等待時間還未到1秒加500毫秒,socket的狀態(tài)發(fā)生了變化 */         printf("ret=%d\n", ret); /* ret這個返回值記錄了發(fā)生狀態(tài)變化的句柄的數(shù)目,由于我們只監(jiān)視了socket這一個句柄,所以這里一定ret=1,如果同時有多個句柄發(fā)生變化返回的就是句柄的總和了 */     /* 這里我們就應(yīng)該從socket這個句柄里讀取數(shù)據(jù)了,因為select函數(shù)已經(jīng)告訴我們這個句柄里有數(shù)據(jù)可讀 */    if(FD_ISSET(socket, &rdfds)) { /* 先判斷一下socket這外被監(jiān)視的句柄是否真的變成可讀的了 */            /* 讀取socket句柄里的數(shù)據(jù) */             recv(...);     } } | 
    
注意select函數(shù)的第一個參數(shù),是所有加入集合的句柄值的最大那個值還要加1。比如我們創(chuàng)建了3個句柄:
 
    
        
            | int sa, sb, sc; sa = socket(...); /* 分別創(chuàng)建3個句柄并連接到服務(wù)器上 */ connect(sa,...); sb = socket(...); connect(sb,...); sc = socket(...); connect(sc,...); FD_SET(sa, &rdfds);/* 分別把3個句柄加入讀監(jiān)視集合里去 */ FD_SET(sb, &rdfds); FD_SET(sc, &rdfds); | 
    
在使用select函數(shù)之前,一定要找到3個句柄中的最大值是哪個,我們一般定義一個變量來保存最大值,取得最大socket值如下:
    
        
            | int maxfd = 0; if(sa > maxfd)      maxfd = sa; if(sb > maxfd)     maxfd = sb; if(sc > maxfd)     maxfd = sc; | 
    
然后調(diào)用select函數(shù):
    
        
            | ret = select(maxfd + 1, &rdfds, NULL, NULL, &tv); /* 注意是最大值還要加1 */ | 
    
同樣的道理,如果我們要檢測用戶是否按了鍵盤進行輸入,我們就應(yīng)該把標準輸入0這個句柄放到select里來檢測,如下:
FD_ZERO(&rdfds);
FD_SET(0, &rdfds);
tv.tv_sec = 1;
tv.tv_usec = 0;
ret = select(1, &rdfds, NULL, NULL, &tv); /* 注意是最大值還要加1 */
if(ret < 0)
     perror("select");/* 出錯 */
else if(ret == 0)
     printf("超時\n"); /* 在我們設(shè)定的時間tv內(nèi),用戶沒有按鍵盤 */
else { /* 用戶有按鍵盤,要讀取用戶的輸入 */   
scanf("%s", buf); }
 
---------------------------------------------------------------------------------------------------------------------------------
select(I/O多工機制) 
表頭文件 #i nclude
#i nclude
#i nclude
定義函數(shù) int select(int n,fd_set * readfds,fd_set * writefds,fd_set * exceptfds,struct timeval * timeout);
函數(shù)說明 select()用來等待文件描述詞狀態(tài)的改變。參數(shù)n代表最大的文件描述詞加1,參數(shù)readfds、writefds 和exceptfds 稱為描述詞組,是用來回傳該描述詞的讀,寫或例外的狀況。底下的宏提供了處理這三種描述詞組的方式:
FD_CLR(inr fd,fd_set* set);用來清除描述詞組set中相關(guān)fd 的位
FD_ISSET(int fd,fd_set *set);用來測試描述詞組set中相關(guān)fd 的位是否為真
FD_SET(int fd,fd_set*set);用來設(shè)置描述詞組set中相關(guān)fd的位
FD_ZERO(fd_set *set);用來清除描述詞組set的全部位
參數(shù) timeout為結(jié)構(gòu)timeval,用來設(shè)置select()的等待時間,其結(jié)構(gòu)定義如下
struct timeval
{
time_t tv_sec;
time_t tv_usec;
};
返回值 如果參數(shù)timeout設(shè)為NULL則表示select()沒有timeout。
錯誤代碼 執(zhí)行成功則返回文件描述詞狀態(tài)已改變的個數(shù),如果返回0代表在描述詞狀態(tài)改變前已超過timeout時間,當有錯誤發(fā)生時則返回-1,錯誤原因存于errno,此時參數(shù)readfds,writefds,exceptfds和timeout的值變成不可預(yù)測。
EBADF 文件描述詞為無效的或該文件已關(guān)閉
EINTR 此調(diào)用被信號所中斷
EINVAL 參數(shù)n 為負值。
ENOMEM 核心內(nèi)存不足
范例 常見的程序片段:fs_set readset;
FD_ZERO(&readset);
FD_SET(fd,&readset);
select(fd+1,&readset,NULL,NULL,NULL);
if(FD_ISSET(fd,readset){……}
下面是linux環(huán)境下select的一個簡單用法
#include
#include
#include
#include
#include
#include
int main ()
{
int keyboard;
int ret,i;
char c;
fd_set readfd;
struct timeval timeout;
keyboard = open("/dev/tty",O_RDONLY | O_NONBLOCK);
assert(keyboard>0);
while(1)
{
timeout.tv_sec=1;
timeout.tv_usec=0;
FD_ZERO(&readfd);
FD_SET(keyboard,&readfd);
ret=select(keyboard+1,&readfd,NULL,NULL,&timeout);
if(FD_ISSET(keyboard,&readfd))
{
i=read(keyboard,&c,1);
if('\n'==c)
continue;
printf("hehethe input is %c\n",c);
if ('q'==c)
break;
}
}
}
用來循環(huán)讀取鍵盤輸入