I/O复用:即使得程序可以同时监听多个文件描述符。
1、select系统调用
(1)系统调用原型:
图1(2)参数介绍:其中nfds指定被监听的文件描述符的总数,通常被设置为select监听的所有文件描述符中的最大值加1;readfds、writefds、exceptfds分别指向可读、可写和异常等对应的文件描述符集合,select调用返回时,内核将修改它们来通知应用程序哪些文件描述符已经就绪,它们都是fd_set结构指针类型,fd_set结构体定义如下:
图2由以上定义可知,fd_set结构体仅包含一个整形数组,该数组的每个元素的每一位标记一个文件描述符。_FD_SETSIZE限制了select能同时处理的文件描述符的总量。由于位操作过于繁琐,采用下面的函数来访问fd_set结构体中的位:
图3timeout用来设置select函数的超时时间,它是一个timeval结构类型的指针,该结构体定义如下:
图4若给timeout的两个参数都传0,则select立即返回;若给timeout传递NULL,则select一直阻塞,直到某个文件描述符就绪。
(3)系统调用介绍:select成功时返回就绪(可读、可写和异常)文件描述符的总数。
2、poll系统调用
(1)系统调用原型:
图5(2)参数介绍:fds是一个pollfd结构类型的数组,它指定所有我们感兴趣的文件描述符上发生的可读、可写和异常的事件,pollfd结构体定义如下:
图6nfds指定被监听事件集合fds的大小,其定义如下:
图7timeout指定poll的超时值,单位是ms;当timeout为-1时,poll调用将永远阻塞,直到某个事件发生,当timeout值为0时,poll调用立即返回。
3、epoll系统调用
声明:epoll与select、poll不同,它将用户关心的文件描述符放在内核里的一个事件表中,不像select、poll,每次调用都要重复传入文件描述符集或事件集。
(1)系统调用相关函数介绍:
一、epoll需要使用一个额外的文件描述符来唯一标识内核中的这个事件表,通过以下函数创建:
图8其中size只是告诉内核事件表需要多大的提示。
二、操作内核事件表的函数:
图9epfd是上述函数创建得到的文件描述符,通过它来访问内核事件表,op指定操作的类型,有如下3种:
图10event指定事件,它是epoll_event结构指针类型,有关定义如下:
图11其中events事件常用的有EPOLLIN(读事件)和EPOLLOUT(写事件)。epoll_data_t是一个联合体,它的4个成员中使用最多的是fd,它指定事件所从属的目标文件描述符。
函数成功返回0,失败返回-1。
三、在一段时间内等待一组文件描述符上的事件的函数:
图12函数成功返回就绪的文件描述符的个数,失败返回-1。
maxevents指定最多监听多少个事件,必须大于0,该函数若检测到事件,就将所有就绪事件从内核事件表中复制到它的第二个参数events指向的数组中。
四、epoll的LT(水平触发)和ET(边沿触发)模式。
LT:系统默认的工作模式,即阻塞,也就是会再次通知应用程序响应事件;
ET:通过注册EPOLLET事件设置,它不会再次通知。
五、epoll的EPOLLONESHOT事件
一个socket上的某个事件很可能被触发多次,在并发程序中,一个线程或者进程在读取完某个socket上的数据后开始处理这些数据,而在数据处理过程中该socket上又有新数据可读(即EPOLLIN再次触发),这是就会唤醒另外一个线程或者进程来读取这些数据,这种局面就是两个线程(进程)同时操作一个socket,为了避免这种情况,注册EPOLLONESHOT事件可以使一个socket连接在任一时刻仅被一个线程(进程)处理。
4、三组I/O复用函数的比较
图13epoll适用于连接数量多,但活动连接少(因为若活动连接数多,会频繁调用回调函数)。
5、基于Reactor模式的I/O框架库包括以下组件:
(1)句柄-----------即I/O框架库要处理的对象(I/O事件、信号、定时事件,三者合称为统一事件源)。一个事件源通常和一个句柄绑定在一起。当内核检测到就绪事件时,它将通过句柄来通知应用程序这一事件。在LINUX环境下,I/O事件对应的句柄是文件描述符,信号事件对应的句柄是信号值。
(2)事件多路分发器-----------因为程序需要循环地等待并处理随机或异步到来的事件,而等待事件一般使用I/O复用技术实现。将系统支持的各种I/O复用系统调用封装成统一接口,称为事件多路分发器(方法是等待事件的核心函数,内部调用的是select、poll、epoll_wait),此外,它还要提供register_event(向事件多路分发器中添加事件,被register_handler调用)和remove_event(删除事件多路分发器中的事件,被remove_handler调用)的方法。
(3)事件处理器和具体事件处理器-----------事件处理器执行事件对应的业务逻辑。它通常包含一个或多个handle_event回调函数(等待事件-->依次处理所有就绪事件对应的事件处理器),这些回调函数在事件循环中被执行。I/O框架库提供的事件处理器通常就是个接口(通常斜写为虚函数),用户需要继承它来实现自己的事件处理器,即具体事件处理器。此外,它还提供一个get_handle方法,返回与该事件处理器关联的句柄(解释:当事件多路分发器检测到有事件发生时,它是通过句柄来通知应用程序的,而只有事件处理器和句柄绑定,才能在事件发生时获取到正确的事件处理器)。
2、使用Libevent的案例:高性能的分布式内存对象缓存软件memcached
3、高性能I/O框架库Libevent的优点:(1)跨平台支持(2)统一事件源(3)线程安全
网友评论