事件驱动编程是一种编程范式,这里程序的执行流由外部事件来决定。它的特色是包含一个事件循环,当外部事件发生时使用回调机制来触发相应的处理。另外两种常见的编程范式是(单线程)同步以及多线程编程。 html
当咱们面对以下的环境时,事件驱动模型一般是一个好的选择: linux
网络应用程序一般都有上述这些特色,这使得它们可以很好的契合事件驱动编程模型。 web
具体介绍见:IO多路复用(番外篇) 编程
讨论的背景是Linux环境下的network IO 缓存
IO多路复用(epool)和gevent模块(协程)区别与联系: 安全
都是遇到IO就切换,在linux下底层都是经过libevent.so实现。能够认为gevent是对IO多路复用更上层的封装,IO多路复用是其默认设置,其更专一于任务之间的切换。网络
操做系统的核心是内核,能够访问受保护的内存空间,也有访问底层硬件设备的全部权限。为了保证内核的安全,用户进程不能直接操做内核(kernel),系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。 多线程
为了控制进程的执行,内核必须有能力挂起正在CPU上运行的进程,并恢复之前挂起的某个进程的执行。这种行为被称为进程切换。 并发
正在执行的进程,因为期待的某些事件未发生,如请求系统资源失败、等待某种操做的完成、新数据还没有到达或无新工做作等,则由系统自动执行阻塞原语(Block),使本身由运行状态变为阻塞状态。可见,进程的阻塞是进程自身的一种主动行为,也所以只有处于运行态的进程(得到CPU),才可能将其转为阻塞状态。当进程进入阻塞状态,是不占用CPU资源的。 app
文件描述符(File descriptor)是计算机科学中的一个术语,是一个用于表述指向文件的引用的抽象化概念。文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每个进程所维护的该进程打开文件的记录表。(文件句柄是真实的文件对象)
注:文件描述符这一律念每每只适用于UNIX、Linux这样的操做系统。
又被称做标准 I/O,大多数文件系统的默认 I/O 操做都是缓存 I/O。在 Linux 的缓存 I/O 机制中,操做系统会将 I/O 的数据缓存在文件系统的页缓存( page cache )中,也就是说,数据会先被拷贝到操做系统内核的缓冲区中,而后才会从操做系统内核的缓冲区拷贝到应用程序的地址空间。
缺点:
数据在传输过程当中须要在应用程序地址空间和内核进行屡次数据拷贝操做,这些数据拷贝操做所带来的 CPU 以及内存开销是很是大的。
对于一次IO访问(以read举例),数据会先被拷贝到操做系统内核的缓冲区中,而后才会从操做系统内核的缓冲区拷贝到应用程序的地址空间。(内核态到用户态数据拷贝)
因此,当一个read操做发生时,它会经历两个阶段:
1. 等待数据准备 (Waiting for the data to be ready)
2. 将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)
正是由于这两个阶段,linux系统产生了下面五种网络模式的方案。
阻塞IO(bloking IO)
linux下,默认状况下全部的socket都是blocking。其特色就是在IO执行的两个阶段都是阻塞的 。
非阻塞 I/O(nonblocking IO)
linux下,能够经过设置socket使其变为non-blocking。其特色是IO执行的第一个阶段不阻塞,用户进程不断的主动询问kernel数据有没有准备好,kernel作出相应的回应,但IO执行的第二个阶段仍然是阻塞的。
I/O 多路复用( IO multiplexing)
就是咱们说的select,poll,epoll,也称这种IO方式为event driven IO。select/epoll的好处就在于单个process就能够同时处理多个网络链接的IO。它的基本原理就是select,poll,epoll这个function会不断的轮询所负责的全部socket,当某个socket有数据到达了,就通知用户进程数据准备好了,但IO执行的第二个阶段仍然是阻塞的。
这个图和blocking IO的图其实并无太大的不一样,事实上,其性能还更差一些。由于这里须要使用两个system call (select 和 recvfrom),而blocking IO只调用了一个system call (recvfrom)。可是,用select的优点在于它能够同时处理多个connection。
因此,若是处理的链接数不是很高的话,使用select/epoll的web server不必定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。select/epoll的优点并非对于单个链接能处理得更快,而是在于能处理更多的链接。
在IO multiplexing Model中,实际中,对于每个socket,一般都设置成为non-blocking,可是,如上图所示,整个用户的process实际上是一直被block的。只不过process是被select这个函数block,而不是被socket IO给block。
异步 I/O(asynchronous IO)
linux下的asynchronous IO其实用得不多。
用户进程发起read操做以后,就能够开始去作其它的事。从kernel的角度,当它收到一个asynchronous read以后,首先它会马上返回信号给用户进程(确认收到请求的信号?),因此不会对用户进程产生任何block。而后,kernel等待数据准备完成后,将数据拷贝到用户内存,拷贝完成以后,kernel会给用户进程发送一个signal,告诉它read操做完成了。
各个IO Model的比较
具体介绍见:Python Select 解析
select目前几乎在全部的平台上支持,其良好跨平台支持也是它的一个优势,可是使用select单个进程可以监视的文件描述符的数量存在最大限制,在Linux上通常为1024,不过能够经过修改宏定义甚至从新编译内核的方式提高这一限制。select监视到文件描述符有活跃,只向用户进程返回有活跃信号,没有返回具体活跃的文件描述符,用户进程还需再次循环检查浪费时间和资源。
poll和select在本质上没有多大差异,可是poll没有最大文件描述符数量的限制,能够看做是一个过渡阶段。
epoll直到Linux2.6才出现,它几乎具有了以前所说的一切优势,被公认为Linux2.6下性能最好的多路I/O就绪通知方法。epoll能够同时支持水平触发和边缘触发(Edge Triggered,只告诉进程哪些文件描述符刚刚变为就绪状态,它只说一遍,若是咱们没有采起行动,那么它将不会再次告知,这种方式称为边缘触发),理论上边缘触发的性能要更高一些,可是代码实现至关复杂。
注:epoll在linux上的文件描述符数量,可能会受到OS用户最大链接数限制,注意调整。
服务端
客户端
封装好的IO多路复用模块,默认使用epool,当系统不支持时使用select。
服务端
多并发客户端
Twisted本身用异步形式重写了SSH、DNS、FTP、HTTP等,代码比较复杂,游戏开发会用到。
http://www.cnblogs.com/alex3714/articles/5248247.html
参考:
http://www.cnblogs.com/alex3714/articles/5248247.html
http://www.cnblogs.com/alex3714/articles/5876749.html
http://www.cnblogs.com/alex3714/p/4372426.html