共计 3311 个字符,预计需要花费 9 分钟才能阅读完成。
与君初相识,犹如故人归。
咫尺明月新,朝暮最相思。– 唐. 杜牧《会友》
最近很喜爱这首诗。
浅谈 常见的 I / O 模型
咱们再从新梳理一下网络通信模型,或者说是 I / O 模型更为精确一些,因为网络也能够算作一种 I /O。一般来说一个典型的读写流程是这样的:
咱们这里假如是过程发动了读操作,又晓得过程在创立时,操作系统会调配给它内存和必要的资源,咱们权且能够将操作系统调配给过程的内存称之为用户空间。当过程发动读操作,事实上会调用操作系统的函数,咱们这里要再次强调一下,咱们写的程序是无奈间接接触到文件的,他们是通过调用操作系统向外裸露的接口来实现读写的。读操作大抵上分成两个阶段:
- 期待数据就绪.
- 数据就绪,将数据从内核空间 (简略的能够了解为操作系统所占有的空间) 拷贝进入用户空间(粗略的说,就是过程被调配的空间)。
当数据从内核空间拷贝到用户空间结束,程序就获取到了所要读取的数据,假如在数据没有达到用户空间之前,过程始终处于阻塞状态,咱们就称程序的 I / O 形式是 blocking I / O,咱们并不心愿过程在解决一个文件或者连贯陷入阻塞状态,所以咱们能够在过程里开线程或者子过程去解决一个连贯或者文件,然而连贯多了,耗费又太多。
于是 Non—Blocking I/ O 应需而生,当数据还未达到用户空间时,零碎 I / O 函数会通知过程,数据还没筹备好,你再等等吧。事实上 NIO 也是在一直的询问操作系统: 数据好了没,数据好了没啊。然而这是被过程所负责的,连贯过多或读取的文件过多的时候,会大幅度晋升 CPU 的使用率。个别操作系统提供了更为高效的监测数据筹备好了的接口供过程所应用,也就是 I / O 多路复用。
I/ O 多路复用,粗略的说就是以前是过程被动的询问操作系统数据好没好,当初是操作系统会跟你讲数据好没好,但每一个 Socket 操作还是非阻塞的,所以从概念上来讲 I / O 多路复用和 Non-Blocking I/O 十分类似。这也跟编程语言有关系,如果应用的是比拟贴近底层的高级语言,像 C、C++,大略了解这两个的区别是不艰难的,然而对于 Java 这种跨平台的语言来说,这两个概念权且能够一概而论,JDK 做了大量的封装,外表上是 NIO,Java 外部这么称说,事实上是两种混合在一起的,也就是 NIO+ 多路复用。
I/ O 多路复用在《NIO 学习笔记 (二) 相识篇》,Socket 简介和 I / O 多路复用,曾经有过不少探讨,这里咱们零碎的总结一下,当用户调用操作系统对应的多路复用函数时,Linux 下是 select(),不同的操作系统会有不同的实现,咱们就临时用 select()来称说这个操作系统提供的接口,该接口会启动一个过程会侦测服务端对应的所有客户端的行为,如果发现某个客户端的数据就绪,服务端此时应该做读解决,并依据发来的数据来决定是否发送数据,如果要发送数据,就须要期待 select 过程告诉写事件就绪。
这与事件响应模型有几分相似,然而这个模型依然有一些弊病,服务端程序依据不同的事件来采取对应的行为,咱们用伪码大抵来示意。
while(true){status = select();
if(status == 可读){//}
if (status == 可写){}}
假如可读大量的呈现,那么客户端收到服务端的响应就会有肯定的提早。所以从这里来讲 BIO 也不是一无是处。我在刚学 Java 的时候,总是认为 NIO 比 BIO 先进,当初来看还是须要依据不同的场景来抉择不同的 I /O。不过下面说的问题,一些高级语言库曾经解决了。
人类的社会在倒退,人类的社会在提高,asynchronous I/ O 进去了,Linux 内核 2.6 版本引入了,咱们先来简略的看一下它的流程,用户过程在通过异步 I / O 接口发动读操作之后,首先它会立即返回,某种意义上你能够了解为这个返回是一个假的后果,在操作系统内核实现数据从内核空间到用户数据的迁徙之后,也就是数据曾经能够读了之后,会告诉用户过程。
java 中的 AIO(asynchronous I/O)
Java 当中的 AIO(asynchronous I/O)简略的说就是在原有 NIO 根底上,减少了几种类型的 channel。
咱们再来回顾一下 Java 中 NIO 的几个概念: channel、buffer、selector。
channel 是通道,buffer 是缓冲区,就像是水管和水池的关系。NIO 是如果水池中有水了,来告诉我一下,我当初用通道将水池的水取出来。而 AIO 则更进一步,通道曾经获取了水池里的水了,来告诉一下。
新增的 channel 都由 AsynchronousChannel 接口派生而出。
告诉的形式大抵有两种:
- future 接口
- 回调函数(很像 Ajax,服务端胜利响应调用一个 success 函数,失败调用一个函数)
上面是用 asynchronous channel 操作文件的例子,咱们能够大抵通过例子来感受一下异步, 读取完了怎么告诉呢,事实上也是通过一个线程来做的:
Future 接口示例:
AsynchronousFileChannel channel = AsynchronousFileChannel.open(Paths.get("E:\\ 学习材料 \\ 测试.txt"));
ByteBuffer buffer = ByteBuffer.allocate(21 * 1024);
Future<Integer> future = channel.read(buffer, 0);
while (!future.isDone()){System.out.println("正在执行的是:"+Thread.currentThread().getName());
}
// 阻塞式办法, 直到线程执行结束
Integer result = future.get();
buffer.flip();
System.out.println(result);
System.out.println(new String(buffer.array(),0,buffer.limit()));
回调函数示例:
AsynchronousFileChannel channel = AsynchronousFileChannel.open(Paths.get("E:\\ 学习材料 \\ 测试.txt"));
ByteBuffer buffer = ByteBuffer.allocate(21 * 1024);
channel.read(buffer, 0, null, new CompletionHandler<Integer, Object>() {
@Override
public void completed(Integer result, Object attachment) {buffer.flip();
System.out.println(new String(buffer.array(),0,buffer.limit()));
System.out.println("读取结束");
}
@Override
public void failed(Throwable exc, Object attachment) {}});
while (true){
// 这里是为了验证零碎开拓了一个线程, 所做的打印。System.out.println("正在执行的是:"+Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(5);
}
Java NIO 存在的问题
- 写起来太繁琐了
参看《NIO 学习笔记 (二) 相识篇》,一个聊天室就写了那么多,而且还有坑。
- NIO != 高性能
我晚期的时候是这么感觉的,NIO 性能十分弱小,甩 BIO 十条街。当初来看,还是要看并发状况的,当并发数不高,连接数 < 1000, NIO 并没有显著的劣势
- JDK NIO 存在 bug
Java nio 空轮询 bug 到底是什么,以我目前来看,JDK 并没有解决这个 BUG,这个 bug 是被 Java 畛域的网络编程框架 mina、netty 解决了,用 netty 做网络编程相对来说写起来更简略清晰,如果你想做网络编程,举荐应用 Java 畛域内的网络编程框架,不举荐间接应用原生的 NIO,很可能一不小心就踩坑了,这个我深有体会。
参考资料:
- 5 种网络 IO 模型
- Java NIO 浅析