I/O复用
I/O复用是指一个进程或一个线程能够同时对多对文件描述符(sockfd)提供服务。那么服务器上的进程或线程如何对多个文件描述符统一监听,当任意一个文件描述符上有事件发生,其都能及时处理?
有三种方法,今天我着重介绍一下第一种
1.select
2.poll
3.epoll
select
int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);
其中,参数的意义是:nfds:最大文件描述符的值+1
readfds:用户感兴趣的可读事件的文件描述符集合
writefds:可读事件的文件描述符的集合
exceptfds:异常事件的文件描述符的集合
timeout:设置超时时间,如果timeout为NULL,则select一直阻塞
返回值:>0 返回就绪文件描述符的个数
==0 超时
==-1 出错
流程:1.将文件描述符设置到readfds,writefds,exceptfds
2.select返回后,知道哪些文件描述符就绪
其中想将文件描述符设置到readfds,writefds,exceptfds上面必须调用几个系统调用函数:
1.FD_ZERO(fd_set *set) 清空set的所有位
2.FD_SET(int fd,fd_set *set) 设置set的位fd
3.FD_CLR(int fd,fd_set *set) 清除set的位fd
而想知道哪些文件描述符已经就绪要借助下面的系统调用函数
4.FD_ISSET(int fd,fd_set *set) 测试set的位fd是否被设置
fd_set
fd_set的结构:
typedef struct
{
int fd_bits[32];
}fd_set;
实现
代码如下:
服务器:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
#include<assert.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/select.h>
#include<arpa/inet.h>
#include<netinet/in.h>
void Init(int *fds,int len) //初始化
{
int i=0;
for(;i<len;i++)
{
fds[i]=-1;
}
}
void Insert(int *fds,int fd,int len) //插入
{
int i=0;
for(;i<len;i++)
{
if(fds[i]==-1)
{
fds[i]=fd;
break;
}
}
}
void Delete(int *fds,int fd,int len) //删除
{
int i=0;
for(;i<len;i++)
{
if(fds[i]!=-1)
{
if(fds[i]==fd)
{
fds[i]=-1;
break;
}
}
}
}
int main()
{
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(8000);
ser.sin_addr.s_addr=inet_addr("127.0.0.1");
int ret=bind(sockfd,(struct sockaddr*)(&ser),sizeof(ser));
assert(ret!=-1);
listen(sockfd,5);
int fds[100];
Init(fds,100);
Insert(fds,sockfd,100); //将sockfd插入到fds数组中
fd_set readfds;
while(1)
{
FD_ZERO(&readfds);
int maxfd=-1;
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("error\n");
continue;
}
i=0;
for(;i<100;i++)
{
if(fds[i]!=-1 && FD_ISSET(fds[i],&readfds)) //探测到就绪文件描述符后,判断文件描述符是sockfd还是不是,如果是sockfd表示有客户端完成了三次握手,否则是表示客户端有数据到达
{
if(fds[i]==sockfd)
{
int client=sizeof(cli);
int c=accept(sockfd,(struct sockaddr*)(&cli),&client);
if(c<=0)
{
printf("error");
continue;
}
else
{
Insert(fds,c,100); //将accept所返回的套接字也要插入到fds中
}
}
else
{
char buff[128]={0}; //客户端有数据到达,读取数据
int c=fds[i];
int n=recv(c,buff,127,0);
if(n<=0)
{
close(c); //如果其中没有数据,就关闭文件描述符
Delete(fds,c,100); //并从fds删除这个文件描述符
continue;
}
else
{
printf("%d: %s\n",c,buff); //读取数据
send(c,"OK",2,0); //发送ok
}
}
}
}
}
}
客户端:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<assert.h>
int main()
{
int sockfd=socket(PF_INET,SOCK_STREAM,0);
assert(sockfd != -1);
struct sockaddr_in ser,cli;
ser.sin_family=AF_INET;
ser.sin_port=htons(8000);
ser.sin_addr.s_addr=inet_addr("127.0.0.1");
connect(sockfd,(struct sockaddr*)(&ser),sizeof(ser));
while(1)
{
printf("please input: ");
fflush(stdout);
char buff[128]={0};
fgets(buff,128,stdin);
buff[strlen(buff)-1] = '\0';
send(sockfd,buff,127,0);
if(strcmp(buff,"end")==0)
{
break;
}
char recvbuff[128]={0};
recv(sockfd,recvbuff,127,0);
printf("%s\n",recvbuff);
}
close(sockfd);
}
结果如下: