select、poll、epoll使用小结
来源:互联网 发布:全途打单软件怎么样 编辑:程序博客网 时间:2024/06/11 18:41
Linux上可以使用不同的I/O模型,我们可以通过下图了解常用的I/O模型:同步和异步模型,以及阻塞和非阻塞模型,本文主要分析其中的异步阻塞模型。
一、select使用
这个模型中配置的是非阻塞I/O,然后使用阻塞select系统调用来确定一个I/O描述符何时有操作。使用select调用可以为多个描述符提供通知,对于每个提示符,我们可以请求描述符的可写,可读以及是否发生错误。异步阻塞I/O的系统流程如下图所示:
使用select常用的几个函数如下:
- FD_ZERO(int fd, fd_set* fds)
- FD_SET(int fd, fd_set* fds)
- FD_ISSET(int fd, fd_set* fds)
- FD_CLR(int fd, fd_set* fds)
- int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)
下面我们来看如何使用select:
- SOCKADDR_IN addrSrv;
- int reuse = 1;
- SOCKET sockSrv,connsock;
- SOCKADDR_IN addrClient;
- pool pool;
- int len=sizeof(SOCKADDR);
- /*创建TCP*/
- sockSrv=socket(AF_INET,SOCK_STREAM,0);
- /*地址、端口的绑定*/
- addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
- addrSrv.sin_family=AF_INET;
- addrSrv.sin_port=htons(port);
- if(bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR))<0)
- {
- fprintf(stderr,"Failed to bind");
- return ;
- }
- if(listen(sockSrv,5)<0)
- {
- fprintf(stderr,"Failed to listen socket");
- return ;
- }
- setsockopt(sockSrv,SOL_SOCKET,SO_REUSEADDR,(const char*)&reuse,sizeof(reuse));
- init_pool(sockSrv,&pool);
- while(1)
- {
- /*通过selete设置为异步模式*/
- pool.ready_set=pool.read_set;
- pool.nready=select(pool.maxfd+1,&pool.ready_set,NULL,NULL,NULL);
- if(FD_ISSET(sockSrv,&pool.ready_set))
- {
- connsock=accept(sockSrv,(SOCKADDR *)&addrClient,&len);
- //loadDeal()/*连接处理*/
- //printf("test\n");
- add_client(connsock,&pool);//添加到连接池
- }
- /*检查是否有事件发生*/
- check_client(&pool);
- }
二、poll使用
poll函数类似于select,但是其调用形式不同。poll不是为每个条件构造一个描述符集,而是构造一个pollfd结构体数组,每个数组元素指定一个描述符标号及其所关心的条件。定义如下:
- #include <sys/poll.h>
- int poll (struct pollfd *fds, unsigned int nfds, int timeout);
- struct pollfd {
- int fd; /* file descriptor */
- short events; /* requested events to watch */
- short revents; /* returned events witnessed */
- };
每个结构体的events域是由用户来设置,告诉内核我们关注的是什么,而revents域是返回时内核设置的,以说明对该描述符发生了什么事件。这点与select不同,select修改其参数以指示哪一个描述符准备好了。在《unix环境高级编程》中有一张events取值的表,如下:
POLLIN :可读除高优级外的数据,不阻塞
POLLRDNORM:可读普通数据,不阻塞
POLLRDBAND:可读O优先数据,不阻塞
POLLPRI:可读高优先数据,不阻塞
POLLOUT :可写普数据,不阻塞
POLLWRNORM:与POLLOUT相同
POLLWRBAND:写非0优先数据,不阻塞
其次revents还有下面取值
POLLERR :已出错
POLLHUP:已挂起,当以描述符被挂起后,就不能再写向该描述符,但是仍可以从该描述符读取到数据。
POLLNVAL:此描述符并不引用一打开文件
对poll函数,nfds表示fds中的元素数,timeout为超时设置,单位为毫秒若为0,表示不等待,为-1表示描述符中一个已经准备好或捕捉到一个信号返回,大于0表示描述符准备好,或超时返回。函数返回值返回值若为0,表示没有事件发生,-1表示错误,并设置errno,大于0表示有几个描述符有事件。
poll的使用和select基本类似。在此不再介绍。poll相对于是select的优势是监听的描述符数量没有限制。
三、epoll学习
epoll是linux内核为处理大批量句柄而作了改进的poll,是linux下多路复用IO接口select/pool的增强版本,它能显著减少程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。epoll有两种模式,Edge Triggered(简称ET) 和 Level Triggered(简称LT).在采用这两种模式时要注意的是,如果采用ET模式,那么仅当状态发生变化时才会通知,而采用LT模式类似于原来的select/poll操作,只要还有没有处理的事件就会一直通知.
1)epoll数据结构介绍:
- typedef union epoll_data
- {
- void *ptr;
- int fd;
- __uint32_t u32;
- __uint64_t u64;
- } epoll_data_t;
- struct epoll_event
- {
- __uint32_t events; /* Epoll events */
- epoll_data_t data; /* User data variable */
- };
EPOLLIN:表示对描述符的可以读
EPOLLOUT:表示对描述符的可以写
EPOLLPRI:表示对描述符的有紧急数据可以读
EPOLLERR:发生错误
EPOLLHUP:挂起
EPOLLET:边缘触发
EPOLLONESHOT:一次性使用,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
2)函数介绍
epoll的三个函数
- int epoll_creae(int size);
参数:size为epoll上能关注的最大描述符数
- int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
参数:epfd由epoll_create生成的epoll专用描述符
op操作:EPOLL_CTL_ADD 注册 EPOLL_CTL_MOD修改 EPOLL_DEL删除
fd:关联的文件描述符
evnet告诉内核要监听什么事件
- int epoll_wait(int epfd,struct epoll_event*events,int maxevents,int timeout);
参数:epfd要检测的句柄
events:用于回传待处理时间的数组
maxevents:告诉内核这个events有多大,不能超过之前的size
timeout:为超时时间
(a) 使用epoll_create()函数创建文件描述,设定可管理的最大socket描述符数目。
(b) 创建与epoll关联的接收线程,应用程序可以创建多个接收线程来处理epoll上的读通知事件,线程的数量依赖于程序的具体需要。
(c) 创建一个侦听socket的描述符ListenSock,并将该描述符设定为非阻塞模式,调用Listen()函数在该套接字上侦听有无新的连接请求,在epoll_event结构中设置要处理的事件类型EPOLLIN,工作方式为 epoll_ET,以提高工作效率,同时使用epoll_ctl()来注册事件,最后启动网络监视线程。
(d) 网络监视线程启动循环,epoll_wait()等待epoll事件发生。
(e) 如果epoll事件表明有新的连接请求,则调用accept()函数,将用户socket描述符添加到epoll_data联合体,同时设定该描述符为非阻塞,并在epoll_event结构中设置要处理的事件类型为读和写,工作方式为epoll_ET。
(f) 如果epoll事件表明socket描述符上有数据可读,则将该socket描述符加入可读队列,通知接收线程读入数据,并将接收到的数据放入到接收数据的链表中,经逻辑处理后,将反馈的数据包放入到发送数据链表中,等待由发送线程发送。
程序代码为:#include <iostream>#include <sys/socket.h> #include <sys/epoll.h> #include <netinet/in.h> #include <arpa/inet.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> #define MAXLINE 10 #define OPEN_MAX 100 #define LISTENQ 20 #define SERV_PORT 5555 #define INFTIM 1000 void setnonblocking(int sock) { int opts; opts=fcntl(sock,F_GETFL); if(opts<0) { perror("fcntl(sock,GETFL)"); exit(1); } opts = opts | O_NONBLOCK; //设置套接口为非阻塞 if(fcntl(sock,F_SETFL,opts)<0) { perror("fcntl(sock,SETFL,opts)"); exit(1); } } int main() { int i, maxi, listenfd, connfd, sockfd, epfd, nfds; ssize_t n; char line[MAXLINE]; socklen_t clilen; struct epoll_event ev,events[20]; //声明epoll_event结构体的变量, ev用于注册事件, events数组用于回传要处理的事件 epfd=epoll_create(256); //生成用于处理accept的epoll专用的文件描述符, 指定生成描述符的最大范围为256 struct sockaddr_in clientaddr; struct sockaddr_in serveraddr; listenfd = socket(AF_INET, SOCK_STREAM, 0); setnonblocking(listenfd); //把用于监听的socket设置为非阻塞方式 ev.data.fd=listenfd; //设置与要处理的事件相关的文件描述符 ev.events=EPOLLIN | EPOLLET; //设置要处理的事件类型 epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev); //注册epoll事件 bzero(&serveraddr, sizeof(serveraddr)); serveraddr.sin_family = AF_INET; char *local_addr="200.200.200.204"; inet_aton(local_addr,&(serveraddr.sin_addr)); serveraddr.sin_port=htons(SERV_PORT); //或者htons(SERV_PORT); bind(listenfd,(sockaddr *)&serveraddr, sizeof(serveraddr)); listen(listenfd, LISTENQ); maxi = 0; for( ; ; ) { nfds=epoll_wait(epfd,events,20,500); //等待epoll事件的发生 for(i=0;i<nfds;++i) //处理所发生的所有事件 { if(events[i].data.fd==listenfd) /**监听事件**/ { connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen); if(connfd<0){ perror("connfd<0"); exit(1); } setnonblocking(connfd); //把客户端的socket设置为非阻塞方式 char *str = inet_ntoa(clientaddr.sin_addr); std::cout<<"connect from "<<str<<std::endl; ev.data.fd=connfd; //设置用于读操作的文件描述符 ev.events=EPOLLIN | EPOLLET; //设置用于注测的读操作事件 epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev); //注册ev事件 } else if(events[i].events&EPOLLIN) /**读事件**/ { if ( (sockfd = events[i].data.fd) < 0) continue; if ( (n = read(sockfd, line, MAXLINE)) < 0) { if (errno == ECONNRESET) { close(sockfd); events[i].data.fd = -1; } else { std::cout<<"readline error"<<std::endl; } } else if (n == 0) { close(sockfd); events[i].data.fd = -1; } ev.data.fd=sockfd; //设置用于写操作的文件描述符 ev.events=EPOLLOUT | EPOLLET; //设置用于注测的写操作事件 epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); //修改sockfd上要处理的事件为EPOLLOUT } else if(events[i].events&EPOLLOUT) /**写事件**/ { sockfd = events[i].data.fd; write(sockfd, line, n); ev.data.fd=sockfd; //设置用于读操作的文件描述符 ev.events=EPOLLIN | EPOLLET; //设置用于注册的读操作事件 epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); //修改sockfd上要处理的事件为EPOLIN } } } }
- select、poll、epoll使用小结
- select、poll、epoll使用小结
- select、poll、epoll使用小结
- select、poll、epoll使用小结
- select、poll、epoll使用小结
- select,poll和epoll使用
- select,poll和epoll使用
- select,poll和epoll使用
- select poll epoll使用示例
- select poll和epoll使用和区别
- select、Poll、epoll比较。
- select,poll,epoll区别
- select,poll,epoll用法
- select, poll, epoll
- select、poll、epoll
- select,poll,epoll区别
- select、poll和epoll
- socket-select ,poll ,epoll
- 每日阅读12内核设计与实现——中断处理程序与中断上下文
- 如何建立工作组
- ORACLE内存结构-SGA
- 硬解析和软解析
- samba错误:session setup failed: NT_STATUS_LOGON_FAILURE
- select、poll、epoll使用小结
- SystemUtil 运行应用程序
- 【程序47】宏#define命令练习(2)(VC6.0可运行版本)
- 教你怎么实现高速高效双缓冲绘图
- 注册和反注册dll链接库
- 单元测试利器JUnit4
- 写给3年后的XXX【连载】--1.3 步入而立之年的程序员的迷茫
- 获取某个对象的元数据
- 第四次c语言上机实验报告