IO发生时涉及的对象和步骤。对于一个network IO (这里我们以read举例),它会涉及到两个系统对象,一个是调用这个IO的process (or thread),另一个就是系统内核(kernel)。当一个read操作发生时,它会经历两个阶段:
1)等待数据准备 (Waiting for the data to be ready)
2)将数据从内核拷贝到进程中(Copying the data from the kernel to the process)
记住这两点很重要,因为这些IO模型的区别就是在两个阶段上各有不同的情况。
因为本人对C语言也不熟,所以简单抄了几个系统调用上来:
recvfrom
Linux系统提供给用户用于接收网络IO的系统接口。从套接字上接收一个消息,可同时应用于面向连接和无连接的套接字。
如果此系统调用返回值<0,并且 errno为EWOULDBLOCK或EAGAIN(套接字已标记为非阻塞,而接收操作被阻塞或者接收超时 )时,连接正常,阻塞接收数据(这很关键,前4种IO模型都设计此系统调用)。
select
select系统调用允许程序同时在多个底层文件描述符上,等待输入的到达或输出的完成。以数组形式存储文件描述符,32位机器位1024个,64位机器默认2048个。当有数据准备好时,无法感知具体是哪个流OK了,所以需要一个一个的遍历,函数的时间复杂度为O(n)。
poll
以链表形式存储文件描述符,没有长度限制。本质与select相同,函数的时间复杂度也为O(n)。
epoll
是基于事件驱动的,如果某个流准备好了,会以事件通知,知道具体是哪个流,因此不需要遍历,函数的时间复杂度为O(1)。
sigaction
用于设置对信号的处理方式,也可检验对某信号的预设处理方式。Linux使用SIGIO信号来实现IO异步通知机制。
IO模型主要由阻塞式I/O模型(BIO),非阻塞式I/O模型(NIO),I/O复用模型(NIO),信息驱动式I/O模型(NIO),异步I/O模型(AIO)。
阻塞式I/O模型(也就是BIO,Blocking IO)
也称为同步阻塞I/O,是比较传统的I/O模型。

用户态进程发出一个IO请求时,会调用\(recvfrom\)这个系统调用去内核态获取数据,如果当前内核态中数据没有准备好,那么用户态的进程会让出CPU时间片,一直阻塞等待,不会进行其他操作。
直到内核态中的数据准备好,将数据拷贝到用户态空间内存,然后recvfrom返回成功的信号,此时用户态进行才解除阻塞的状态,处理收到的数据。
阻塞I/O在I/O执行的两个阶段(等待数据和拷贝数据)中都被block了。
非阻塞时I/O模型(Non-Blocking IO)
也称同步非阻塞I/O,默认创建的\(socket\)都是阻塞的,想要使用非阻塞的I/O要求\(socket\)被设置为NONBLOCK。
在非阻塞式I/O模型中,当用户态进程发出read操作,而内核态中的数据还没有准备好,它并不会阻塞用户态进程,而是会立即返回一个error。而用户态进程看到自己发起了read请求没有被阻塞马上收到了error时,用户态进程就知道数据还没有准备好。
于是进程会不断询问内核,不断发起read请求,直到内核准备好数据。 用户态进程调用\(recvfrom\)接收数据,当前并没有数据报文产生,此时\(recvfrom\)返回\(EWOULDBLOCK\),用户态进程会一直调用\(recvfrom\)询问内核。
待内核准备好数据的时候,之后用户态进程不再询问内核,待数据从内核复制到用户空间,\(recvfrom\)成功返回,用户态进程开始处理数据。在非阻塞式IO中,用户态进程其实是需要不断的主动询问内核态数据准备好了没有。
非阻塞I/O在I/O的两个阶段中,等待数据时是非阻塞的,但是进程需要盲等,不断地轮询,拷贝数据阶段是阻塞的。
多路复用I/O(Multiplexing IO)
多路复用I/O也称事件驱动(event driven I/O)。其本质上是将轮询从用户态转移到内核态;多路复用器解决的是IO状态的问题。
select和poll
当没有I/O事件时,内核态进程处于阻塞状态。当有I/O事件时,就会有一个内核态的代理去唤醒内核态进程,对所有的文件描述符进行轮询(一个文件描述符对应一个与用户态Socket连接),来处理I/O事件。(这里的代理也就是select和poll,select基于数组,只能监测有限个,poll基于链表可监测无限个)
select、poll会不断的轮询其所负责的所有Socket,当某个Socket有数据达到了,就通知用户进程。当调用select,整个进程都会阻塞,其中任意一个socket准备就绪状态,select就会立即返回,用户进程就可以read操作了。
epoll
epoll是对select和poll的升级版,解决了很多问题,是线程安全的,而且可以通知进程是哪个Socket连接有I/O事件,不需要进行全部连接进行遍历,提高了查找效率。
epoll和select/poll最大区别是:
(1)epoll内部使用了mmap共享了用户和内核的部分空间,避免了数据的来回拷贝;
(2)epoll基于事件驱动,epoll_wait只返回发生的事件避免了像select和poll对事件的整个轮询操作(时间复杂度为O(N)),epoll时间复杂度为O(1))。
信息驱动式I/O模型
用户态建立好信号处理程序后,通过系统调用sigaction并立即返回,在系统调用中告知内核态自己需要的数据,然后就去执行其他任务了,内核准备好数据后,会给用户态发送一个SIGIO信号,用户态的信号处理程序收到之后,会立即调用recvfrom,等待数据从内核态复制到用户态,待完成之后recvfrom返回成功指示,用户态进程处理数据。
在数据准备阶段是非阻塞的,用户态进程可以干其他的事情,但在拷贝阶段是阻塞的。
异步I/O模型

