I/O复用—-select系统调用
先上代码(服务器)
ser.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<sys/select.h>
#define MAX 1024
int main()
{
int listenfd=socket(AF_INET,SOCK_STREAM,0);
assert(listenfd!=-1);
struct sockaddr_in ser,cli;
ser.sin_family=AF_INET;
ser.sin_port=htons(6000);
ser.sin_addr.s_addr=inet_addr("127.0.0.1");
int res=bind(listenfd,(struct sockaddr*)&ser,sizeof(ser));
assert(res!=-1);
listen(listenfd,5);
fd_set reads; //定义**可读事件**对应的文件描述符集合
int fds[MAX]; //初始化一个数组,全部赋值-1
int i=0;
for(;i<MAX;++i)
{
fds[i]=-1;
}
fds[0]=listenfd; //第一个放listenfd,之后的全放c
while(1)
{
FD_ZERO(&reads); //清零reads所有位
FD_SET(listenfd,&reads); //设置fdset的位fd,即将某位置1
int maxfd=-1; //select第一个参数maxfd+1
for(i=0;i<MAX;++i) //将数组中的描述符放入reads中
{
if(fds[i]!=-1)
{
FD_SET(fds[i],&reads);
if(fds[i]>maxfd)
{
maxfd=fds[i]; //最大的文件描述符+1(监听的最远位置,之后的肯定都为-1,不需要监听)
}
}
}
int n=select(maxfd+1,&reads,NULL,NULL,NULL);//参数:最远检测到哪(不会超过1024;2可读,3可写,4异常事件描述符集合;5超时时间)
// printf("select\n");
if(n==0) //若超时时间内没有任何文件描述就位,select返回0
{
printf("time out\n");
continue;
}
else if(n==-1) //失败返回-1
{
printf("select error\n");
break;
}
else
{
for(i=0;i<MAX;++i) //对每一位做处理
{
if(fds[i]!=-1&&FD_ISSET(fds[i],&reads)) //有连接并且就绪,进入循环;FD_ISSET()测试某位是否就绪。
{
if(fds[i]==listenfd) //listenfd
{
int len=sizeof(cli);
int c=accept(listenfd,(struct sockaddr*)&cli,&len);
int j=0;
for(;j<MAX;++j) //得到文件描述符c并放入fds[]数组,到上面的while循环再将fds[]拷贝到reads
{
if(fds[j]==-1)
{
fds[j]=c;
break;
}
}
}
else //为文件描述符c,接收数据并发送ok
{
char buff[128]={0};
int num=recv(fds[i],buff,127,0);
if(num<=0)
{
printf("break\n");
close(fds[i]);
fds[i]=-1;
continue;
}
printf("%d:%s\n",fds[i],buff);
send(fds[i],"ok",2,0);
}
}
}
}
}
}
select优势:
select能同时监听多个文件描述符上的可读、可写、异常三类事件
select劣势
只能关注这3类事件;
所能监听的文件描述符的数量1024(0——1023);
每次调用前,都必须重复传入描述符事件集;
每次都会将所有文件描述符返回,必须循环检测哪些就绪,哪些未就绪;
采用轮询方式监听文件描述符;
会存在两次拷贝过程:第一次调用时,从用户空间拷贝到内核;第二次返回,从内核拷贝到用户空间;