原因:应用程序通常需要在多于一个文件描述符上阻塞,在不使用线程,尤其是独立处理每一个文件的情况下,进程无法在多个文件描述符上同时阻塞。如果读一个未准备好的文件描述符,进程会被阻塞,不能再操作其他文件,可能阻塞只有几秒,但是应用无响应也会造成不好的用户体验。
解决:非阻塞IO。缺点:进程需要不断的发起IO请求,直到响应的fd准备好,其次进程要是可以睡眠,处理器运行效率更高,等到一个或多个文件描述符可以进行IO操作时再唤醒进程。
于是产生了IO多路复用:
1、IO多路复用允许应用在多个文件描述符上同时阻塞,并在某个文件描述符可以读写的时候收到通知。
2、IO多路复用设计原则:
1、当任何文件描述符准备好I/O的时候告诉通知进程;2、在一个或者多个文件描述符就绪之前时钟处于睡眠状态;3、哪个fd准备好了;4、在不阻塞的情况下,处理所有IO就绪文件描述符;5、返回第一步,重新开始。
3、I/O多路复用方案:
3.1:select()系统调用:同步IO多路复用机制
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
FD_CLR(int fd, fd_set *set);//从指定集合中移除一个文件描述符
FD_ISSET(int fd, fd_set *set);//确认一个文件描述符是否在集合中
FD_SET(int fd, fd_set *set);//向指定集合添加文件描述符
FD_ZERO(fd_set *set);//清除指定集合中全部的文件描述符,select调用之前需要使用该宏
监测三类文件描述符: 当指定集合为NULL时,select不对此类进行监测。
readfds集合:确认其中是否有可读数据;
writefds集合:确认其中是否有一个写操作做可以无阻塞完成。
exceptefds:确认其中是否有异常发生或者出现带外数据。
timeout:当没有文件描述符处于就绪状态时,select将在timeout时间后返回。期间Unix系统处于未定义状态
n:最大文件描述符加一
select:在指定文件描述符准备好IO之前或者设定的时间到之前,select调用就会阻塞。【主动阻塞】
select返回值:所有三个集合中文件描述符的个数。
4、poll系统调用
#include <sys/poll.h>
int poll (struct pollfd *fds, unsigned int nfds, int timeout);
/*传递一个pollfd结构体类型的数组, 数组每个元素监视一个文件描述符, */
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events to watch */
short revents; /* returned events witnessed */
};
5、poll和select
6、epoll:
poll() 和 select() 每次调用时都需要所有被监听的文件描述符,内核必须遍历所有被监听的文件描述符,当这个表变得很大的时候,每次调用就成为了明显的瓶颈。
epoll() 把监听注册从 实际的监听中分离出来。
创建一个epoll实例:
/*
* 参数:size参数表示需要监听的描述符个数。一个大概的数字
* 返回:一个与创建的实例相关的描述符epfd,该描述符与真正的普通文件无关,用于后续系统调用
*/
int epoll_create (int size);
控制epoll :向指定的epfd中添加或删除需要监测文件描述符,并指定监听的事件
/*
* 输入:
* op:操作类型:EPOLL_CTL_ADD、EPOLL_CTL_DEL、EPOLL_CTL_MOD
* event.events:给定文件描述符上的监听事件 event.data:
*
*/
int epoll_ctl (int epfd, int op, int fd, struct epoll_event *event);
struct epoll_event{
__u32 events;//指定监测fd上的哪种触发事件
union{void *ptr; int fd; __u32 u32; __u64 u64;}data;//供用户使用,一般初始化为描述符fd,最后可以用于检查该结构体代表的事件属于那个fd
};
等待epoll事件:
int epoll_wait (int epfd, struct epoll_event *events, int maxevents, int timeout);
函数说明:
返回:成功时返回就绪的文件描述符的个数,失败时返回-1并设置errno
timeout:指定epoll的超时时间,单位是毫秒。当timeout为-1是,epoll_wait调用将永远阻塞,直到某个事件发生。当timeout为0时,epoll_wait调用将立即返回。
maxevents:指定最多监听多少个事件
events:检测到事件,将所有就绪的事件从内核事件表中复制到它的第二个参数events指向的数组中。用户根据之前设置的data字段可以判断该结构代表触发了那个文件描述上的事件。