I/O复用技术

来源:互联网 发布:steam mac 游戏推荐 编辑:程序博客网 时间:2024/05/29 14:09

Unix下5种IO模型:
阻塞式IO;
非阻塞式IO;
IO复用;
信号驱动IO;
异步IO;

阻塞IO会使调用进程在IO数据未准备好时阻塞。

非阻塞IO在数据未准备好时不是阻塞调用进程,而是返回一个错误。

IO复用使调用进程阻塞在IO复用函数上,而不是阻塞在真正IO系统调用上,其优势在于可以等待多个描述符就绪。

信号驱动IO首先要开启套接字的信号驱动IO功能,并通过sigaction系统调用安装一个信号处理函数,它不会阻塞进程,当数据准备好的时候,内核就为该进程产生一个SIGIO信号,然后就可以在信号处理函数中去处理IO操作,或者通知主循环处理IO操作。

异步IO是由内核通知我们IO操作何时完成,而信号驱动IO是内核通知我们何时可以启动一个IO操作。

下面介绍IO复用的函数

select函数

#include <sys/select.h>#include <sys/time.h>int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);返回:若有就绪描述符则为其数目,若超时则为0,若出错则为-1
struct timeval{    long  tv_sec;  //秒    long  tv_usec;  //微秒};

timeout参数:
1、NULL —— 永远等待下去
2、指定秒数和微秒数 —— 等待一段固定时间
3、定时器的值为0 —— 根本不等待

readset, writeset, exceptset指定要让内核测试读、写和异常条件的描述符。

四个宏:
void FD_ZERO(fd_Set *fdset); //清除fdset中所有bits
void FD_SET(int fd, fd_set *fdset); //在fdset中为fd turn on位
void FD_CLR(int fd, fd_set *fdset); //在fdset中为fd turn off位
int FD_ISST(int fd, fd_set *fdset); //测试fdset中的fd描述符

注意:描述符集内任何与未就绪描述符对应的位返回时均清成0,因此每次重新调用select函数时,我们都得再次把所有描述符集内所关心的位均置为1。

maxfdp1是待测试的最大描述符加1。

poll函数

#include <poll.h>int poll(struct pollfd *fdarray, unsigned long nfds, int timeout);返回:如有就绪描述符则为其数目,若超时则为0,若出错则为-1
struct pollfd{    int    fd;       //待检查的描述符    short  events;   //在fd上感兴趣的事件    short  revents;  //在fd上发生的事件}

事件标志:
POLLIN 普通或者优先级带数据可读
POLLRDNORM 普通数据可读
POLLRDBAND 优先级带数据可读
POLLPRI 高优先级带数据可读

POLLOUT 普通或优先级带数据可写
POLLWRNORM 普通数据可写
POLLWRBAND 优先级带数据可写

POLLERR 发生错误
POLLHUP 发生挂起
POLLNVAL 描述符不是一个打开的文件

nfds指定结构数组个数

timeout参数指定poll函数返回前等待多长时间,其值是一个应等待毫秒数的正直,如果值为INFTIM就永远等待,如果为0就立即返回。

epoll

epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。epoll除了提供select/poll那种IO事件的水平触发(Level Triggered)外(LT),还提供了边缘触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率(即高速模式ET)。传统的select/poll另一个致命弱点就是当你拥有一个很大的socket集合,不过由于网络延时,任一时间只有部分的socket是“活跃”的,但是select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。但是epoll不存在这个问题,它只会对“活跃”的socket进行操作---这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么,只有“活跃”的socket才会主动的去调用 callback函数,其他idle状态socket则不会。另外,epoll使用mmap加速内核与用户空间的消息传递,无论是select, poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就很重要,在这点上,epoll是通过内核与用户空间mmap同一块内存实现的。epoll有两种工作方式:LT 和 ET。LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表。ET (edge-triggered)是高速工作方式,只支持non-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once)。

epoll的接口

epoll操作过程需要三个接口,分别如下:

#include <sys/epoll.h>int epoll_create(int size);int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

(1) int epoll_create(int size);
  创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。
这里写图片描述

(2)int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
  epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。第一个参数是epoll_create()的返回值,第二个参数表示动作,用三个宏来表示:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下:

struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};

events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述
(3) int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
  等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。

这里写图片描述

0 0
原创粉丝点击