1.select簡單介紹
①select:一次用來等待多個文件描述符,只負責等待
②select函數:
int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout)
③參數介紹:
- 第一個參數nfds表示最大的文件描述符+1
- fd_set是文件描述符集,readfds表示讀事件,writefds表示寫事件,exceptfds表示異常事件;
- 後四個參數都是輸入輸出型參數:中間三個參數在輸入時,比特位的內容表示關心的文件描述符事件,輸出時,比特位的內容表示哪個文件描述符已經就緒。
- 例如:在輸入時,readfds的第3個比特位爲1,表示只關心3號文件描述符的讀事件;在輸出時,readfds的第3個比特位爲1,表示3號文件描述符已經就緒;
- 最後一個參數timeout可以將select設置爲阻塞式等待、非阻塞式等待或按特定時間非阻塞等待;
④只要有任何一個事件就緒,select就會返回;
2.select服務器
①select服務器介紹
select()檢測到來自服務器端的connect()行爲,作爲輸入參數,readfds,writefds,exceptfds應該標記所有需要探測的“可讀事件”,“可寫事件”,“錯誤事件”的句柄,作爲輸出參數,readfds,writefds,exceptfds中保存了select()捕捉到所有事件的句柄值,使用FD_ISSET檢查哪些句柄發生了事件。
②編寫select服務器:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/select.h>
#include<fcntl.h>
int fds[1024];
static void Usage(const char* proc)
{//服務器的使用介紹
printf("%s[local_ip][local_post]\n",proc);
}
int startup(const char* _ip,int _port)
{
//創建套接字
int sock=socket(AF_INET,SOCK_STREAM,0);
if(sock<0)
{
perror("socket");
exit(2);
}
struct sockaddr_in local;
local.sin_family=AF_INET;
local.sin_port=htons(_port);
local.sin_addr.s_addr=inet_addr(_ip);
//綁定套接字
if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
{
perror("bind");
exit(3);
}
//監聽套接字
if(listen(sock,10))
{
perror("listen");
exit(4);
}
return sock;
}
int main(int argc,char* argv[])
{
if(argc!=3)
{
Usage(argv[0]);
return 1;
}
//創建監聽套接字
int listen_sock=startup(argv[1],atoi(argv[2]));
int nums=sizeof(fds)/sizeof(fds[0]);
int maxfd=-1;
int i=1;
for(;i<nums;i++)
{
fds[i]=-1;//從1開始清空文件描述符
}
//將listen_sock託管給select_server
fds[0]=listen_sock;
while(1)
{
struct timeval timeout={5,0};
fd_set rfds;
//清空rfds的所有位
FD_ZERO(&rfds);
maxfd=-1;
for(i=0;i<nums;i++)
{
if(fds[i]>0)
{
//標記可讀事件句柄,在rfds中設置文件描述符
FD_SET(fds[i],&rfds);
if(maxfd<fds[i])
{//獲取最大文件描述符
maxfd=fds[i];
}
}
}
switch(select(maxfd+1,&rfds,NULL,NULL,/*&timeout*/NULL))
{
case 0: printf("timeout...\n");
break;
case -1: perror("select");
break;
default:
{
for(i=0;i<nums;i++)
{
if(fds[i]<0)
{//遍歷rdfs
continue;
}
if(i==0 && FD_ISSET(listen_sock,&rfds))
{//listen_sock就緒
struct sockaddr_in client;
socklen_t len=sizeof(client);
//等待客戶端的連接
int new_sock=accept(listen_sock,(struct sockaddr*)&client,&len);
if(new_sock<0)
{
perror("accept");
continue;
}
//客戶端成功連接
printf("get new client:[%s:%d]\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
int j=1;
for(;j<nums;j++)
{
if(fds[j]==-1)
{
break;
}
}
if(j==nums)
{
printf("server full!\n");
close(new_sock);
}
else
{
fds[j]=new_sock;
}
}
else if(i>0 && FD_ISSET(fds[i],&rfds))
{//其他事件就緒
char buf[1024];
//讀取客戶端發送的數據
ssize_t s=read(fds[i],buf,sizeof(buf)-1);
if(s>0)
{
buf[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");
}
}
else
{}
}
}
break;
}
}
}
運行結果:
使用telnet連接select服務器,連接成功後給出提示,並且telnet向select發送數據:
當telent退出時,select給出連接斷開的提示:
3.select的優缺點
優點:
與多線程和多進程服務器相比,select一次等待多個文件描述符,等待時間變短,提高了性能;
缺點:
①select本身對能監控的文件描述符有上限,默認爲1024;
②每次使用select都要將後4個參數重新設置,性能變低;
③每次調用select都要遍歷所有fd,當fd較多時,開銷很大;
④select爲系統調用,每次調用都要將fd集合從用戶態拷貝到內核,開銷很大;