一、什么是I/O多路复用?
I/O多路复用是指内核指定的一个或多个IO条件准备读取,它就通知该进程。适用于如下场景:
- 当客户处理多个描述符时(一般是交互式输入和网络套接字),必须使用IO复用
- 一个tcp服务器既要监听套接口,又要处理已经连接的套接口,一般也要用到IO复用
- 一个服务器既要处理tcp又要处理udp,一般也要使用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 *execptset,const struct timeval *timeout)
返回值:就绪描述符的数目,超时返回0,出错返回-1。
函数参数如下介绍:
(1). 第一个参数maxfdp1指定待测试的描述字个数,它的值是待测试的最大描述符+1,描述符0、1、2、3、……maxfdp1 - 1均将被测试。文件描述符是从0开始的。
(2). 中间的三个参数readset,writeset,exceptset指定我们要让内核测试读,写和异常条件的描述符。如果对某一个条件不感兴趣,就可以把它设置为空指针。这三个参数可以理解为一个集合,集合中放的是文件描述符。可以通过以下四个宏进行设置
void FD_ZERO(fd_set *fdset) //清空集合中设置的位
void FD_SET(int fd,fd_set *fdset) //设置fdset的位fd
void FD_CLR(int fd,fd_set *fdset) //清楚fdset的位fd,用来删除其中的位
int FD_ISSET(int fd,fd_set *fdset) //测试fdset中的位fd是否被设置,检测它是否可以读写
(3). timeout用来设置select函数的超时时间
struct timeval{
long tv_sec ; //秒
long tv_usec; //微妙
};
三、poll函数
poll的机制和select类似,与select在本质上没有多大区别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是poll没有最大文件描述符数量的限制。poll和select同样存在一个缺点就是,包含大量文件描述符的数组被整体复制与用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它们的开销随着文件描述符数量的增加而线性增大。
poll的函数原型如下:
#include <poll.h>
int poll(struct pollfd *fds,unsigned int nfds,int timeout);
pollfd 结构体定义如下:
struct pollfd{
int fd; // 文件描述符
short events; // 等待的事件
short revents; //
实际发生了的事件
};
每一个pollfd结构体指定了一个被监视的文件描述符,可以传递多个结构体,指示poll()监视多个文件描述符。每个结构体的events域是监视该文件描述符的事件掩码,由用户来设置这个域。revevts域是文件描述符的操作结果事件掩码,内核在调用返回时设置这个域。events域中请求的任何事件都可能在revents域中返回。
timeout参数指定等待的事件,无论IO是否准备好,poll都会返回。timeout指定为负数值表述无限超时,使poll()一直挂起直到一个指定事件发生;timeout为0指示poll函数调用立即返回并列出准备好的IO文件的描述符,但并不等待其他的事件。这种情况下,poll()就像它名字那样,一旦选举出来,立即返回。
四、epoll函数
epoll是上面的select和poll的增强版。相对于select和poll来说,epoll更加灵活,没有描述符限制。epoll用一个文件描述符管理多个描述符,epoll把用户关心的文件描述符上的事件放在内核的一个事件表中,从而无需向select和poll那样每次调用都需要重复传入文件描述集或者事件集,只需要copy一次就可以了。
epoll的操作过程需要三个接口,分别如下:
#include <sys/epoll.h>
int epoll_creat(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)
创建一个额外的文件描述符,来唯一标识内核中的事件表。size用来告诉内核这个监听的数目一共多大。当创建好这个文件句柄后,它就会占用一个fd值,在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。
(2).int epoll_ctl(int epfd,int op,int fd,struct epoll_evernt *event);
fd参数是要操作的文件描述符,op参数指定操作类型,有以下三种:
- EPOLL_CTL_ADD往时间表中注册fd上的事件
- EPOLL_CTL_MOD修改fd上的注册事件
- EPOLL_CTL_DEL删除fd上的注册事件
event参数指定事件,它是epoll_event结构指针类型,定义如下:
struct epoll_ecent{
_uint32_t events; //epoll事件
epoll_data_t data; //用户数据
};
其中events成员描述事件类型。epoll_data_t是一个联合体,其中4个成员中使用最多的是fd。
(3). int epoll_wait(int epfd,struct epoll_event *event,int maxevents,int timeout);
该函数成功时返回就绪的文件描述符的个数,失败时返回-1并设置errno;timeout参数是超时时间。maxevents参数指定最多监听多少个时间,必须大于0。
epoll_wait函数如果监测到事件,就将所有就绪的事件从内核事件表中复制到它的第二个参数events指向的数组中。这个数组中值用于输出epoll_wait监测到的就绪事件,而不像select和poll的数组参数那样既用于传入用户注册的事件,又用于输出内核监测到的就绪事件。这就大大的提高了应用程序索引就绪文件描述符的效率。