IO多路复用

阻塞I/O有一个比较明显的缺点是在I/O阻塞模式下,一个线程只能处理一个流的I/O事件。如果想要同时处理多个流,需要多个进程或者多个线程,但是这种方式效率不高。
非阻塞的I/O可以在数据未准备好之前,先去做其它的,但是需要经常轮询查看流是否已经准备好了。轮询方法有:
a. 忙轮询,
while true {
for i in stream[]; {
if i has data
  read until unavailable
}
}
b. 无差别的轮询方式:即通过引进一个代理,这个代理为select/poll,这个代理可以同时观察多个流的I/O事件。当所有的流都没有准备就绪时,会把当前线程阻塞掉;当有一个或多个流的I/O事件就绪时,就从阻塞状态中醒来,然后轮询一遍所有的流,处理已经准备好的I/O事件。轮询的过程可以参照如下:

while true {
select(streams[])
for i in streams[] {
if i has data
  read until unavailable
}
}
c. 最小轮询方式:无差别的轮询方式有一个缺点就是,随着监控的流越来越多,需要轮询的时间也会随之增加,效率也会随之降低。所以还有另外一种轮询方式,最小轮询方式,即通过epoll方式来观察多个流,epoll只会把发生了I/O事件的流通知我们,我们对这些流的操作都是有意义的,时间复杂度降低到O(k),其中k为产生I/O事件的流个数。轮询的过程如下:
while true {
active_stream[] = epoll_wait(epollfd)
for i in active_stream[] {
read or write till unavailable
}
}

select/poll/epoll都是采用I/O多路复用机制的,其中select/poll是采用无差别轮询方式,而epoll是采用最小的轮询方式。
 
select: Select系统调用是用来让我们的程序监视多个文件句柄的状态变化。程序会阻塞在select函数上,直到被监视的文件句柄中有一个或多个发生了状态变化。
int select(int maxfd,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout)
参数说明:
maxfd:需要监视的最大的文件描述符值+1;
readset:需要检测的可读文件描述符的集合;
writeset:需要检测的可写文件描述符的集合
exceptset:需要检测的异常文件描述符的集合
timeout:超时时间;超时时间有三种情况: 
    NULL:永远等待下去,仅在有一个描述字准备好I/O时才返回;
    0:立即返回,仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生;

    特定的时间值: 如果在指定的时间段里没有事件发生,select将超时返回;

函数返回值有三种情况:
返回0表示超时了;
返回-1,表示出错了;
返回一个大于0的数,表示文件描述符状态改变的个数;
fd_set是一个文件描述符集合,可以通过以下宏来操作:
FD_CLR(inr fd,fd_set* set):用来清除文件描述符集合set中相关fd的位
FD_ISSET(int fd,fd_set *set):用来测试文件描述符集合set中相关fd的位是否为真
FD_SET(int fd,fd_set*set):用来设置文件描述符集合set中相关fd的位
FD_ZERO(fd_set *set):用来清除文件描述符集合set的全部位

Poll的处理机制与Select类似,只是Poll选择了pollfd结构体来处理文件描述符的相关操作:
struct pollfd {
int fd;         /* 文件描述符 */
short events;   /* 等待的事件 */
short revents;  /* 实际发生了的事件 */
} ;

每一个pollfd结构体都指定了一个文件描述符fd,events代表了需要监听该文件描述的事件掩码,可选的有:
POLLIN:有数据可读。
POLLRDNORM:有普通数据可读。
POLLRDBAND:有优先数据可读。
POLLPRI:有紧迫数据可读。
POLLOUT:写数据不会导致阻塞。
POLLWRNORM:写普通数据不会导致阻塞。
POLLWRBAND:写优先数据不会导致阻塞。
POLLMSGSIGPOLL:消息可用。
revents代表文件描述符的操作结果掩码,内核在调用返回时设置这个域。events域中请求的任何事件都可能在revents域中返回,除此之外,revents域还可以包含以下事件:

POLLER:指定的文件描述符发生错误。
POLLHUP:指定的文件描述符挂起事件。
POLLNVAL:指定的文件描述符非法。

poll的函数原型:
# include <poll.h>
int poll ( struct pollfd * fds, unsigned int nfds, int timeout);

参数说明:
fds:需要被监视的文件描述符集合;
nfds:被监视的文件描述符数量;
timeout:超时时间,有三种取值: 
负数:无限超时,一直等到一个指定事件发生;
0:立即返回,并列出准备好的文件描述符;
正数:等待指定的时间,单位为毫秒;
poll函数与select函数的最大不同之处在于:select函数有最大文件描述符的限制,一般1024个,而poll函数对文件描述符的数量没有限制。但select和poll函数都是通过轮询的方式来查询某个文件描述符状态是否发生了变化,并且需要将整个文件描述符集合在用户空间和内核空间之间来回拷贝,这样随着文件描述符的数量增加,相应的开销也随之增加。

epoll是在Linux内核2.6引进的,是select和poll函数的增强版。与select相比,epoll没有文件描述符数量的限制。epoll使用一个文件描述符管理多个文件描述符,将用户关心的文件描述符事件存放到内核的一个事件列表中,这样在用户空间和内核空间只需拷贝一次。

epoll对文件描述符的操作由两种模式:水平触发LT(level trigger)和边沿触发ET(edge trigger)。默认的情况下为LT模式。LT模式与ET模式的区别在于:

LT模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。
ET模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。
ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。

从上面对select/poll/epoll函数的介绍,可以知道epoll与select/poll相比,具有如下优势:
监视的描述符数量不受限制,所支持的FD上限是最大可以打开文件的数目;
I/O效率不会随着监视fd的数量增长而下降。epoll不同于select和poll轮询的方式,而是通过每个fd定义的回调函数来实现的,只有就绪的fd才会执行回调函数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值