在网络编程中,用户启动的应用程序都是以进程的形式存在,而无论对于网络数据还是磁盘上的数据,都以二进制的流方式传递,必须由内核把数据复制到进程所在的内存空间才能访问。
如上图所示,一个客户端的HTTP请求发出后,先通过内核找到硬盘或者网络上的资源,加载到内核空间的缓冲区中,在通过内核复制回显到网页上,显示出用户所需要的HTML网页。在此期间进程实际上是阻塞的,在客户端没有得到所需要的数据之前进程会一直阻塞,直到任务完成;
IO模型
五种常见的IO模型 |
---|
阻塞式 IO |
非阻塞式 IO |
多路复用 IO 模型 |
信号驱动模型 |
异步 IO |
1.阻塞式IO (进程阻塞)
阻塞式IO 是之前一直使用的IO,一次IO操作分为两个步骤:
- 数据从磁盘到内核空间的缓冲区
- 内核把内核空间的数据复制到进程空间,然后删除内核空间的数据
这个过程一直是阻塞的,只有等到一个步骤做完下一个步骤才能执行。
2.非阻塞式IO(轮询 / IO通知机制)
非阻塞式IO 机制时用户线程必须通过一个while(true)循环去不断的轮询数据是否准备好,如果返回的时error表示没有准备好则继续执行轮询操作,当数据一旦准备好,并且再次收到了用户线程的请求,那么它就会马上拷贝内核中的数据到用户的进程空间然后返回。
while(true){
data = socket.read();
if(data!= error){
处理数据
break;
}
}
但是需要明白的是:由于while循环会一直轮询数据是否准备好,所以非阻塞IO不会交出CPU,导致CPU的使用率过高
3.IO多路复用
目前使用最多的就是IO多路复用技术,比如NIO,Redis等
在IO多路复用中,存在一个选择器selector,通过调用一个线程来选择出当前socket端有通信的时候。因为只需要一个线程就可以完成上述操作,所以节省了系统的开销。
如上图所示,当多个线程同时进行写操作,选择器会通过一个线程进行轮询,等到有一个socket端写满才会使用资源进行连接;
4.信号驱动
当用户线程发起一个IO请求操作,会在对应的socket注册一个信号函数,然后用户继续执行,当内核数据准备就绪之后会发送一个信号给用户线程,用户线程收到信号以后便调用IO读写操作来进行实际的IO操作
一般用于UDO中,TCP的套接字接口几乎没用。这是因为该信号过于频繁,且信号产生只是为了通知用户线程
5.异步IO模型
当用户发起read操作后,就可以去做别的事情。反之内核在接收到异步read信号之后也立刻返回,并不会阻塞用户线程,然后内核会等待数据准备完成,将数据从内核拷贝到线程空间。当这些都完成之后,内核会发送一个信号到用户,告诉他read已经完成。也就是说用户不用关心实际的IO操作是如何进行的,只需要发起一个请求,当接收到来自内核的成功信号后表示操作已经完成,不需要再现线程中调用IO读写操作
再JDK1.7之后,系统都支持异步IO ,即
Asynchronous IO 简称 AIO