I/O复用——一个进程或线程能同时对多个文件描述符(socket)提供服务。
select
-
记录每种时间的结构,在数组按位来记录关注的文件描述符上的事件
-
每次最多可以监听1024个文件描述符,并且其最大值是1023
-
select函数返回时,通过传递的结构体变量将结果带回,并且内核会修改用户变量
int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *execfds,struct timeval *timeout);
nfds:最大文件描述符的值+1(为了提高select底层调用的效率)
readfds:用户感兴趣的可读事件的文件描述符集合
writefds:可写事件的文件描述符集合
execfds:异常事件的文件描述符集合
timeout:设置超时时间,如果timeout为NULL,则select一直阻塞
返回值:-1 出错,返回值为0 时超时,返回值>0时,返回的是就绪的文件描述符个数
每次select调用之前都必须重新设置readfds、writefds、execfds
这里就存在两个问题
1、如何将文件描述符分别设置到readfds、writefds、execfds
这三个参数是fd_set结构指针类型,其结构体定义如下:
由以上定义可见,fd_set结构体仅包含一个整型数组,该数组的每个元素的每一位标记一个文件描述符。fd_set能容纳的文件描述符数量由FD_SETSIZE指定,这就限制了select能同时处理的文件描述符的总量。
由于位操作过于频繁,,我们应该使用下面的一系列宏来访问fd_set结构体中的为:
FD_ZERO(fd-set *fdset);//清除fdset的所有位
FD_SET(int fd,int fd_set *fdset);//设置fdset的位fd
FD_CLR(int fd,fd_set *fdset);//清除fdset的位fd
int FD_ISSET(int fd,fd_set *fdset);//测试fdset的位fd是否被设置
2、select返回后,如何知道哪些文件描述符就绪
select每次都会将所有的文件描述符返回(包括就绪的和未就绪的)
select返回后还必须循环探测具体哪些是就绪的文件描述符,探测就绪文件描述符的时间复杂度为O(n)
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<assert.h>
#include<sys/select.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<netinet/in.h>
void Init_fds(int *fds,int len)
{
int i=0;
for(;i<len;++i)
{
fds[i]=-1;
}
}
void Insert_fd(int *fds,int fd,int len)
{
int i=0;
for(;i<len;++i)
{
if(fds[i]==-1)
{
fds[i]==fd;
break;
}
}
}
void Delete_fd(int *fds,int fd,int len)
{
int i=0;
for(;i<len;++i)
{
if(fds[i]==fd)
{
fds[i]=-1;
break;
}
}
}
int main()
{ //TCP服务器设置
int sockfd=socket(AF_INET,SOCK_STREAM,0);
assert(sockfd!=-1);
struct sockaddr_in ser,cli;
memset(&ser,0,sizeof(ser));
ser.sin_family=AF_INET;
ser.sin_port=htons(6000);
ser.sin_addr.s_addr=inet_addr("127.0.0.1");
int res=bind(sockfd,(struct sockaddr*)&ser,sizeof(ser));
assert(res!=-1);
listen(sockfd,5);
//将sockfd添加到fds中
fd_set readfds;
int fds[100];
Init_fds(fds,100);
Insert_fd(fds,sockfd,100);
int maxfd=-1;
//启动while循环
while(1)
{
int maxfd=-1;//定义一个maxfd,每次添加时把maxfd找出来
FD_ZERO(&readfds);
int i=0;
for(;i<100;++i)
{
if(fds[i]!=-1)
{
if(fds[i]>maxfd)
{
maxfd=fds[i];
}
FD_SET(fds[i],&readfds);//将fds中记录的值设置到readfds上
}
}
int n=select(maxfd+1,&readfds,NULL,NULL,NULL);//启动select完成监听
if(n<=0)
{
printf("select fail\n");
continue;
}
for(i=0;i<100;++i)//循环探测那些文件描述符就绪
{
if(fds[i]!=-1&&FD_ISSET(fds[i],&readfds))
{
if(fds[i]==sockfd)
{
int len=sizeof(cli);
int c=accept(sockfd,(struct sockaddr*)&cli,&len);//有客户端完成了三次握手,accept将c取出
if(c<0)
{
printf("one client link error\n");
continue;
}
Insert_fd(fds,c,100);//将返回的c插入到fds中
}
else//客户端有数据到达
{
int fd=fds[i];
char buff[128]={0};
int n=recv(fd,buff,127,0);
if(n<=0)
{
close(fd);
Delete_fd(fds,fd,100);
continue;
}
printf("%d: %s\n",fd,buff);
send(fd,"OK",2,0);
}
}
}
}
}