I/O多路轉接之select伺服器
(1)每次調⽤用select,都需要把fd集合從⽤使用者態拷貝到核心態,這個開銷在fd很多時會很⼤大 (2)同時每次調⽤用select都需要在核心遍歷傳遞進來的所有fd,這個開銷在fd很多時也很⼤大 (3)select⽀支援的⽂檔案描述符數量太⼩小了,預設是1024 select 伺服器
發生了改變就是讀事件寫事件就緒
檔案描述符通常關心讀事件、寫事件、異常事件
也可以關心至少一個或者有多個事件
select是系統呼叫介面
只負責等,一次等多個檔案描述符就緒之後select就會返回通知上層
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
select的引數:
nfds:等多個檔案描述符中最大的加一
後四個引數都是輸入輸出型引數
輸入型:哪些檔案描述符關心對應型別的事件
輸出型:哪些你所關心的檔案描述符對應得事件已經發生了
rdset,wrset,exset分別對應於需要檢測的可讀⽂檔案描述符的集合,可寫⽂檔案描述符的集 合及異 常⽂檔案描述符的集合。
struct timeval結構⽤用於描述⼀一段時間長度,如果在這個時間內,需要監視的描述符沒有事件 發⽣生則函式返回,返回值為0。
下⾯面的巨集提供了處理這三種描述片語的⽅方式: FD_CLR(inr fd,fd_set* set);⽤用來清除描述片語set中相關fd 的位 FD_ISSET(int fd,fd_set *set);⽤用來測試描述片語set中相關fd 的位是否為真 FD_SET(int fd,fd_set*set);⽤用來設定描述片語set中相關fd的位 FD_ZERO(fd_set *set);⽤用來清除描述片語set的全部位 引數timeout為結構timeval,⽤用來設定select()的等待時間,其結構定義如下:
如果引數timeout設為: NULL:則表⽰示select()沒有timeout,select將⼀一直被阻塞,直到某個⽂檔案描述符上發⽣生了 事件。 0:僅檢測描述符集合的狀態,然後⽴立即返回,並不等待外部事件的發⽣生。 特定的時間值:如果在指定的時間段⾥裡沒有事件發⽣生,select將超時返回
函式返回值: 執行成功則返回檔案描述詞狀態已改變的個數 如果返回0代表在描述詞狀態改變前已超過timeout時間,沒有返回; 當有錯誤發⽣生時則返回-1,錯誤原因存於errno,此時引數readfds,writefds,exceptfds和 timeout的值變成不可預測。錯誤值可能為: EBADF ⽂檔案描述詞為⽆無效的或該⽂檔案已關閉 EINTR 此調⽤用被訊號所中斷 EINVAL 引數n 為負值。 ENOMEM 核⼼心記憶體不⾜足 常見的程式⽚片段如下: fs_set readset; FD_SET(fd,&readset); select(fd+1,&readset,NULL,NULL,NULL); if(FD_ISSET(fd,readset)){…⋯…⋯}
理解select模型:
理解select模型的關鍵在於理解fd_set,為說明方便,取fd_set長度為1位元組,fd_set中的每⼀一bit 可以對應⼀一個⽂檔案描述符fd。則1位元組長的fd_set最⼤大可以對應8個fd。
(1)執⾏行fd_set set; FD_ZERO(&set);則set⽤用位表⽰示是0000,0000。
(2)若fd=5,執⾏行FD_SET(fd,&set);後set變為0001,0000(第5位置為1)
(3)若再加⼊入fd=2,fd=1,則set變為0001,0011
(4)執⾏行select(6,&set,0,0,0)阻塞等待
(5)若fd=1,fd=2上都發⽣生可讀事件,則select返回,此時set變為0000,0011。注意:沒有事件 發⽣生的fd=5被清空。 基於上⾯面的討論,可以輕鬆得出select模型的特點:
(1)可監控的檔案描述符個數取決與sizeof(fd_set)的值。我這邊服務 器上sizeof(fd_set)= 512,每bit表示一個⽂檔案描述符,則我伺服器上⽀支援的最⼤大⽂檔案描述符是512*8=4096。據說 可調,另有說雖 然可調,但調整上限受於編譯核心時的變數值。
(1)可以有效突破select可監控的⽂檔案描述符上 限。
(2)將fd加⼊入select監控集的同時,還要再使⽤用一個數據結構array儲存放到select監控集 中的fd,一是⽤用於再select 返回後,array作為源資料和fd_set進⾏行FD_ISSET判斷。二是select 返回後會把以前加⼊入的但並無事件發⽣生的fd清空,則每次開始 select前都要重新從array取得fd 逐⼀一加入(FD_ZERO最先),掃描array的同時取得fd最⼤大值maxfd,⽤用於select的第⼀一個 參 數。
(3)可見select模型必須在select前迴圈array(加fd,取maxfd),select返回後迴圈array (FD_ISSET判斷是否有時間發⽣生)。
#include<stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
int fds[sizeof(fd_set)*8];
static void Usage(char*proc)
{
printf("%s [local_ip] [local port]\n");
}
int startUp(char*ip,int port)
{
int sock=socket(AF_INET,SOCK_STREAM,0);
if(sock<0)
{
perror("socket");
exit(2);
}
struct sockaddr_in server;
server.sin_family=AF_INET;
server.sin_port=htons(atoi(port));
server.sin_addr.s_addr=inet_addr(ip);
if(bind(sock,(struct sockaddr*)&server,sizeof(struct sockaddr_in))<0)
{
perror("bind");
exit(3);
}
if(listen(sock,10)<0)
{
perror("listen");
exit(4);
}
return sock;
}
int main(int argv,char*argc[])
{
if(argv!=3)
{
Usage(argc[0]);
return 1;
}
int listen_sock=startUp(argc[1],argc[2]);
int nums=sizeof(fd_set)*8;
fd_set rds;
int i=0;
for(i=0;i<nums;i++)
{
fds[i]=-1;
}
while(1)
{
int max=-1;
struct timeval timeout={5,0};
fds[0]=listen_sock;
for(i=0;i<nums;i++)
{
if(fds[i]>-1)
{
FD_SET(fds[i],&rds);//在rdss設定所要關心的檔案描述符對應的事件
}
if(max<fds[i])
{
max=fds[i];
}
}
switch(select(max+1,&rds,NULL,NULL,&timeout))// 執行成功則返回檔案描述詞狀態已改變的個數
{
case 0:
printf("timeout...\n");
break;
case -1:
perror("select");
break;
default:
for(i=0;i<nums;i++)
{
if(i==0&&FD_ISSET(fds[i],&rds))//判斷listen_sock描述符上對應的事件是否就緒
{
struct sockaddr_in client;
socklen_t len=sizeof(client);
int newsock=accept(listen_sock,(struct socketaddr*)&client,&len);//繼續取出將要關心的對應的檔案描述符上的對應的事件
if(newsock<0)
{
perror("accept");
return 2;
}
else
{
int j=0;
for(j=0;j<nums;j++)
{
if(fds[j]==-1)
{
break;
}
}
if(j==nums)
{
close(newsock);
}
else
{
fds[j]=newsock;//將新的所要關心的檔案描述符對應的事件放到fds合適的位置
}
}
}
else if(i!=0&&FD_ISSET(fds[i],&rds))//如果不是監聽套接字但是是其他檔案描述符對應的讀事件就緒了
{
char buf[1024];
ssize_t s=read(fds[i],buf,sizeof(buf)-1);
if(s>0)
{
printf("client say:%s\n",buf);
}
else if(s==0)
{
printf("client quit!\n");
close(fds[i]);
fds[i]=-1;
}
else
{
perror("read");
close(fds[i]);
fds[i]=-1;
}
}
}
}
}
return 0;
}
(1)每次調⽤用select,都需要把fd集合從使用者態拷貝到核心態,這個開銷在fd很多時會很大
(2)同時每次呼叫select都需要在核心遍歷傳遞進來的所有fd,這個開銷在fd很多時也很大 ’
(3)select⽀支援的⽂檔案描述符數量太小了,預設是1024