代表select阻塞最多10秒超时返回,监听标准输入的 读ready状态,会发现程序处于阻塞状态,直到10秒后select返回,打印 0:,也就是没有ready
运行./select - 0r 代表select不会超时直到 标准输入 读ready,程序会一直挂起,直到我们敲回车,返回0:r
运行./select 5 0r 1w 代表最多5秒超时,同时监听标准输入的读状态,和标准输出的写状态,返回0: 1:w
2.2、poll
poll跟select机制基本一样,不同点在于select将监听的文件描述符分开到3个集合中,poll只需一个文件描述符列表
1>定义
#include <poll.h>
int poll(struct pollfd fds[], nfds_t nfds, int timeout);
2>参数
- struct pollfd fds:文件描述符数组
struct pollfd {
int fd; /* File descriptor */
short events; /* Requested events bit mask */
short revents; /* Returned events bit mask */
};
- nfds:fds数组中文件描述符的总数
- timeout:超时时间,传-1阻塞直到有文件描述符ready,传0不阻塞,传>0的值,不管有没有ready的文件描述符,到指定时间超时返回
3>返回值
- 返回-1:有错误产生
- 返回0:没有任何文件描述符ready
- 返回正数:fds元素revents不为0的文件描述的个数
3、poll和select存在哪些问题?
1、每次调用poll()或select(),内核必须检查传过来的所有文件描述符,当文件描述符超过一定数量后,光是检查这一步就已经非常耗时了
2、每次调用poll()或select(),需要从用户态初始化文件描述符的数据结构,然后传递到内核态,在内核态检查IO状态,然以将状态更新到文件描述符数据结构,再返回这个数据结构,数据需不断的在用户态和内核态间拷贝,同样当文件描述符数量很大时,非常耗费CPU时间
3、每次调用完poll()或select(),还要遍历返回的所有文件描述符,判断状态,浪费时间
解决以上问题的关键是:1)不要每次都在内核态和用户态复制这些监听的文件描述符数据 2)只返回IO ready 的文件描述符,不要只标识状态,自己还需要再遍历一遍,因此,Linux给了我们epoll。
二、Epoll
Linux的epoll,也是I/O多路复用的一种实现方式,同样是同时监听多个文件描述符的I/O状态,他有如下几个优势:
- 同时监听的大量文件描述符,效率高于select和epoll
- epoll api同时提供了水平触发通知和边缘触发通知可选,select和epoll只有水平触发通知的方式(两种通知方式后面会讲)
- 对于监听文件描述的具体事件(读、写等,通过bit mask的方式),更为灵活
不像select和poll直接就是系统调用的函数,epoll由3个系统调用函数组成
1、epoll的3个函数
1.1、epoll_create
通过调用epoll_create,创建一个新的epoll内核数据结构实例,返回该实例的文件描述符,然后,调用进程可以使用这个文件描述符向epoll实例添加、删除或修改它想要监视的其他文件描述符。
#include <sys/epoll.h>
int epoll_create(int size);
1.2、epoll_ctl
进程可以通过调用epoll_ctl向epoll实例添加它希望监视的文件描述符,所有这些文件描述符由epoll实例维护在interest list中。当被监视的文件描述符为I/O做好准备时,他们就进入到ready list,ready list是interest list的一个子集
定义:
#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
参数:
- epfd:epoll_create返回的文件描述符
- fd:准备加入监视列表(interest list)的文件描述符
- op:要对这个fd做什么操作,有3种
- 添加:EPOLL_CTL_ADD
- 删除:EPOLL_CTL_DEL
- 修改:EPOLL_CTL_MOD
- event:指向epoll_event结构体的指针,结构体中存储着我们实际想要监视fd的哪些事件
epoll_event结构:
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
events:
事件的成员是位掩码,比如fd是一个socket,我们可能希望监视他,以获取套接字缓冲区上的新数据(使用EPOLLIN),如果希望事件的通知机制使用边缘触发的方式,可以使用EPOLLET。也就是读操作就绪,使用边缘触发的通知方式,需要这样指定events:EPOLLIN | EPOLLET
所有可以指定的events见http://man7.org/linux/man-pages/man2/epoll_ctl.2.html
data:
epoll_data_t 是一个union,可以存储一些数据,当该文件描述符ready的时候,返回给调用监听的进程
typedef union epoll_data {
void *ptr; /* Pointer to user-defined data */
int fd; /* File descriptor */
uint32_t u32; /* 32-bit integer */
uint64_t u64; /* 64-bit integer */
} epoll_data_t;
返回值:返回0执行成功,-1发生错误
1.3、epoll_wait
通过调用epoll_wait,可以获取之前监听的所有事件(interest list)中I/O准备好的事件(ready list)
定义:
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *evlist, int maxevents, int timeout);
参数: