IO多路转接
IO多路转接也叫做IO多路复用,它是一种处理高并发的IO事件监控。它可以同时对大量的描述符进行监控,监控其是否具备了IO条件。
就绪:包括了读就绪事件(就是有数据到来的时候),写就绪事件(缓冲区有空闲的空间),异常事件(发生异常)。对于服务器来说,很多时候我们都是监控的读事件,对于写事件和异常事件都只会在特定的情况下使用。
IO多路转接之select
select简介
系统提供了select系统调用来实现多路复用的输入输出模型。
- select系统调用是用来让我们的程序监视多个文件描述符的状态变化的
- 程序会停在select这里等待,直到被监视的文件描述符有一个或多个发生了状态改变
select函数原型
头文件: #include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout)
- nfds是需要监视的最大的文件描述符值+1
- readfds,writefds,exceptfds分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集 合及异常文件描 述符的集合;
- timeout为结构timeval,用来设置select()的等待时间
timeout:
- NULL:则表示select()没有timeout,select将一直被阻塞,直到某个文件描述符上发生了事件;
- 0:仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生。
- 固定的时间值:如果在指定的时间段里没有事件发生,select将超时返回。
返回值
- 执行成功则返回文件描述词状态已改变的个数
- 如果返回0代表在描述词状态改变前已超过timeout时间
- 当有错误发生时则返回-1,错误原因存于errno,此时参数readfds,writefds, exceptfds和timeout的值变成不可预测。
fd_set结构体
fd_set 是一个结构体,严格来说它是一个位图,通过使用位图中对应的位来表示要监视的文件描述符。它提供了一组操作fd_set的接口, 来比较方便的操作位图.
void FD_CLR(int fd, fd_set *set); // 用来清除描述词组set中相关fd 的位
int FD_ISSET(int fd, fd_set *set); // 用来测试描述词组set中相关fd 的位是否为真
void FD_SET(int fd, fd_set *set); // 用来设置描述词组set中相关fd的位
void FD_ZERO(fd_set *set); // 用来清除描述词组set的全部位
select缺点
- 每次调用select, 都需要手动设置fd集合, 使用起来非常不便.
- 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
- 同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
- select支持的文件描述符数量太小
IO多路转接之poll
poll函数简介
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
// pollfd结构
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
参数说明
- fds是一个poll函数监听的结构列表. 每一个元素中, 包含了三部分内容: 文件描述符, 监听的事件集合, 返 回的事件集合.
- nfds表示fds数组的长度.
- timeout表示poll函数的超时时间, 单位是毫秒
events和revents的取值
返回值说明
- 返回值小于0, 表示出错;
- 返回值等于0, 表示poll函数等待超时;
- 返回值大于0, 表示poll由于监听的文件描述符就绪而返回
poll优缺点
优点
- pollfd结构包含了要监视的event和发生的event,不再使用select“参数-值”传递的方式. 接口使用比select更加简单。
- poll并没有最大数量限制 (但是数量过大后性能也是会下降).
缺点
- 和select函数一样,poll返回后,需要轮询pollfd来获取就绪的描述符.
- 每次调用poll都需要把大量的pollfd结构从用户态拷贝到内核中.
- 同时连接的大量客户端在一时刻可能只有很少的处于就绪状态, 因此随着监视的描述符数量的增长, 其效 率也会线性下降.
IO多路转接之epoll
epoll 是Linux下性能最高的IO多路转接模型。epoll 与select和poll在使用和实现上有很大区别。首先,epoll使用一组函数来完成,而不是单独的一个函数;其次,epoll把用户关心的文件描述符上的事件放在内核里的一个事件表中,无须向select和poll那样每次调用都要重复传入文件描述符集合事件集。
epoll相关的系统调用
- epoll_create
// #include <sys/epoll.h>
//创建一个epoll句柄
int epoll_create(int size);
//自从linux2.6.8之后,size参数是被忽略的.
//用完之后, 必须调用close()关闭.
- epoll_ctl
//epoll的事件注册函数,在内核的eventpoll结构中添加移除,修改所监控的事件结构。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
//第二个参数
EPOLL_CTL_ADD :注册新的fd到epfd中;
EPOLL_CTL_MOD :修改已经注册的fd的监听事件;
EPOLL_CTL_DEL :从epfd中删除一个fd
它不同于select()是在监听事件时告诉内核要监听什么类型的事件, 而是在这里先注册要监听的事件类型.
- 第一个参数是epoll_create()的返回值(epoll的句柄).
- 第二个参数表示动作,用三个宏来表示.
- 第三个参数是需要监听的fd.
- 第四个参数是告诉内核需要监听什么事
struct epoll_event 结构如下
struct epoll_event
{
__int32_t events; //epoll事件
epoll_data_t data; //用户数据
};
typedef union epoll_data
{
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
}epoll_data;
在使用epoll_ctl时,是把fd添加、修改到内核事件表中,或从内核事件表中删除fd的事件。如果是添加事件到事件表中,可以往data中的fd上添加事件events,或者不用data中的fd,而把fd放到用户数据ptr所指的内存中(因为epoll_data是一个联合体,只能使用其中一个数据),再设置events。
- epoll_wait
//它在一段时间内等待一个组文件描述符上的事件。
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
- timeout参数和select与poll相同,指定一个超时时间
- maxevents指定最多监听多少个事件
- events是一个传出型参数,epoll_wait函数如果检测到事件就绪,就将所有就绪的事件从内核事件表(epfd所指的文件)中复制到events指定的数组中。这个数组用来输出epoll_wait检测到的就绪事件,而不像select与poll那样
epoll的底层实现
当某一进程调用epoll_create方法时,Linux内核会创建一个eventpoll结构体,这个结构体中有两个成员与epoll的使用方式密切相关,一个是红黑树,另一个是双向链表。红黑树中存储着所有添加到epoll中的需要监控的事件,双链表中则存放着将要通过epoll_wait返回给用户的满足条件的事件。这些事件都会挂载在红黑树中,如此,重复添加的事件就可以通过红黑树而高效的识别出来(红黑树的插
入时间效率是log(n),其中n为树的高度).而所有添加到epoll中的事件都会与设备(网卡)驱动程序建立回调关系,也就是说,当响应的事件发生时会调用这个回调方法.这个回调方法在内核中叫ep_poll_callback,它会将发生的事件添加到rdlist双链表中.通过这种方式大大的提高了效率。
epoll的工作模式
epoll 对文件描述符的操作有两种模式:LT(level trigger:水平触发模式,default)和 ET(edge trigger:边沿触发模式)。
- LT模式:当 epoll_wait 检测到描述符事件发生并将此事件通知应用程序后,应用程序可以不处理该事件,下次调用 epoll_wait 时,会再次向应用程序通知此事件,直到事件被处理,可能会重复处理事件。
- ET 模式:当 epoll_wait 检测到描述符事件发生并将此事件通知应用程序后,应用程序必须立即处理该事件,如果不处理,下次调用 epoll_wait 时,不会再次向应用程序通知此事件,可能会丢失事件处理。
epoll的优缺点
- 优点
1)epoll没有监控的上线
2)采用事件结构简化了select监控集合的监控流程
3)epoll是一个异步阻塞操作,发起调用,让操作系统进行文件描述符的监控,使用事件回调函数对描述符进行监控,避免了select的遍历轮询,性能不会随着文件描述符增多而下降
4)epoll发起调用进行等待,循环判断内核中epoll就绪时间链表是不是为空来确定是否有就绪事件,若有就绪事件,则将对应的事件拷贝到用户态,直接告诉了用户那些描述符就绪了,不需要循环判断。
5)epoll描述符的事件结构,只需要向内核中拷贝一次,不需要每次都拷贝 - 缺点
1)不能跨平台
2)延时时间只能精确到毫秒