阻塞式I/O編程有兩個特點:
一、如果一個發現I\O有輸入,讀取的過程中,另外一個也有了輸入,這時候不會產生任何反應.這就需要你的程式語句去用到select函數的時候才知道有資料輸入。
二、程式去select的時候,如果沒有資料輸入,程式會一直等待,直到有資料位元置,也就是程式中無需迴圈和sleep。
Select在Socket編程中還是比較重要的,可是對於初學Socket的人來說都不太愛用Select寫程式,他們只是習慣寫諸如connect、accept、recv或recvfrom這樣的阻塞程式
(所謂阻塞方式block,顧名思義,就是進程或是線程執行到這些函數時必須等待某個事件的發生,如果事件沒有發生,進程或線程就被阻塞,函數不能立即返回)。
可是使用Select就可以完成非阻塞(所謂非阻塞方式non-block,就是進程或線程執行此函數時不必非要等待事件的發生,一旦執行肯定返回,以傳回值的不同來反映函數的執行情況,如果事件發生則與阻塞方式相同,若事件沒有發生則返回一個代碼來告知事件未發生,而進程或線程繼續執行,所以效率較高)方式工作的程式,它能夠監視我們需要監視的檔案描述符的變化情況——讀寫或是異常。
下面man下select函數的:
/* According to POSIX.1-2001 */
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, structtimeval *timeout);
第一, struct fd_set可以理解為一個集合,這個集合中存放的是檔案描述符(filedescriptor),即檔案控制代碼,這可以是我們所說的普通意義的檔案,當然Linux下任何裝置、管道、FIFO等都是檔案形式,全部包括在內,所以毫無疑問一個socket就是一個檔案,socket控制代碼就是一個檔案描述符。
fd_set集合可以通過一些宏由人為來操作,
比如
清空集合FD_ZERO(fd_set*);
將一個給定的檔案描述符加入集合之中FD_SET(int,fd_set *);
將一個給定的檔案描述符從集合中刪除FD_CLR(int,fd_set*);
檢查集合中指定的檔案描述符是否可以讀寫FD_ISSET(int,fd_set* )。
第二,structtimeval是一個大家常用的結構,用來代表時間值,有兩個成員,一個是秒數,另一個是毫秒數。
具體解釋select的參數:
int maxfdp是一個整數值,是指集合中所有檔案描述符的範圍,即所有檔案描述符的最大值加1,不能錯!
fd_set *readfds是指向fd_set結構的指標,這個集合中應該包括檔案描述符,我們是要監視這些檔案描述符的讀變化的,即我們關心是否可以從這些檔案中讀取資料了,如果這個集合中有一個檔案可讀,select就會返回一個大於0的值,表示有檔案可讀,如果沒有可讀的檔案,則根據timeout參數再判斷是否逾時,若超出timeout的時間,select返回0,若發生錯誤返回負值。可以傳入NULL值,表示不關心任何檔案的讀變化。
fd_set *writefds是指向fd_set結構的指標,這個集合中應該包括檔案描述符,我們是要監視這些檔案描述符的寫變化的,即我們關心是否可以向這些檔案中寫入資料了,如果這個集合中有一個檔案可寫,select就會返回一個大於0的值,表示有檔案可寫,如果沒有可寫的檔案,則根據timeout參數再判斷是否逾時,若超出timeout的時間,select返回0,若發生錯誤返回負值。可以傳入NULL值,表示不關心任何檔案的寫變化。
fd_set *errorfds同上面兩個參數的意圖,用來監視檔案錯誤異常。
struct timeval* timeout是select的逾時時間,這個參數至關重要,它可以使select處於三種狀態,
第一, 若將NULL以形參傳入,即不傳入時間結構,就是將select置於阻塞狀態,一定等到監視檔案描述符集合中某個檔案描述符發生變化為止;
第二, 第二,若將時間值設為0秒0毫秒,就變成一個純粹的非阻塞函數,不管檔案描述符是否有變化,都立刻返回繼續執行,檔案無變化返回0,有變化返回一個正值;
第三, timeout的值大於0,這就是等待的逾時時間,即select在timeout時間內阻塞,逾時時間之內有事件到來就返回了,檔案無變化返回0,有變化返回一個正值;
傳回值:
負值:select錯誤
正值:某些檔案可讀寫或出錯
0:等待逾時,沒有可讀寫或錯誤的檔案
舉個簡單的例子:
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/select.h>
#include<stdio.h>
#include<stdlib.h>
int main()
{
fd_setreadfds;
FD_ZERO(&readfds);
FD_SET(STDIN_FILENO,&readfds);
intret;
charbuf[256]={0};
structtimeval tv={5,1000};
// tv.tv_sec=5;
// tv.tv_usec=1000;
ret=select(STDIN_FILENO+1,&readfds,NULL,NULL,&tv);
//ret=select(STDIN_FILENO+1,&readfds,NULL,NULL,NULL);
//ret=select(STDIN_FILENO+1,&readfds,NULL,NULL,0);
printf("ret=%d\n",ret);
if(ret==-1)
{
perror("selsecterror ");
exit(EXIT_FAILURE);
}
elseif(ret)
{
if(FD_ISSET(STDIN_FILENO,&readfds))
read(STDIN_FILENO,buf,256);
printf("readfrom stdin msg : %s\n",buf);
}
else
printf("timeout\n");
return0;
}
運行結果:3中狀態:
1. 錯誤。
2. 逾時,在指定的時間內沒有檢測到使用者的輸入。Selsect()返回0
3.使用者輸入了,即檢測到了標準輸入已經準備好了
下面我們看一下socket編程中select()函數的使用。
在此之貼出select函數部分。
用戶端:
while(1)
{
FD_ZERO(&readfds);
FD_SET(sockfd,&readfds);
FD_SET(STDIN_FILENO,&readfds);
maxfd=sockfd;
printf("before select %d\n",readfds);
if((ret=select(maxfd+1,&readfds,NULL,NULL,NULL))==-1)
{
perror("select:");
exit(EXIT_FAILURE);
}
//哪個檔案描述符準備好了就將那個所對應的位設定為1,其他的設定為0
//eg:socket=3準備好了則readfds為00000100(8)
//select 函數的傳回值為:準備好的檔案描述符的個數。
DEBUG("ret=%d\n",ret);
printf("after select %d\n",readfds);
if(FD_ISSET(sockfd,&readfds))
{ printf("sockfd select %d\n",readfds);
if(read(sockfd,read_buf,sizeof(read_buf))<=0)
{
perror("read <=0 ");
break;
}
printf("%s\n",read_buf);
memset(read_buf,0,sizeof(read_buf));
}
if(FD_ISSET(STDIN_FILENO,&readfds)) //有標準輸入描述符準備好了
{ printf("STDIN_FILENO select %d\n",readfds);
read(STDIN_FILENO,buf,sizeof(buf));
//或 scanf("%s",buf);//從標準輸入裝置的緩衝區中取出內容放到buf中
write(sockfd, buf, strlen(buf)+1);
}
}
服務端:
void server_write_read(int sockfd)
{
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(sockfd,&rfds);
int i, maxfd = sockfd,ret=-1;
int newfd;
struct sockaddr_in client;
socklen_t len = sizeof(client);
char buf[MAXBUF] = {'\0'};
while(1)
{
FD_ZERO(&rfds); //clear set
for(i = 0; i < MAXCLIENT; i++)
{
// add fd to set
if(connfd[i].fd != 0)
{
FD_SET(connfd[i].fd ,&rfds);
}
//get tje max fd
if(connfd[i].fd != 0 &&connfd[i].fd> maxfd)
{
maxfd=connfd[i].fd;
}
}
FD_SET(sockfd,&rfds);
printf("server_write_read before select %d\n",rfds);
if((ret=select(maxfd+1,&rfds,NULL,NULL,NULL))==-1)
{
perror("select:");
continue;
}
printf("server_write_read after select %d\n",rfds);
if(FD_ISSET(sockfd, &rfds))
{ printf("server_write_read sockfd %d\n",rfds);
newfd = accept(sockfd, (struct sockaddr *)&client, &len);
if(-1 == newfd)
system_error("accept");
else
printf("welcome %s was connected.\n", inet_ntoa(client.sin_addr));
DEBUG("newfd=%d\n",newfd);
//save socket description
for(i = 0; i < MAXCLIENT; i++)
{
if(connfd[i].fd == 0)
{
connfd[i].fd = newfd;
break;
}
}
}
for(i = 0; i < MAXCLIENT; i++)
{
if(FD_ISSET(connfd[i].fd, &rfds))
{
if(connfd[i].fd == 0)continue;
if ((read(connfd[i].fd, buf, MAXBUF)) <= 0)
{DEBUG("server read <=0\n");
for(i = 0; i < MAXCLIENT; i++)
{
if(connfd[i].fd==newfd)
{
connfd[i].fd=0;
break;
}
}
close(newfd);
FD_CLR(newfd, &rfds);
}
else
{
int j=0;
printf("////////// buf=%s\n",buf);
for(; chat_func[j].func; j++)
{
if (buf[0] == chat_func[j].protocol)
{
chat_func[j].func(buf, connfd[i].fd);
break;
}
}
memset(buf,0,MAXBUF);
}
}
}
}
}