与信号驱动模型的主要区别是:信号驱动I/O由内核通知我们何时可以开起一个I/O操作;异步I/O模型由内核通知我们I/O操作何时已经完成。在复制数据到用户空间这个时间段内,用户态进程也是不阻塞的。
用户进程发起read操作之后,这个时候就可以去干其他事情了。而从内核态的角度,当它受到一个异步 read之后,首先它会立刻返回,所以不会对用户进程产生任何阻塞。然后,内核会等待数据准备完成,然后将数据拷贝到用户内存的缓冲区,当这一切都完成之后,内核会给用户进程发送一个信号,告诉它read操作完成了。
五种I/O对比

Java中的IO
操作系统的IO模型是底层基石,Java对于IO的操作其实就是进一步的封装。适配一些系统调用方法,让我们玩地更爽。
BIO--同步阻塞的编程方式
JDK1.4之前常用的编程方式。
实现过程:首先在服务端启动一个ServerSocket来监听网络请求,客户端启动Socket发起网络请求,默认情况下ServerSocket会建立一个线程来处理此请求,如果服务端没有线程可用,客户端则会阻塞等待或遭到拒绝,并发效率比较低。
服务器实现的模式是一个连接一个线程,若有客户端有连接请求服务端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销。当然,也可以通过线程池机制改善。
使用场景
BIO适用于连接数目比较小且固定的架构,对服务器资源要求高,并发局限于应用中。
NIO--同步非阻塞的编程方式
NIO 本身是基于事件驱动思想来完成的,当socket有流可读或可写入时,操作系统会相应地通知应用程序进行处理,应用再将流读取到缓冲区或写入操作系统。一个有效的请求对应一个线程,当连接没有数据时,是没有工作线程来处理的。
服务器实现模式为一个请求一个通道,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有 I/O 请求时才启动一个线程进行处理。
使用场景
NIO 方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程复杂,JDK1.4 开始支持。
NIO中的几种重要角色
有缓冲区Buffer,通道Channel,多路复用器Selector。
Buffer
在NIO库中,所有数据都是用缓冲区(用户空间缓冲区)处理的。在读取数据时,它是直接读到缓冲区中的;在写入数据时,也是写入到缓冲区中。任何时候访问NIO中的数据,都是通过缓冲区进行操作。
缓冲区实际上是一个数组,并提供了对数据的结构化访问以及维护读写位置等信息。
写操作顺序
- clear()
- put() -> 写操作
- flip() ->重置游标
- SocketChannel.write(buffer); ->将缓存数据发送到网络的另一端
- clear()
读操作顺序
- clear()
- SocketChannel.read(buffer); ->从网络中读取数据
- buffer.flip()
- buffer.get() ->读取数据
- buffer.clear()
Channel
nio中对数据的读取和写入要通过Channel,它就像水管一样,是一个通道。通道不同于流的地方就是通道是双向的,可以用于读、写和同时读写操作。
Selector
多路复用器,用于注册通道。客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理
AIO--异步非阻塞编程方式
进行读写操作时,只须直接调用api的read或write方法即可。一个有效请求对应一个线程,客户端的IO请求都是OS先完成了再通知服务器应用去启动线程进行处理。
使用场景
AIO 方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用 OS 参与并发操作,编程比较复杂,JDK1.7 开始支持。
参考链接
- https://zhuanlan.zhihu.com/p/355582245
- https://zhuanlan.zhihu.com/p/73575748