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复用的更多相关文章

  1. IO多路复用select/poll/epoll详解以及在Python中的应用

    IO multiplexing(IO多路复用) IO多路复用,有些地方称之为event driven IO(事件驱动IO). 它的好处在于单个进程可以处理多个网络IO请求.select/epoll这两 ...

  2. select.poll,epoll的区别与应用

    先讲讲同步I/O的五大模型 阻塞式I/O, 非阻塞式I/O, I/O复用,信号驱动I/O(SIGIO),异步I/O模型 而select/poll/epoll属于I/O复用模型 select函数 该函数 ...

  3. select, poll, epoll

    select的一 个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024 http://www.cnblogs.com/bigwangdi/p/3182958.html ...

  4. 笔记-select,poll,epoll

    笔记-select,poll,epoll 1.      I/O多路复用 I/O多路复用是指:通过一种机制或一个进程,可以监视多个文件描述符,一旦描述符就绪(写或读),能够通知程序进行相应的读写操作. ...

  5. Linux I/O复用中select poll epoll模型的介绍及其优缺点的比較

    关于I/O多路复用: I/O多路复用(又被称为"事件驱动"),首先要理解的是.操作系统为你提供了一个功能.当你的某个socket可读或者可写的时候.它能够给你一个通知.这样当配合非 ...

  6. Select\Poll\Epoll异步IO与事件驱动

    事件驱动与异步IO 事件驱动编程是一种编程规范,这里程序的执行流由外部事件来规定.它的特点是包含一个事件循环,但外部事件发生时使用回调机制来触发响应的处理.另外两种常见的编程规范是(单线程)同步以及多 ...

  7. Java IO 学习(二)select/poll/epoll

    如上文所说,select/poll/epoll本质上都是同步阻塞的,但是由于实现了IO多路复用,在处理聊天室这种需要处理大量长连接但是每个连接上数据事件较少的场景时,相比最原始的为每个连接新开一个线程 ...

  8. Linux 网络编程的5种IO模型:多路复用(select/poll/epoll)

    Linux 网络编程的5种IO模型:多路复用(select/poll/epoll) 背景 我们在上一讲 Linux 网络编程的5种IO模型:阻塞IO与非阻塞IO中,对于其中的 阻塞/非阻塞IO 进行了 ...

  9. Python之路-python(Queue队列、进程、Gevent协程、Select\Poll\Epoll异步IO与事件驱动)

    一.进程: 1.语法 2.进程间通讯 3.进程池 二.Gevent协程 三.Select\Poll\Epoll异步IO与事件驱动 一.进程: 1.语法 简单的启动线程语法 def run(name): ...

随机推荐

  1. 召回率与准确率[ZZ]

    最近一直在做相关推荐方面的研究与应用工作,召回率与准确率这两个概念偶尔会遇到,知道意思,但是有时候要很清晰地向同学介绍则有点转不过弯来. 召回率和准确率是数据挖掘中预测.互联网中的搜索引擎等经常涉及的 ...

  2. 读书笔记之 - javascript 设计模式 - 装饰者模式

    本章讨论的是一种为对象增添特性的技术,它并不使用创建新子类这种手段. 装饰者模式可以透明地把对象包装在具有同样接口的另一对象之中,这样一来,你可以给一些方法添加一些行为,然后将方法调用传递给原始对象. ...

  3. eclipse下使用Genymotion调试Android程序出现的问题

    一. The connection to adb is down, and a severe error has occured. You must restart adb and Eclipse. ...

  4. 一个简单的Hibernate工具类HibernateUtil

    HibernateUtil package com.wj.app.util; import org.hibernate.Session; import org.hibernate.SessionFac ...

  5. 使用Express搭建服务器

    Express是基于Node.js平台,快速.开放.极简的web开发框架.所以,使用Express之前,请确保已安装Node.js. 1.创建一个目录作为当前工作目录: $ mkdir myapp $ ...

  6. SQL的四种语言和数据库范式

    1. SQL的四种语言 DDL(Data Definition Language)数据库定义语言 CREATE ALTER DROP TRUNCATE COMMENT RENAME DML(Data ...

  7. MFC学习指南大纲

    最近一直在做MFC的项目,顺便学习一下以下MFC核心知识吧: 大纲: 1. 消息队列 2. send message 3. post message 4. 面向对象编程 5. 指针 一个一个来学习哈.

  8. bzoj 3751: [NOIP2014]解方程 同余系枚举

    3.解方程(equation.cpp/c/pas)[问题描述]已知多项式方程:a ! + a ! x + a ! x ! + ⋯ + a ! x ! = 0求这个方程在[1, m]内的整数解(n 和 ...

  9. Park Visit

    hdu4607:http://acm.hdu.edu.cn/showproblem.php?pid=4607 题意:给你一棵树,树上每条边的权值是1,然后然你选择m个点,求遍历m个点的最小花费. 题解 ...

  10. salt-API基本验证命令

    配置SALT-API,网上有很多,作下来也很顺利. 我的参考: 作一下验证的记录: curl -k https://x.x.x.x:8000/login -H "Accept: applic ...