select、poll、epoll三组IO复用
int select(int nfds,fd_set* readfds,fd_set* writefds,fd_set* exceptfds,struct timeval* timeout)//其中nfds是被监听的文件描述符总数通常设置为所有文件描述符中的最大值加一(是否可以理解为凡是小于这个描述符的都要被轮询?,加1是因为文件描述符从0开始计数),readfds、writefds、exceptfds分别指向可读可写异常事件对应的文件描述符集合(当事件发生时内核在线修改该集合以此来通知应用程序),timeout指出等待时间(能精确到微秒但是不可信)若为NULL则一直阻塞直到有事件发生,fd_set结构体能容纳的文件描述符数目由FD_SETSIZE限制(所有事件的数目)其同时限制了select能同时处理的文件描述符总量。调用成功返回就绪事件描述符总数失败返回-1。宏FD_SET(int fd,fd_set* fdset)将文件描述符fd和事件集合fd_set绑定(这个fd_set可以传给select中间的三个参数之一),宏FD_ISSET(int fd,fd_set* fdset)可以检测fd上的事件是否发生(内核在线修改这个数据机构所以select调用后可以使用该宏判断并处理相应事件),由于是内核在线修改事件集合所以每次select调用后都需要重置事件集合才能进行下一次select调用。因此select编写格式大致绑定文件描述符和事件集合,select调用,测试特定文件描述符上的事件是否发生,重新绑定文件描述符和事件集合的关系,再次调用select,伪代码如下:
int sockfd=socket(PF_INET,SOCK_STREAM,0);//以一个网络socket文件描述符为例
bind(sockfd,...);
listen(sockfd,...);
fd_set read_fds;//申明可读事件集合
FD_ZERO(&read_fds);//清零
while(1){//循环等待连接发送数据事件
FD_SET(sockfd,&read_fds);//将文件描述符sockfd和可读事件集合read_fds绑定
int ret=select(sockfd+1,&read_fds,NULL,NULL,NULL)//注意sockfd+1若监听多个文件描述符还需要找出哪个最大,这里只监听可读事件所以中间的可写和异常置为NULL,最后的超时时间置为NULL表示阻塞等待直到有事件就绪
if(ret<0)
cout<<"select错误"<<endl;
if(FD_ISSET(sockfd,&read_fds)){//测试read_fds的位sockfd是否被设置,若被设置则表明内核在线修改即sockfd上事件就绪
这里定义处理数据的逻辑代码
}//若多个文件描述符定义为数组,这里改为循环测试文件描述符
}//可见每次循环都需要重置文件描述符和事件集合的绑定关系,当处理多个文件描述符的逻辑关键在绑定文件描述符到相应的事件集合上,且找出最大的文件描述符,且循环多次测试文件描述符
int poll(struct pollfd* fds,nfds_t nfds,int timeout)//其中fds是个pollfd结构体数组(因为是数组所以才能监听多个文件描述符),pollfd结构体作用是将文件描述符fd和事件events(一些列事件的按位或,具体事件类型可以查阅其它资料)绑定并且内核修改revents成员从而达到侦听并告知应用程序(注意select是在线修改事件集合而poll内核修改revents所以决定了poll不用每次调用后重置事件集合,因为events并没有被修改),nfds是告诉内核fds数组的大小,timeout是超时值若为-1则永远阻塞直到事件发生若为0则立即返回。伪代码实例:
int poll_two(int fd1,int fd2){
struct pollfd poll_array[2];
int ret;
poll_array[0].fd = fd1;
poll_array[1].fd = fd2;
poll_array[0].events = POLLIN;
poll_array[1].events = POLLIN;
while(1){
ret = poll(poll_array,(unsigned long)2,-1);//这里的2是指注册了事件的文件描述符个数
if(ret < 0){
cout<<"poll错误"<<strerror(errno)<<endl;
return -1;
}
for(int i=0;i<2;i++){//循环处理每个注册的文件描述符,注意这里的2
if(((poll_array[i].revents&POLLHUP) == POLLHUP) ||//检测事件是否发生一些异常,是否挂起,比如管道的写端被关闭读端收到POLLHUP事件
((poll_array[i].revents&POLLERR) == POLLERR) ||//是否错误
((poll_array[i].revents&POLLNVAL) == POLLNVAL))//文件描述符没有打开
return 0;
if(poll_array[i].events&POLLIN){
这里定义处理第i个文件描述符fd上的数据的逻辑代码
}
}//end_for
}//end_while
}//可见poll索引就绪事件是扫描整个注册了事件的文件描述符(将上面的2替换你想要的数字)
epoll则有三个系统调用构成的IO复用逻辑,epoll将用户关心的文件描述符上的事件放在内核的一个事件表中,从而不像select和poll那样每次调用都重复传入事件集合(一切缘由多个系统调用将select和poll的底层逻辑分开了),因此epoll需要一个额外的文件描述符标示这个事件表,这个事件表由epoll_create函数创建
int epoll_create(int size)//其中size只是给内核一个提示告诉内核事件表需要多大现在已经不起作用了,返回事件表描述符,这个描述符将在后续中使用
int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event)//该函数用来操作事件表,其中fd是需要操作的文件描述符(不是事件表描述符),op参数指定操作类型:EPOLL_CTL_ADD往fd上添加事件,EPOLL_CTL_MOD修改fd上事件,EPOLL_CTL_DEL删除fd上的注册事件。结构体epoll_event的成员events是事件类型(一些列事件的按位或,具体有哪些类型就部列出了不过值得注意的是epoll事件是poll事件前面加E),成员data又是个结构体用于存储用户数据最常用的就是将data.fd设置为需要监听的文件描述符fd,成功返回0失败返回-1
int epoll_wait(int epfd,struct epoll_event* events,int maxevents,int timeout)//在一段超时时间内等待一组文件描述符上的事件,成功返回事件就续的文件描述符个数,失败返回-1.其中epfd就是事件表描述符,maxevents指定最多监听多少个事件(必须大于0),timeout和poll的相同。伪代码实例:
epoll_event events[MAX_EVENT_NUMBER];//定义就绪事件集合(这里events不要和结构体epoll_event里的成员events混淆),该集合表示事件就绪后内核修改并由epoll_wait返回
int epollfd=epoll_create(5);//创建事件表
//这里将你需要监听的文件描述符的相应事件注册到事件表中
for(int i=0;i<监听的文件描述符个数;i++){//假设监听的文件描述符存于数组fd中
epoll_event event;//定义临时事件结构体
event.data.fd=fd[i];//描述符fd[i]写到用户数据中
event.events=EPOLLIN;//注册fd[i]是监听事件为可读,可以是其它事件按位或
epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&event);//将该监听事件注册到事件表中
}
while(1){
int ret=epoll_wait(epollfd,events,MAX_EVENT_NUMBER,-1)//阻塞等待直到有事件就绪,events将返回就绪事件集合
for(int i=0;i<ret;i++){//逐个处理就绪事件
这里定义就绪事件对应的文件描述符events[i].data.fd上的数据处理逻辑
}
}//可见epoll返回的仅仅是就绪事件集合
三组IO复用的比较:
它们都是在等待timeout时间内返回就绪事件个数,select由于没有将文件描述符和事件集合自动绑定且内核在线修改就绪事件仍在同一事件集合中进行,所以再次调用select需要重新手动绑定文件描述符和事件集合,且只有三种事件集合可以绑定;poll提供了一个封装的结构体pollfd相对来说简洁些,当事件就绪后内核修改通知也在一个额外的参数revents内进行所以再次调用poll无需重新绑定文件描述符和事件集合的关系,不过select和poll每次返回的都是整个用户注册的事件集合所以应用程序索引就绪文件描述符的复杂度为O(n)//n是注册文件描述符个数;epolll则采用独立的事件表维护注册事件,采用epoll_ctl修改事件表,epoll_wait等待就绪事件且返回的就仅仅是就绪事件集合(采用独立参数),使得底层逻辑模块独立化,这使得应用程序索引就绪文件描述符的复杂度为O(1).
poll和epoll都采用额外参数指定监听文件描述符的个数(nfds和maxevents)这两个数值都能达到系统允许打开的最大文件描述符个数(65536),而select受限与FD_SETSIZE(貌似是1024忘记了...)
select和poll采用轮询机制每次调用都扫描整个注册文件描述符集合并将其中就绪的文件描述符返回给应用程序,所以它们检测就是事件的时间复杂度是O(n)。epoll_wait则采用事件回调函数当检测到就绪文件描述符时将触发回调函数,回调函数就将该文件描述符上对应的事件插入到内核就绪事件队列,时间复杂度为O(1)。当活动连接比较多的时候epoll_wait未必比select和poll效率高,因为回调函数过于频繁。所以epoll_wait适合连接数量多且idle连接较多(即活动连接少)的情形。
关于epoll的LT模式和ET模式及EPOLLINONESHOT参见前两篇文章。
select、poll、epoll三组IO复用的更多相关文章
- IO多路复用select/poll/epoll详解以及在Python中的应用
IO multiplexing(IO多路复用) IO多路复用,有些地方称之为event driven IO(事件驱动IO). 它的好处在于单个进程可以处理多个网络IO请求.select/epoll这两 ...
- select.poll,epoll的区别与应用
先讲讲同步I/O的五大模型 阻塞式I/O, 非阻塞式I/O, I/O复用,信号驱动I/O(SIGIO),异步I/O模型 而select/poll/epoll属于I/O复用模型 select函数 该函数 ...
- select, poll, epoll
select的一 个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024 http://www.cnblogs.com/bigwangdi/p/3182958.html ...
- 笔记-select,poll,epoll
笔记-select,poll,epoll 1. I/O多路复用 I/O多路复用是指:通过一种机制或一个进程,可以监视多个文件描述符,一旦描述符就绪(写或读),能够通知程序进行相应的读写操作. ...
- Linux I/O复用中select poll epoll模型的介绍及其优缺点的比較
关于I/O多路复用: I/O多路复用(又被称为"事件驱动"),首先要理解的是.操作系统为你提供了一个功能.当你的某个socket可读或者可写的时候.它能够给你一个通知.这样当配合非 ...
- Select\Poll\Epoll异步IO与事件驱动
事件驱动与异步IO 事件驱动编程是一种编程规范,这里程序的执行流由外部事件来规定.它的特点是包含一个事件循环,但外部事件发生时使用回调机制来触发响应的处理.另外两种常见的编程规范是(单线程)同步以及多 ...
- Java IO 学习(二)select/poll/epoll
如上文所说,select/poll/epoll本质上都是同步阻塞的,但是由于实现了IO多路复用,在处理聊天室这种需要处理大量长连接但是每个连接上数据事件较少的场景时,相比最原始的为每个连接新开一个线程 ...
- Linux 网络编程的5种IO模型:多路复用(select/poll/epoll)
Linux 网络编程的5种IO模型:多路复用(select/poll/epoll) 背景 我们在上一讲 Linux 网络编程的5种IO模型:阻塞IO与非阻塞IO中,对于其中的 阻塞/非阻塞IO 进行了 ...
- Python之路-python(Queue队列、进程、Gevent协程、Select\Poll\Epoll异步IO与事件驱动)
一.进程: 1.语法 2.进程间通讯 3.进程池 二.Gevent协程 三.Select\Poll\Epoll异步IO与事件驱动 一.进程: 1.语法 简单的启动线程语法 def run(name): ...
随机推荐
- 禁用UITextField复制粘贴等方法
要实现此功能只需创建一个继承自UITextField的子类,重写以下方法即可. - (BOOL)canPerformAction:(SEL)action withSender:(id)sender{ ...
- 【html】【3】html标签列表
必看参考: http://www.divcss5.com/html/h323.shtml http://www.w3school.com.cn/tags/tag_html.asp 常用: <ht ...
- window远程连接linux
一.字符界面连接Linux 1.直接使用window自带的telnet. 2.但现在Linux一般都不启用telnet,而是启用ssh.这样的话,window就要安装客户端来访问Linux了.这 ...
- Js浏览器对象
Js浏览器对象——window对象 1.window对象: (1)window对象是BOM的核心,window对象指当前的浏览器窗口. (2)所有的JavaScript全局对象.函数以及变量均自动成为 ...
- 朋友的礼物(英雄会,csdn,高校俱乐部)信封问题,匹配模型
前言: 首先这是一题解,但是重点最代码之后,有耐心的可以直接从代码后看. 上题目:n个人,每个人都有一件礼物想送给他人,他们决定把礼物混在一起,然后每个人随机拿走一件,问恰好有m个人拿到的礼物恰好是自 ...
- winForm帮助信息
在项目开发中,由于没更新一块内容,帮助文档都得及时更新,否则将导致最新的应用程序与帮助文档不一致.为此,写了一个帮助页面,这样就可以实时看到帮助信息. 首先,新建了一个帮助信息类,代码如下: /// ...
- php之面向对象(2)
注意:看这篇文章之前建议看看之前的文章,因为内容之间衔接性比较强.勿喷.. 面向对象,是一种思维模式的名字,并不是指某种特定的写法,面向对象简称oop,思路的核心在于:什么时候 什么东西 做什么. 编 ...
- lamp环境中的/到底是指的网站根目录还是linux的根
在lamp中经常会用到 require,require_once等包含文件的语句. 如果你使用相对路径没有问题. 但是如果你使用了这样的语句就要小心了. 对于如图网站目录 require_once ' ...
- Spire.XLS
又一款Excel处理神器Spire.XLS,你值得拥有(二) 前言:上篇 C#组件系列——又一款Excel处理神器Spire.XLS,你值得拥有 介绍了下组件的两个功能,说不上特色,但确实能解决我 ...
- Scroll view 备忘
Stroyboard中使用ScrollView 当我们使用Storyboard开发项目时,如果要往控制器上拖入一个ScrollView并且添加约束设置滚动区域,是有特殊的规定的: 拖入一个scroll ...