I/O复用
第十三章、I/O复用
总而言之:需要处理多个文件描述符时,就用io复用
1.select方法
1.select方法的应用-使用select方法实现服务器并发处理
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<fcntl.h>
#include<sys/select.h>
#include<sys/time.h>
#define MAXFD 100
void fds_init(int fds[])
{
int i=0;
for(;i<MAXFD;++i)
{
fds[i] = -1;
}
}
void fds_add(int fds[],int fd)
{
int i=0;
for(;i<MAXFD;++i)
{
if(fds[i] == -1)
{
fds[i]=fd;
break;
}
}
}
void fds_del(int fds[],int fd)
{
int i=0;
for(;i<MAXFD;++i)
{
if(fds[i]==fd)
{
fds[i] = -1;
break;
}
}
}
int create_socket();
int main()
{
int sockfd = create_socket();
assert(sockfd != -1);
int fds[MAXFD];
fds_init(fds);
fds_add(fds,sockfd);
fd_set fdset;//把所有文件描述符放进集合中
while(1)
{
FD_ZERO(&fdset);
int maxfd=-1;
int i=0;
for(;i<MAXFD;++i)
{
if(fds[i]!=-1)
{
FD_SET(fds[i],&fdset);
if(maxfd<fds[i])
{
maxfd =fds[i];//找到数组中描述符最大的那个
}
}
}
struct timeval tv ={5,0};
int n=select(maxfd+1,&fdset,NULL,NULL,&tv);
if(n<0)
{
printf("select error\n");
continue;
}
else if(n==0)
{
printf("time out\n");
continue;
}
else
{
int i=0;
for(;i<MAXFD;++i)
{
if(fds[i] == -1)
{continue;}
if(FD_ISSET(fds[i],&fdset))//检测当前描述符上有没有数据就>绪
{
//处理分两类,对于socket套接字(监听套接字)采用accept()方法
//对于连接套接字,采用recv()方法
if(fds[i]==sockfd)
{
struct sockaddr_in caddr;
int len= sizeof(caddr);
int c=accept(sockfd,(struct sockaddr*)&caddr,&len);
if (c==-1)
{
continue;
}
printf("accept c=%d\n",c);
fds_add(fds,c);
}
else
{
char buff[128]={0};
int res =recv(fds[i],buff,127,0);
if(res <= 0)//对方出错/关闭
{
close(fds[i]);
fds_del(fds,fds[i]);
printf("one client over\n");
}
else
{
printf("buff(c=%d)=%s\n",fds[i],buff);
send(fds[i],"ok",2,0);
}
}
}
}
}
}
}
int create_socket()
{
int sockfd =socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1)
{
return -1;
}
struct sockaddr_in saddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family=AF_INET;
saddr.sin_port=htons(6000);
saddr.sin_addr.s_addr=inet_addr("127.0.0.1");
int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
if(res == -1)
{
return -1;
}
res = listen(sockfd,5);
if(res == -1)
{
return -1;
}
return sockfd;
}
2.select方法的应用-使用select方法实现客户端并发处理
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
int main()
{
int sockfd =socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1)
{
return -1;
}
struct sockaddr_in saddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family=AF_INET;
saddr.sin_port=htons(6000);
saddr.sin_addr.s_addr=inet_addr("127.0.0.1");
int res = connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
if(res == -1)
{
return -1;
}
while(1)
{
printf("input:\n");
char buff[128]={0};
fgets(buff,127,stdin);//从键盘获取数据
if(strncmp(buff,"end",3) == 0)
{
break;
}
send(sockfd,buff,strlen(buff)-1,0);//把键盘上接收到的数据发送给服务器
memset(buff,0,128);
recv(sockfd,buff,127,0);//将服务器返回的打印出来
printf("buff=%s\n",buff);
}
close(sockfd);
}
2.poll方法
int poll(struct pollfd *fds,nfds_t nfds,int timeout);
//poll系统调用成功后返回就绪文件描述符的总数,超时返回0,失败返回-1
服务器端代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<fcntl.h>
#include<assert.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<poll.h>
#define MAXFD 10
void fds_init(struct pollfd fds[])
{
int i=0;
for(;i<MAXFD;++i)
{
fds[i].fd = -1;
fds[i].events = 0;//注册的事件
fds[i].revents = 0;//内核反馈给我们的事件
}
}
void fds_add(struct pollfd fds[],int fd)
{
//在此代码中,我们只关注读事件因此将其写死
int i=0;
for(;i<MAXFD;++i)
{
if(fds[i].fd == -1)
{
fds[i].fd = fd;
fds[i].events = POLLIN; //读事件r
fds[i].revents = 0;
break;
}
}
}
void fds_del(struct pollfd fds[],int fd)
{
int i=0;
for(;i<MAXFD;++i)
{
if(fds[i].fd == fd)
{
fds[i].fd = -1;
fds[i].events = 0;
fds[i].revents = 0;
}
}
}
int create_socket();
int main()
{
int sockfd = create_socket();
assert(sockfd != -1);
//由于是对应多个客户端,因此我们要把文件描述符收集起来
//之后再来检测有没有我们关心的事件产生
struct pollfd fds[MAXFD]; //将文件描述符存入数组中
fds_init(fds);
fds_add(fds,sockfd);
while(1)
{
int n=poll(fds,MAXFD,5000);//时间单位是ms,会阻塞住,直到超时或者有事件返>回
//也可以将其写为-1,就是永久阻塞住,直到有时间返回
if(n== -1)
{
continue;
}
else if(n==0)
{
printf("time out\n");
continue;
}
else
{
//检测哪个描述符上有数据
int i = 0;
for(;i<MAXFD;++i)
{
if(fds[i].fd == -1)
{
continue;
}
if(fds[i].revents&POLLIN)//判断返回的事件中有没有pollin读>事件
{
if(fds[i].fd == sockfd)
{
//accept
struct sockaddr_in caddr;
int len = sizeof(caddr);
int c=accept(sockfd,(struct sockaddr*)&caddr,&len);
if(c<0)
{
continue;
}
printf("accept c=%d\n",c);
fds_add(fds,c);
}
else
{
//recv
char buff[128]={0};
int num =recv(fds[i].fd,buff,127,0);
if(num<=0)
{
close(fds[i].fd);
fds_del(fds,fds[i].fd);
printf("client close\n");
continue;
}
printf("buff(%d)=%s\n",fds[i].fd,buff);
send(fds[i].fd,"ok",2,0);
}
}
//if(fds[i].revents&POLLOUT)//判断是否有写事件
}
}
}
}
int create_socket()
{
int sockfd =socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1)
{
return -1;
}
struct sockaddr_in saddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family=AF_INET;
saddr.sin_port=htons(6000);
saddr.sin_addr.s_addr=inet_addr("127.0.0.1");
int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
if(res == -1)
{
return -1;
}
res = listen(sockfd,5);
if(res == -1)
{
return -1;
}
return sockfd;
}
客户端代码与之前的一模一样,略
3.epoll
1.epoll各接口的使用
2.LT与ET模式
2.1.LT模式下的服务器代码(默认情况下是LT模式)
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<fcntl.h>
#include<assert.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/epoll.h>
#define MAXFD 10
//此时只关注读事件,因此不传入事件
void epoll_add(int epfd ,int fd)
{
struct epoll_event ev;
ev.events = EPOLLIN;//read
ev.data.fd=fd;
if(epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev) == -1)
{
perror("epoll ctl add err");
}
}
void epoll_del(int epfd,int fd)
{
if(epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL) == -1)
{
perror("epoll ctl del err");
}
}
int create_socket();
int main()
{
int sockfd = create_socket();
assert(sockfd != -1);
//由于是对应多个客户端,因此我们要把文件描述符收集起来
//之后再来检测有没有我们关心的事件产生
int epfd = epoll_create(MAXFD);
assert(epfd!=-1);
epoll_add(epfd,sockfd);
//往内核实现表中添加元素,检测是否有数据
//获取就绪描述符
struct epoll_event evs[MAXFD];
while(1)
{
int n=epoll_wait(epfd,evs,MAXFD,5000);//5000ms=5s
if(n==-1)
{
perror("epoll wait err\n");
}
else if(n==0)
{
printf("time out\n");
}
else
{
int i=0;
for(;i<n;++i)
{
int fd=evs[i].data.fd;
if(evs[i].events&EPOLLIN)
{
//描述符不同处理方法不同
if(fd==sockfd)
{
//accept
struct sockaddr_in caddr;
int len = sizeof(caddr);
int c=accept(sockfd,(struct sockaddr*)&caddr,&len);
if(c<0)
{
continue;
}
printf("accept c=%d\n",c);
epoll_add(epfd,c);
}
else
{
//recv
char buff[128]={0};
int num =recv(fd,buff,127,0);
if(num<=0)
{
epoll_del(epfd,fd);
close(fd);
printf("client close\n");
continue;
}
printf("buff(%d)=%s\n",fd,buff);
send(fd,"ok",2,0);
}
}
}
}
}
}
int create_socket()
{
int sockfd =socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1)
{
return -1;
}
struct sockaddr_in saddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family=AF_INET;
saddr.sin_port=htons(6000);
saddr.sin_addr.s_addr=inet_addr("127.0.0.1");
int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
if(res == -1)
{
return -1;
}
res = listen(sockfd,5);
if(res == -1)
{
return -1;
}
return sockfd;
}
2.2.ET模式
Q:如何开启ET模式
A:在添加事件时或一个EPOLLET即:
void epoll_add(int epfd ,int fd)
{
struct epoll_event ev;
ev.events = EPOLLIN | EPOLLET;///在这里!!!!
ev.data.fd=fd;
if(epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev) == -1)
{
perror("epoll ctl add err");
}
}
2.2.1.ET和LT的区别
2.2.2.ET模式下的服务器代码(只提醒一次,因此要在收到一次提醒后,把所有数据读完)
accept方法也需要循环读取,此代码并未修改这个部分
ET模式下会产生矛盾:
1.收到提示后,如果只读一次,如果没读完,不会再提醒。
2.如果第一次读完后再去读第二次,如果没数据了,recv会阻塞。
因此将思路设置为:
1.将描述符设置为非阻塞模式。
2.循环读取数据,直到数据读取完成
步骤:
1.在添加事件类型时,添加上EPOLLET;(开启ET模式)
2.描述符需要设置为非阻塞模式(fcntl方法)
3.在io函数返回后,提醒我们描述符上有读事件产生。采用循环处理,直到数据被读完
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<fcntl.h>
#include<assert.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/epoll.h>
#include<fcntl.h>
#include<errno.h>
#define MAXFD 10
/!!!!!!!!!!!!!///
//1.将描述符设为非阻塞//
void setnonblock(int fd)
{
int oldfl = fcntl(fd,F_GETFL);
int newfl = oldfl | O_NONBLOCK;
if(fcntl(fd,F_SETFL,newfl) == -1)
{
perror("fcntl error\n");
}
}
//!!!!!!!!//
//此时只关注读事件,因此不传入事件
void epoll_add(int epfd ,int fd)
{
struct epoll_event ev;
ev.events = EPOLLIN;//read
ev.data.fd=fd;
//每添加一个描述符,都将其设为非阻塞模式!!!!
setnonblock(fd);
/!!!!!!!!!!!!
if(epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev) == -1)
{
perror("epoll ctl add err");
}
}
void epoll_del(int epfd,int fd)
{
if(epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL) == -1)
{
perror("epoll ctl del err");
}
}
int create_socket();
int main()
{
int sockfd = create_socket();
assert(sockfd != -1);
//由于是对应多个客户端,因此我们要把文件描述符收集起来
//之后再来检测有没有我们关心的事件产生
int epfd = epoll_create(MAXFD);
assert(epfd!=-1);
epoll_add(epfd,sockfd);
//往内核实现表中添加元素,检测是否有数据
//获取就绪描述符
struct epoll_event evs[MAXFD];
while(1)
{
int n=epoll_wait(epfd,evs,MAXFD,5000);//5000ms=5s
if(n==-1)
{
perror("epoll wait err\n");
}
else if(n==0)
{
printf("time out\n");
}
else
{
int i=0;
for(;i<n;++i)
{
int fd=evs[i].data.fd;
if(evs[i].events&EPOLLIN)
{
//描述符不同处理方法不同
if(fd==sockfd)
{
//accept
struct sockaddr_in caddr;
int len = sizeof(caddr);
int c=accept(sockfd,(struct sockaddr*)&caddr,&len);
if(c<0)
{
continue;
}
printf("accept c=%d\n",c);
epoll_add(epfd,c);
}
else
{
//recv
//修改对于连接套接字的处理方式(循环读取)
while(1)
{
char buff[128]={0};
int num =recv(fd,buff,1,0);
if(num == -1)
{
if(errno != EAGAIN && errno != EWOULDBLOCK)
{
perror("recv err");
}
else //这种情况只是因为数据读完了,因此回复ok(非阻塞情况下,缓冲区中没有数据)
{
send(fd,"ok",2,0);
}
break;
}
else if(num==0)
{
epoll_del(epfd,fd);
close(fd);
printf("client close\n");
break;
}
else
{
printf("rec:%s\n",buff);
}
}
}
}
}
}
}
}
int create_socket()
{
int sockfd =socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1)
{
return -1;
}
struct sockaddr_in saddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family=AF_INET;
saddr.sin_port=htons(6000);
saddr.sin_addr.s_addr=inet_addr("127.0.0.1");
int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
if(res == -1)
{
return -1;
}
res = listen(sockfd,5);
if(res == -1)
{
return -1;
}
return sockfd;
}