首先提一下,在Java中,主要有三种IO模型,分别是阻塞IO(BIO)、非阻塞IO(NIO)和 异步IO(AIO)。可以理解为是Java语言对操作系统的各种IO模型的封装。程序员在使用这些API的时候,不需要关心操作系统层面的知识,也不需要根据不同操作系统编写不同的代码,只需要使用Java的API就可以了。
所以接下来讲的是操作系统层面的IO,不是Java层面的IO,不要混淆哦。 在Linux中一共有五种IO模型:
应用进程通过系统调用 recvfrom 接收数据,但由于内核还未准备好数据报,应用进程就会阻塞住,直到内核准备好数据报,从内核拷贝到用户空间,recvfrom 返回成功,应用进程才能结束阻塞状态。
阻塞IO模型的优点是编程简单,但缺点是需要配合大量线程/线程池使用。应用进程每接收一个连接,就需要为此连接创建一个线程来处理该连接上的读写任务。
调用进程在等待数据的过程中不会被阻塞,而是会不断地轮询查看数据有没有准备好。如果某一次轮询发现数据已经准备好了,将数据从内核空间拷贝到用户空间。
应用进程通过 recvfrom 调用不停的去和内核交互,直到内核准备好数据。如果没有准备好,内核会返回error,应用进程在得到error后,过一段时间再发送recvfrom请求。在两次发送请求的时间段,进程可以先做别的事情,等待数据的过程是非阻塞的,但数据拷贝时仍是阻塞的。
为了解决非阻塞IO不断轮询导致CPU占用升高的问题,出现了IO复用模型。 Linux中提供了select、poll和epoll三种方式来实现IO复用。
管道统一和内核进行交互。下图以select函数为例,多个进程的IO可以注册到同一个select上,当用户进程调用该select,select会监听所有注册好的IO,如果所有被监听的IO需要的数据都没有准备好时,select调用进程会阻塞。当任意一个IO所需的数据准备好之后,select调用就会返回,然后进程再通过recvfrom来把对应的数据拷贝到用户空间中。
应用程序创建一个信号驱动程序SIGIO,向内核注册一个信号处理函数,然后用户进程返回继续运行,不会被阻塞。当内核数据准备就绪时会发送一个信号给应用进程,之后信号驱动程序就会执行,用户进程便在信号处理函数中开始把数据拷贝到用户空间中。
相比于同步IO,异步IO不是顺序执行的。应用进程在执行aio_read系统调用之后,给内核传递描述符、缓冲区指针、缓冲区大小等,告诉内核当整个操作完成时,如何通知进程,当内核收到aio_read后,会立刻返回,然后应用进程就去做其他事情了。
然后内核开始等待数据准备,当数据准备好之后,内核直接复制数据给用户进程,内核完成相关操作后,然后发出信号告诉应用进程本次IO已经完成。
代码小兵86504-19 19:55
代码小兵49806-21 15:40
代码小兵69607-21 11:32
代码小兵87207-21 12:51