一、概念
1、作用
将获取数据的操作延后到数据到达以后。数据到达以后,有数据的文件描述符上会有一个就绪事件,服务器只需要处理就绪事件就可以。这样单进程、单线程就可以同时监听多个文件描述符。下面讨论的I/O复用方式都是单进程的。
2、linux下实现I/O复用的系统调用主要有:select、poll、epoll
二、select
1、作用
在一段时间内,采用轮询方式监听用户感兴趣的文件描述符上的可读、可写和异常事件。
2、函数原型
#include <sys/select.h>
int select(int nfds,fd_set *read,fd_set *write,fd_set *execpt,struct timeval *timeout)
参数意义:
nfds:最大文件描述符+1。如果监听的是3号文件描述符,那么此处的nfds就为4。
read、write、execpt:分别存储用户感兴趣的文件描述符的可读、可写、异常事件。
struct fd_set
{
long fd_set[32];
}
结构体总共1024位,每位监听一个文件描述符,所以最多可以监听1024个文件描述符,值为0~1023。
如上图所示,监听的是3号文件描述符,服务器如何判断该文件描述符上是否有时间发生?
fd_set[31] & 1<<3(3为3号文件描述符),即0000 & 1000==0,表示内核修改过,该文件描述符上有事件发生。如果是1000 & 1000==3,所得的结果不等于0,说明该文件描述符上没有事件发生。
timeout:如果为NULL,则表示select一直阻塞,直到有文件描述符就绪
函数返回值:返回值=0,表示超时
返回值>0,返回值的数表示就绪文件描述符的个数
返回值<0,select调用出错
注意:因为内核修改数据时,都是在同一个数组上修改,所以每次select之前,都必须重新设置可读、可写、异常事件
三、poll
1、作用
在指定事件内轮询一定数量的文件描述符,以测试其中是否有就绪者
2、函数原型
#include <poll.h>
int poll(struct pollfd *fds,int nfds,int timeout)
参数意义:
struct pollfd
{
int fd;//文件描述符
short events;//关注的事件类型
short revents;//由内核填充,指定文件描述符上发生了什么事情
}
nfds:数组元素个数,即数组大小
timeout:超出时间,单位毫秒。-1表示永远阻塞,0表示立即返回
函数返回值:返回值=0,超时
返回值>0,就绪文件描述符的个数
返回值<0,调用poll函数出错
3、select与poll的比较
(1)poll将监听的文件描述符和其关注的事件分开表示,poll不需要用三个结构体来表示不同的事件类型
(2)因为poll将用户注册的事件与内核修改的事件分开表示,所以poll不需要每次调用之前重新设置
4、部分代码
四、epoll
1、作用
epoll是Linux中特有的I/O复用函数,它在实现和使用上与select、poll有很大差异。epoll把用户关心的文件描述符上的事件放在内核里的一个事件表中,无需每次调用都要重复传入文件描述符集和事件集,所以epoll需要一个额外的文件描述符,来唯一标识内核中的内核事件表。
2、函数原型
#include <sys/epoll.h>
int epoll_create(int size);//创建一个内核事件表,返回内核表示的标识符id
int epoll_ctl(int epfd,int op,struct epoll_event *event);
参数意义:
epfd:epoll_create函数的返回值,内核事件表的id
op:操作
event:事件类型
struct epoll_event
{
int events;
epoll_data_t data;
}
int epoll_wait(int epfd,struct epoll_event *revents,int maxevents,int timeout);//这个函数是epoll系统调用的主要接口,它在一段超时时间内等待一组文件描述符上的事件
参数意义:
revents:是一个记录内核就绪事件的数组
3、部分代码
五、三种IO复用的比较