事件驱动模型就是在持续事务管理过程中,有当前时间点上出现的事件引发的调动可用资源还行相关任务,解决不断出现的问题,防止事件堆积的一种策略。
事件驱动模型一般是由事件收集器、事件发送器和事件处理器三部分基本单元组成。
事件收集器:
专门负责收集所有的事件,包括来自用户的(比如鼠标点击事件、键盘输入事件等等),来自硬件的(比如时钟事件)和来自软件的(比如操作系统、应用程序本身等)
事件发送器:
负责将事件收集器收集到事件分发到目标对象中(目标对象就是事件处理器所处的位置)
事件处理器:
负责具体事件的响应工作,它往往要到实现阶段才能确定
事件驱动机制的实现方式有多种,这里介绍一个 批次程序设计。批次程序设计是一种比较初级的程序设计方式。
批次程序设计的软件,其流程是在设计程序编码过程中觉得的,也就是说,在程序运行的过程中,事件的发生、事件的发送和事件的处理都是预先设计好的。事件驱动更多的是关注了事件产生的随机性。
事件驱动模型大致结构:
事件驱动模型Nginx中的事件驱动模型
事件收集器 和 事件发送器 的实现没有太大的特点,重点介绍 事件处理器。
目标对象中的事件处理器可以有以下实现方式:
1.事件发送器,每传递过来一个请求,目标对象就创建一个新的进程,调用事件处理器来处理该请求。
2.事件发送器,每传递过来一个请求,目标对象就创建一个新的线程,调用事件处理器来处理该请求。
3.事件发送器,每传递过来一个请求,目标对象就将其放入一个待处理事件的列表,使用非堵塞I/O方式调用事件处理器来处理该请求。
这三种处理方式,各有各的特点:
第一种方式,由于创建新的进程的开销比较大,会导致服务器性能比较差,但其实现相对来说比较简单;
第二种方式,由于要涉及到线程的同步,故可能会面临死锁、同步等一系列问题,编码比较复杂;
第三种方式,在编写程序代码时,逻辑比前面两种都复杂。
大多数网络服务器采用了第三方方式,逐渐形成了所谓的“事件驱动处理库”。
事件模型库
select库
创建所关注事件的描述符集合,可以关注其上面的读(Read)事件、写(Write)事件以及异常发生(Exception)事件,所以要创建三类事件描述符集合,分别用来收集读事件的描述符、写事件的描述符和异常事件的描述符。
(也就是说读事件描述是一个集合,写事件的描述符一个集合,异常事件描述符是一个集合)
调用底层提供的select()函数,等待事件发生。这里需要注意一点是,select的堵塞与是否设置堵塞非堵塞I/O是没有关系的。
然后轮询所有事件描述符集合中的每一个事件描述,检查是否有相应的时间发生,如果有,就进行处理。
poll库
poll库和select库的基本工作方式是一样的,都是先创建一个关注事件的描述符集合,再去等待这些事件发生,然后在轮询描述符集合,检查有没有事件发生,如果有,就进行处理。
poll库和select库的主要区别在于,select库需要为读事件、写事件和异常事件分别创建一个描述符集合,因此在最后轮询的时候,需要分别轮询这三个集合。而poll库只需要创建一个集合,在每个描述符对应的结构上分别设置读事件、写事件或者异常事件,最后轮询的时候,可以同时检查这三种事件是否发生。poll库是select库的优化实现。
epoll库
epoll属于poll库的一个变种。
poll库和select库在实际工作中,最大区别在于效率。
前面的几种库它们的处理方式都是创建一个待处理事件列表,然后把这个列表发送给内核,返回的时候,再去轮询检查这个列表,以判断事件是否发生。这样在描述符比较多的应用中,效率就显得比较低下了。
一种比较好的做法是,把描述符列表的管理交由内核负责,一但有某种事件发生,内核把发生事件的描述符列表通知给进程,这样就避免了轮询整个描述符列表。epoll库就是这样一种模型。
首先,epoll库通过相关调用通知内核创建一个有N个描述符的事件列表;然后,给这些描述符设置所关注的事件,并把它添加到内核的事件列表中去,在具体的编码过程中也可以通过相关调用对事件列表中的描述符进行修改和删除。
完成设置之后,epoll库就开始等待内核通知事件发生。某一事件发生后,内核将发生事件的描述符列表上报给epoll库。得到事件列表的epoll库,就可以开始进行事件处理了。
epoll库在Linux平台上是高效的。它支持一个进程打开大数目的事件描述符,上限是系统可以打开文件的最大数目;同时,epoll库的I/O效率不随描述符数目增加而线性下降,因为它只会对内核上报的“活跃”的描述符进行操作。
网友评论