声明:以下内容是根据APUE和mycat两本著做中关于I/O模式的一些内容加上本身的一些理解整理而成,仅供学习使用。html
其中,2,3,4又能够总结成一类叫作非阻塞同步型,他们的实现方式上有一些差异。linux
默认状况下,全部套接字都是阻塞式的,进程调用recvfrom以后到数据成功返回以前,这段时间都是阻塞的。 设计模式
2. 非阻塞式I/O缓存
进程在调用recvfrom的时候,若是数据没有准备好,那么返回一个EWOULDBLOCK错误给进程,这时进程并不进入休眠状态,而是循环调用recvform直到数据准备完毕以后并返回数据处理成功的消息。当循环调用recvfrom这个动做,咱们称之为轮询(polling),这个过程当中将会大量消耗CPU的时间。在平常使用中这种模型并不常见。多线程
3. I/O复用(select和poll)app
进程使用I/O复用的时候,调用select或poll,阻塞在这两个系统调用的某一个之上,而不是阻塞在真正的I/O系统调用之上。 进程阻塞在select调用之上,直到资源已经准备好时,返回套接字可读,以后进程才调用recvfrom去获取数据。这种方式看起来和非阻塞式I/O模型对比并没什么优点,可是使用select的优点在于咱们能够等待多个文件描述符就绪。异步
其实select内部实现机制也就是使用轮询去检查多个文件描述符是否准备就绪,只要有一个文件描述符准备就绪,那么就通知进程去处理该文件。socket
与I/O复用密切相关的另外一种I/O模型是在多线程中使用阻塞式I/O.这种模型与I/O复用模型很类似,但它没有使用select阻塞在多个文件描述符上,而是使用多个线程(每一个文件描述符一个线程),这样每一个线程均可以自由的调用诸如recvfrom之类的阻塞式I/O系统调用了。(PS:或许这就是我以前看socketserver源码中使用select没理解的地方吧。。。)函数
4. 信号驱动式I/O(SIGIO)post
进程在最开始时使用sigaction发送系统调用,以后当即返回调用结果,这时进程处于非阻塞状态,去处理其余事情,当内核在描述符就绪时发送SIGIO信号通知进程,进程再使用recvfrom读取数据。该模型好处在于等待数据准备好这段时间,进程不会处于阻塞状态中。
5. 异步I/O(POSIX的aio_系列函数)
这种模型的工做机制是进程告诉内核要作某个操做,并让内核在完成整个操做以后通知咱们。这种方式和前面的信号驱动式I/O区别是信号驱动I/O由内核通知咱们什么时候能够启动I/O操做,而异步I/O模型是由内核完成了全部操做以后告诉咱们I/O操做完成了。
在linux中实现这种机制的方式是epoll,在2.6x之后的内核中才支持这种方式。。。
前面四种I/O模型都在处理真正的I/O时是阻塞模式,只有最后一种是真正的异步I/O模型,和POSIX定义的异步I/O匹配。
系统 I/O 可分为阻塞型, 非阻塞同步型及非阻塞异步型.
阻塞型 I/O 意味着控制权直接调用操做结束了才会回到调用者手里. 若是调用者被阻塞了, 这段时间作不了任何其它事情. 更郁闷的是,在等待 IO 结果的时间里,调用者所在线程此时没法腾出手来去响应其它的请求,这真是太浪费资源了。拿 read()操做来讲吧, 调用此函数的代码会一直僵在此处直至它所读的 socket 缓存中有数据到来.
相比之下,非阻塞同步是会当即返回控制权给调用者的。调用者不须要等等,它仅调用的函数获取两种结果:要么这次调用成功进行了;要么系统返回错误标识告诉调用者当前资源不可用,你再等等或者再试度看吧。好比 read()操做, 若是当前 socket 无数据可读,则当即返回 EWOULBLOCK/EAGAIN,告诉调用 read()者“ 数据还没准备好,你稍后再试” 。
在非阻塞异步调用中,稍有不一样。调用函数在当即返回时,还告诉调用者,此次请求已经开始了。系统会使用另外的资源或者线程来完成此次调用操做,并在完成的时候知会调用者(好比经过回调函数)。拿 Windows的 ReadFile()或者 POSIX 的 aio_read()来讲,调用它以后,函数当即返回,操做系统在后台同时开始读操做。
在以上三种 IO 形式中,理论上,非阻塞异步是性能最高、伸缩性最好的。
同步和异步是相对于应用和内核的交互方式而言的,同步须要主动去询问,而异步的时候内核在 IO事件发生的时候通知应用程序,而阻塞与非阻塞仅仅是系统在调用系统调用的时候函数的实现方式而已。
NIO 和 AIO 分别对应的两种设计模式:Reactor 和 Proactor
通常状况下,I/O 复用机制须要事件分享器(event demultBossiplexor). 事件分享器的做用,即将那些读写事件源分发给各读写事件的处理者,就像送快递的在楼下喊: 谁的什么东西送了, 快来拿吧。开发人员在开始的时候须要在分享器那里注册感兴趣的事件,并提供相应的处理者(event handlers),或者是回调涵数; 事件分享器在适当的时候会将请求的事件分发给返读 handler 或者回调涵数。
涉及到事件分享器的两种模式称为:Reactor和Proactor.
Reactor 模式是基于同步 I/O 的,而 Proactor 模式是和异步 I/O 相关的. 在 Reactor 模式中,事件分离者等待某个事件或者应用或操做的状态发生(好比文件描述符可读写,或者是 socket 可读写),事件分离者就把返个事件传给事兇注册的事件处理涵数或者回调涵数,由后者来作实际的读写操做。
而在 Proactor 模式中,事件处理者(或者代由事件分离者发起)直接发起一个异步读写操做(至关于请求),而实际的工做是由操做系统来完成的。发起时,须要提供的参数包括用于存放读到数据的缓存区,读的数据大小,或者用于存放外发数据的缓存区,以及这个请求完后的回调涵数等信息。事件分离者得知了这个请求,它默默等待这个请求的完成,而后转发完成事件给相应的事件处理者或者回调。举例来该,在 Windows 上事件处理者投递了一个异步 IO 操做(称有 overlapped 的技术),事件分离者等 IOCompletion 事件完成. 返种异步模式的典型实现是基于操做系统底层异步 API 的,所仌咱们可称之为“ 系统级别” 的或者“ 真正意义上” 的异步,由于具体的读写是由操做系统代劳的。
Reactor 不 Proactor 两种模式的场景区别:
下面是 Reactor 的作法:
1. 等待事件响应 (Reactor job)
2. 分发 “Ready-to-Read” 事件给用户句柄 ( Reactor job)
3. 读数据 (user handler job)
4. 处理数据( user handler job)
下面再来看看真正意义的异步模式 Proactor 是如何作的:
1. 等待事件响应 (Proactor job)
2. 读数据 (Proactor job)
3. 分发 “Read-Completed” 事件给用户句柄 (Proactor job)
4. 处理数据(user handler job)
仅上面能够看出,Reactor 和 Proactor 模式的主要区别就是真正的读取和写入操做是由谁来完成的,Reactor 中须要应用程序本身读取或写入数据,而 Proactor 模式中,应用程序不须要进行实际的读写过程,它只须要从缓存区读取或者写入便可,操做系统会读取缓存区或者写入缓存区到真正的 IO 设备。
最后结合下面的两张图更容易理解: