I/O多路转接有三种实现方式:select , poll ,epoll。今天我们来了解一下select函数实现I/O多路转接。
select系统调用是用来让我们的程序监视多个文件句柄的状态变化的。程序会停在select这里等待,直到被监视的文件句柄有一个或多个发生了状态改变。
参数1:nfds是需要监视的最大的文件描述符值+1;
参数2:readfds是需要检测的可读文件描述符的集合;
参数3:writefds是需要检测的可写文件描述符的集合 ;
参数4:exceptfds是需要检测的异常文件描述符的集合;
参数5:timeout是结构体timeval,用来设置select()的等待时间;
注:fd_set与struct timeval都是输入输出型参数,fd_set作输入时表示关心某个fd,做输出时表示哪个fd就绪。
timeout有三种情况:
1、NULL:仅在有一个描述符准备好I/O时才返回;
2、timeout为指定时间:在有一个描述符准备好I/O时返回,但是不超过由该参数所指向的timeval结构中指定的秒数和微妙数。
3、0:检查描述符后立即返回,这称为轮询即该参数指向一个timeval结构,其中的定时器值为0;
—————————————————————————————————————————————
下面的宏提供了处理fd_set的这三种描述集的方式:
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的全部位;
函数返回值:
1、成功:文件描述词状态已改变的个数;
2、0:在描述词状态改变前已超过timeout时间,没有返回;
3、-1:有错误发生,错误原因存于errno;
select模型的特点:
1、可监控的文件描述符个数取决于sizeof(fd_set)的值;
2、将fd加入select监控集的同时,还要再使用一个数据结构array保存放到select监控集中的fd,一:是用于在select 返回后,array作为源数据和fd_set进行FD_ISSET判断是否有事件发生;二:是select返回后会把以前加入的但并无事件发生的fd清空,则每次开始 select前都要重新从array取得fd逐一加入(FD_ZERO最先),扫描array的同时取得fd最大maxfd,给其+1用于select的第一个参数。
select模型的缺点:
1、每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大;
2、每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大;
3、select支持的文件描述符数量太少了,默认是1024。
使用select实现一个TCP服务器:
//server端:
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/select.h>
#include<stdlib.h>
int array_rfds[1024];
static void Usage(const char* proc)
{
printf("Usage:%s [local_ip][local_port]\n",proc);
}
int Startup(const char* _ip,int _port)
{
int sock= socket(AF_INET,SOCK_STREAM,0);
if(sock<0)
{
perror("socket");
exit(1);
}
int flag = 1;
setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&flag,sizeof(flag));
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port= htons(_port);
server.sin_addr.s_addr = inet_addr(_ip);
if(bind(sock,(struct sockaddr*)&server,sizeof(server))<0)
{
perror("bind");
exit(2);
}
if(listen(sock,10)<0)
{
perror("listen");
exit(3);
}
return sock;
}
int main(int argc,const char* argv[])
{
if(argc!=3)
{
Usage(argv[0]);
return 1;
}
int listen_sock = Startup(argv[1],atoi(argv[2]));
fd_set rfds;
int maxfd = 0;
int array_rsize = sizeof(array_rfds)/sizeof(array_rfds[0]);
array_rfds[0] = listen_sock;
int i = 1;
for(;i<array_rsize;++i)
{
array_rfds[i] = -1;
}
while(1)
{
struct timeval timeout={0,0};
FD_ZERO(&rfds);
maxfd = -1;
for(i=0;i<array_rsize;++i)
{
if(array_rfds[i]>0)
{
FD_SET(array_rfds[i],&rfds);
if(array_rfds[i]>maxfd)
maxfd = array_rfds[i];
}
}
switch(select(maxfd+1,&rfds,NULL,NULL,/*&timeout*/NULL))
{
case -1:
perror("select");
break;
case 0:
printf("timeout...");
break;
default:
{
int j = 0;
for(;j<array_rsize;++j)
{
if(array_rfds[j]<0)
continue;
if(j==0 && FD_ISSET(array_rfds[j],&rfds))
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
int newfd = accept(array_rfds[j],\
(struct sockaddr*)&client,&len);
if(newfd<0)
{
perror("accept");
continue;
}
printf("new client--> %s:%d\n",\
inet_ntoa(client.sin_addr),\
ntohs(client.sin_port));
int k= 1;
for(;k<array_rsize;++k)
{
if(array_rfds[k]<0)
{
array_rfds[k] = newfd;
break;
}
}
if(k == array_rsize)
{
close(newfd);
}
}
else if(j!=0 && FD_ISSET(array_rfds[j],&rfds))
{
char buf[1024];
ssize_t s = read(array_rfds[j],buf,sizeof(buf)-1);
if(s>0)
{
buf[s] = 0;
printf("client say: %s\n",buf);
}
else if(s ==0)
{
printf("client is quit!!\n");
close(array_rfds[j]);
array_rfds[j] = -1;
}
else
{
perror("read");
close(array_rfds[j]);
array_rfds[j] = -1;
}
}
}
}
break;
}
}
return 0;
}
//client端:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static void usage(const char* proc)
{
printf("Usage:%s[server_ip][server_port]\n",proc);
}
int main(int argc,char *argv[])
{
if(argc != 3)
{
usage(argv[0]);
return 1;
}
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock<0)
{
perror("socket");
return 2;
}
struct sockaddr_in peer;
peer.sin_family = AF_INET;
peer.sin_port = htons(atoi(argv[2]));
peer.sin_addr.s_addr = inet_addr(argv[1]);
int ret = connect(sock,(struct sockaddr*)&peer,sizeof(peer));
if(ret<0)
{
perror("connect");
printf("%s\n",strerror(ret));
return 3;
}
char buf[1024];
while(1)
{
printf("please enter: ");
fflush(stdout);
ssize_t s=read(0,&buf,sizeof(buf));
if(s<0)
{
perror("read");
return 4;
}
buf[s-1]=0;
write(sock,&buf,strlen(buf));
printf("server echo: %s\n",buf);
}
close(sock);
return 0;
}
client1:
client2: