I/O多路复用之selectpoll

 2023-09-05 阅读 127 评论 0

摘要:系统提供select函数用来实现I/O多路复用输入/输出模型。select系统调用是用来让我们的程序监视多个文件描述状态变化的。程序会停在select这里等待,直到被监视的文件描述符有一个或多个发生状态变化。通常I/O操作有两个步骤,一个是等,另一个是数据搬迁。

系统提供select函数用来实现I/O多路复用输入/输出模型。select系统调用是用来让我们的程序监视多个文件描述状态变化的。程序会停在select这里等待,直到被监视的文件描述符有一个或多个发生状态变化。通常I/O操作有两个步骤,一个是等,另一个是数据搬迁。select主要是在等的这个状态阻塞着直到事件发生。
头文件:

#include<sys/select.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/time.h>

函数原型
int select(int nfds,fd_set *reads,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);

参数:
nfds:是需要监视的最大的文件描述符的值+1。
fd_set:
fd_set底层是用位图实现的,每一个位都代表一个文件描述符。readfds,writefds,exceptfds分别对应于需要检测的可读文件描述符结合,可写描述符集合,异常文件描述符集合,他们都是输入输出型参数。
当作为输入参数时:只要文件描述符集合中对应的位上为1,就表示select需要监视这个描述符的状态。比如readfds里面的文件描述符就代表他们需要等待读事件,writefds里面的文件描述符就代表他们需要等待写事件。
当作为输出参数时,只要文件描述符集合中对应的位上为1,就代表他们等待的事件已经就绪,这是由内核设定的。

timeout:设置超时时间。
timeout里面的成员设定为特定的时间值:
如果在这段时间里面没有事件发生,select将超时返回。struct timeval结构用于描述一段时间长度,如果在这个时间内,需要监听的描述符没有事件发生则函数返回,返回值为0。
struct timeval
{
long tv_sec; //秒
long tv_usec; //微秒
}
timeout里面的成员等于0:表示非阻塞轮询方式,不断的去检测描述符集合的状态,然后立即返回。
timeout为NULL:表示以阻塞的方式等待事件发生。

返回值:
成功的话,返回文件描述符状态已改变的个数。如果返回0代表在描述符状态改变之前已经超过timeout时间。如果有错误发生的话,则返回-1。

下面的宏用来处理描述符集合:
void FD_CLR(int fd,fd_set* set); 用来清除描述符词组set中相关fd的位。
int FD_ISSET(int fd,fd_set* set); 用来测试set中相关fd的位是否为真。
void FD_SET(int fd,fd_set* set); 用来设置描述词组set中相关fd的位。
void FD_ZERO(fd_set *set);用来清除描述符词组set的全部位。

select模型:
select可监控的描述符取决于sizeof(fd_set)的值,因为文件描述符是用位图表示的,所以能监控的描述符的最大数量是sizeof(fd_set)*8,fd_set的大小可以调整。
将fd加入select监控集的同时,还要使用一个额外的数组保存select监控集中的fd。一方面是用于在select返回后,array作为源数据和fd_set进行FD_ISSET判断事件是否就绪。另一方面是select返回之后会把以前加入的但并无事件发生fd清空,这是由内核清空的,所以每次开始select前都要重新从array中取得fd加入到fd_set中。
还有就是因为select第一个参数是当前要监测的文件描述符的最大值加1,可以在扫描array的同时取得fd的最大值maxfd,用于select的第一个参数。
所以select的缺点就是,每次selcet之前都要遍历数组加入fd,select返回后还要遍历数组进行判断哪些事件已经就绪(FD_ISSET判断是否有事件发生)。

select的缺点:
1、每次调用select,都需要把fd集合从用户态拷贝到内核态。这个开销在fd很多的时候会很大。
2、select在返回之后,需要我们遍历数组去查找事件就绪的描述符。这个过程的时间复杂度是O(N)。而epoll它查找就绪事件的时候是O(1)。
3、select支持的文件描述符的数量太小了,默认是1024。

总结:针对select的缺点来看,即时fd_set可以改动,也不建议将它改的很大,因为一但支持的文件描述多了,效率自然也就降低了。

例:实现一个服务器,使用select让服务器可以同时接受多个客户的链接,并将客户发送的数据打印出来。

#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/select.h>
#include<unistd.h>
#include<sys/time.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#define SIZE 128int startup(char *ip,int port)
{assert(ip);int sock=socket(AF_INET,SOCK_STREAM,0);if(sock<0){perror("socket");exit(0);}int opt=1;setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));struct sockaddr_in local;local.sin_family=AF_INET;local.sin_port=htons(port);local.sin_addr.s_addr=inet_addr(ip);if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0){perror("bind");exit(1);}if(listen(sock,5)<0){perror("listen");exit(2);}return sock;
}int main(int argc,char* argv[])
{if(argc!=3){printf("usae: %s [IP] [PORT]\n",argv[0]);return 0;}int lis_sock=startup(argv[1],atoi(argv[2]));int gfds[SIZE];memset(gfds,-1,SIZE*4);fd_set rfds;FD_ZERO(&rfds);while(1){struct timeval timeout={5,0};gfds[0]=lis_sock;int max_fd=-1;int i=0;for(;i<SIZE;i++){if(max_fd<gfds[i]){max_fd=gfds[i];}if(gfds[i]>=0){FD_SET(gfds[i],&rfds);}}int ret=select(max_fd+1,&rfds,NULL,NULL,NULL);switch(ret){case 0:printf("timeout...\n");break;case -1:printf("error");break;default:if(FD_ISSET(gfds[0],&rfds)){struct sockaddr_in peer;socklen_t len=sizeof(peer);int connfd=accept(lis_sock,(struct sockaddr*)&peer,&len);if(connfd<0){perror("accept");}else{printf("client: %s:%d fd(%d)\n",inet_ntoa(peer.sin_addr),\ntohs(peer.sin_port),connfd);                    int k=0;for(;k<SIZE;k++){if(gfds[k]==-1){gfds[k]=connfd;break;}}if(k>=SIZE){close(connfd);gfds[k]=-1;}}}int j=1;for(;j<SIZE;j++){if(FD_ISSET(gfds[j],&rfds)){char buf[SIZE];ssize_t s=read(gfds[j],buf,sizeof(buf));if(s<0){perror("read");continue;}else if(s==0){printf("client is quit!\n");close(gfds[j]);gfds[j]=-1;}else{buf[s]=0;printf("client# %s\n",buf);}}}break;}}return 0;
}

poll也是一种I/O多路转接的方式,select将三种事件进行了区分,并且用三个位图来表示不同的监测事件。而poll统一用一种结构来管理要监测的事件。

#include<poll.h>
int poll(struct pollfd* fds,nfds_t nfds,int timeout);

参数:
fds:
它是一个结构体数组,其中元素的类型如下:
struct pollfd{
int fd; //保存要监测的文件描述符,由用户自己设定
int events; //保存要监测的事件,比如读事件或写事件,由用户自己设定
short revents; //保存就绪事件,等到事件就绪,由内核设定
}
pollfd结构里面包含了要监视的event和已经发生的event。同时pollfd并没有数量的限制,但是因为select和poll在返回之后,都需要轮询来获取就绪的描述符,所以当监视的文件描述符很多的时候,poll的性能也会下降。fds里面的每一个元素都代表一个要监测的事件。

nfds:表示数组的长度,也就是要监测事件的个数。
timeout:设置超时时间。
返回值:成功的话返回就绪事件的个数。如果超时的话返回0,失败的话返回-1。

poll支持的常见事件类型:
POLLIN:数据可读
POLLOUT:数据可写
POLLERR:错误
POLLRDHUP:TCP连接被对方关闭,或者对方关闭了写操作,他由GNU引入。在使用的时候要加上#define _GNU_SOURCE。

``
**poll与select的比较:
1、select将读写异常的事件分开进行监测,poll将所有的事件类型都统一用一种结构体表示。
2、select的3个fd_set参数都是输入输出型参数,当输入的时候表示要监测的文件描述符,当输出的时候表示就绪的文件描述符,所以每一次select之前都要重新将要监测的文件描述符设置进fd_set中。而poll用events表示要监测的文件描述符,revents表示就绪的文件描述符,所以poll只需要设置一次就可以了。
3、当事件就绪的时候,select和poll都要遍历整个所有监测的文件描述符来查找就绪的事件。**

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://808629.com/669.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 86后生记录生活 Inc. 保留所有权利。

底部版权信息