文档章节

Java NIO怎么理解通道和非阻塞?

kim_o
 kim_o
发布于 2017/07/20 23:31
字数 2202
阅读 7
收藏 0

nio引入了buffer、channel、selector等概念。

通道相当于之前的I/O流。

“通道”太抽象了。Java解释不清的东西只能看它底层是怎么解释的——操作系统的I/O控制,通道控制方式?

I/O设备:CPU——通道——设备控制器——I/O设备

(通道和设备控制器的关系是多对多,设备控制器和I/O设备的关系也是多对多。)

I/O过程,参考http://www.nbrkb.net/lwt/jsjsj/asm/INTR&DMA.htm

1.CPU在执行用户程序时遇到I/O请求,根据用户的I/O请求生成通道程序(也可以是事先编好的)。放到内存中,并把该通道程序首地址放入CAW中。
2.CPU执行“启动I/O”指令,启动通道工作。

3.通道接收“启动I/O”指令信号,从CAW(记录下一条通道指令存放的地址)中取出通道程序首地址,并根据此地址取出通道程序的第一条指令,放入CCW(记录正在执行的通道指令)中;同时向CPU发回答信号,通知“启动I/O”指令完成完毕,CPU可继续执行。

4.与此同时,通道开始执行通道程序,进行物理I/O操作。当执行完一条指令后,如果还有下一条指令则继续执行;否则表示传输完成,同时自行停止,通知CPU转去处理通道结束事件,并从CCW中得到有关通道状态。

如此一来,主处理器只要发出一个I/O操作命令,剩下的工作完全由通道负责。I/O操作结束后,I/O通道会发出一个中断请求,表示相应操作已完成。

通道控制方式是对数据块进行处理的,并非字节。

通道控制方式就是异步I/O,参考http://blog.csdn.net/historyasamirror/article/details/5778378

I/O分两段:1.数据从I/O设备到内核缓冲区。2.数据从内核缓冲区到应用缓冲区

I/O类型:

1.异步I/O不会产生阻塞,程序不会等待I/O完成,继续执行代码,等I/O完成了再执行一个什么回调函数,代码执行效率高。很容易联想到ajax。这个一般用于I/O操作不影响之后的代码执行。

2.阻塞I/O,程序发起I/O操作后,进程阻塞,CPU转而执行其他进程,I/O的两个步骤完成后,向CPU发送中断信号,进程就绪,等待执行。

3.非阻塞I/O并非都不阻塞,其实是第一步不阻塞,第二部阻塞。程序发起I/O操作后,进程一直检查第一步是否完成,CPU一直在循环询问,完成后,进程阻塞直到完成第二步。明白了!这个是“站着茅坑不拉屎”,CPU利用率最低的。逻辑和操作系统的程序直接控制方式一样。

阻塞不阻塞描述的是发生I/O时当前线程的状态。

以上是操作系统的I/O,那么java的nio又是怎样的呢?

个人觉得是模仿了通道控制方式。

先看看nio的示例代码:

服务端TestReadServer.java

[java] view plain copy

  1. import java.io.IOException;  
  2. import java.net.InetSocketAddress;  
  3. import java.net.ServerSocket;  
  4. import java.nio.ByteBuffer;  
  5. import java.nio.channels.SelectionKey;  
  6. import java.nio.channels.Selector;  
  7. import java.nio.channels.ServerSocketChannel;  
  8. import java.nio.channels.SocketChannel;  
  9. import java.util.Iterator;  
  10. import java.util.Set;  
  11.   
  12. public class TestReadServer {  
  13.       
  14.       
  15.     private  int flag = 0;  
  16.       
  17.     private  int BLOCK = 1024*1024*10;  
  18.       
  19.     private  ByteBuffer sendbuffer = ByteBuffer.allocate(BLOCK);  
  20.       
  21.     private  ByteBuffer receivebuffer = ByteBuffer.allocate(BLOCK);  
  22.     private  Selector selector;  
  23.   
  24.     public TestReadServer(int port) throws IOException {  
  25.         // 打开服务器套接字通道  
  26.         ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();  
  27.         // 服务器配置为非阻塞  
  28.         serverSocketChannel.configureBlocking(false);  
  29.         // 检索与此通道关联的服务器套接字  
  30.         ServerSocket serverSocket = serverSocketChannel.socket();  
  31.         // 进行服务的绑定  
  32.         serverSocket.bind(new InetSocketAddress(port));  
  33.         // 通过open()方法找到Selector  
  34.         selector = Selector.open();  
  35.         // 注册到selector,等待连接  
  36.         serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);  
  37.         System.out.println("Server Start----"+port+":");  
  38.     }  
  39.   
  40.   
  41.     // 监听  
  42.     private void listen() throws IOException {  
  43.         while (true) {  
  44.             // 选择一组键,并且相应的通道已经打开  
  45.             selector.select();  
  46.             // 返回此选择器的已选择键集。  
  47.             Set selectionKeys = selector.selectedKeys();  
  48.             Iterator iterator = selectionKeys.iterator();  
  49.             while (iterator.hasNext()) {          
  50.                 SelectionKey selectionKey = iterator.next();  
  51.                 iterator.remove();  
  52.                 handleKey(selectionKey);  
  53.             }  
  54.         }  
  55.     }  
  56.   
  57.     // 处理请求  
  58.     private void handleKey(SelectionKey selectionKey) throws IOException {  
  59.         // 接受请求  
  60.         ServerSocketChannel server = null;  
  61.         SocketChannel client = null;  
  62.         String receiveText;  
  63.         String sendText;  
  64.         int count=0;  
  65.         // 测试此键的通道是否已准备好接受新的套接字连接。  
  66.         if (selectionKey.isAcceptable()) {  
  67.             // 返回为之创建此键的通道。  
  68.             server = (ServerSocketChannel) selectionKey.channel();  
  69.             // 接受到此通道套接字的连接。  
  70.             // 此方法返回的套接字通道(如果有)将处于阻塞模式。  
  71.             client = server.accept();  
  72.             // 配置为非阻塞  
  73.             client.configureBlocking(false);  
  74.             // 注册到selector,等待连接  
  75.             client.register(selector, SelectionKey.OP_READ);  
  76.         } else if (selectionKey.isReadable()) {  
  77.             // 返回为之创建此键的通道。  
  78.             client = (SocketChannel) selectionKey.channel();  
  79.             //将缓冲区清空以备下次读取  
  80.             receivebuffer.clear();  
  81.             //读取服务器发送来的数据到缓冲区中  
  82.             System.out.println(System.currentTimeMillis());  
  83.             count = client.read(receivebuffer);   
  84.             System.out.println(System.currentTimeMillis() + "~"+count);  
  85.         }   
  86.     }  
  87.   
  88.       
  89.     public static void main(String[] args) throws IOException {  
  90.         // TODO Auto-generated method stub  
  91.         int port = 1234;  
  92.         TestReadServer server = new TestReadServer(port);  
  93.         server.listen();  
  94.     }  
  95. }  

客户端TestReadClient.java

[java] view plain copy

  1. import java.io.BufferedInputStream;  
  2. import java.io.File;  
  3. import java.io.FileInputStream;  
  4. import java.io.IOException;  
  5. import java.net.InetSocketAddress;  
  6. import java.nio.ByteBuffer;  
  7. import java.nio.channels.SelectionKey;  
  8. import java.nio.channels.Selector;  
  9. import java.nio.channels.SocketChannel;  
  10. import java.util.Iterator;  
  11. import java.util.Set;  
  12.   
  13. public class TestReadClient {  
  14.   
  15.       
  16.     private static int flag = 0;  
  17.       
  18.     private static int BLOCK = 1024*1024*10;  
  19.       
  20.     private static ByteBuffer sendbuffer = ByteBuffer.allocate(BLOCK);  
  21.       
  22.     private static ByteBuffer receivebuffer = ByteBuffer.allocate(BLOCK);  
  23.       
  24.     private final static InetSocketAddress SERVER_ADDRESS = new InetSocketAddress(  
  25.             "localhost", 1234);  
  26.   
  27.     public static void main(String[] args) throws IOException {  
  28.         // TODO Auto-generated method stub  
  29.         // 打开socket通道  
  30.         SocketChannel socketChannel = SocketChannel.open();  
  31.         // 设置为非阻塞方式  
  32.         socketChannel.configureBlocking(false);  
  33.         // 打开选择器  
  34.         Selector selector = Selector.open();  
  35.         // 注册连接服务端socket动作  
  36.         socketChannel.register(selector, SelectionKey.OP_CONNECT);  
  37.         // 连接  
  38.         socketChannel.connect(SERVER_ADDRESS);  
  39.         // 分配缓冲区大小内存  
  40.           
  41.         Set selectionKeys;  
  42.         Iterator iterator;  
  43.         SelectionKey selectionKey;  
  44.         SocketChannel client;  
  45.         String receiveText;  
  46.         String sendText;  
  47.         int count=0;  
  48.   
  49.         while (true) {  
  50.             //选择一组键,其相应的通道已为 I/O 操作准备就绪。  
  51.             //此方法执行处于阻塞模式的选择操作。  
  52.             selector.select();  
  53.             //返回此选择器的已选择键集。  
  54.             selectionKeys = selector.selectedKeys();  
  55.             //System.out.println(selectionKeys.size());  
  56.             iterator = selectionKeys.iterator();  
  57.             while (iterator.hasNext()) {  
  58.                 selectionKey = iterator.next();  
  59.                 if (selectionKey.isConnectable()) {  
  60.                     System.out.println("client connect");  
  61.                     client = (SocketChannel) selectionKey.channel();  
  62.                     // 判断此通道上是否正在进行连接操作。  
  63.                     // 完成套接字通道的连接过程。  
  64.                     if (client.isConnectionPending()) {  
  65.                         client.finishConnect();  
  66.                         System.out.println("完成连接!");  
  67.                         sendbuffer.clear();  
  68.                         BufferedInputStream br = new BufferedInputStream(new FileInputStream(new File("D:\\BigData.zip")));  
  69.                         byte[] b = new byte[BLOCK];  
  70.                         br.read(b);       
  71.                         sendbuffer.put(b);  
  72.                         sendbuffer.flip();  
  73.                         System.out.println(System.currentTimeMillis());  
  74.                         client.write(sendbuffer);  
  75.                         System.out.println(System.currentTimeMillis());  
  76.                     }  
  77.                     client.register(selector, SelectionKey.OP_READ);  
  78.                 } else if (selectionKey.isReadable()) {  
  79.                     client = (SocketChannel) selectionKey.channel();  
  80.                     //将缓冲区清空以备下次读取  
  81.                     receivebuffer.clear();  
  82.                     //读取服务器发送来的数据到缓冲区中  
  83.                     count=client.read(receivebuffer);  
  84.                     if(count>0){  
  85.                         receiveText = new String( receivebuffer.array(),0,count);  
  86.                         System.out.println("客户端接受服务器端数据--:"+receiveText);  
  87.                         client.register(selector, SelectionKey.OP_WRITE);  
  88.                     }  
  89.                 }   
  90.             }  
  91.             selectionKeys.clear();  
  92.         }  
  93.     }  
  94. }  

例子是TestReadClient向TestReadServer发送一个本地文件。TestReadServer收到后每次打印读取到的字节数。

如何体现异步I/O?

看看TestReadClient中的:

[java] view plain copy

  1. if (selectionKey.isConnectable()) {  
  2.     System.out.println("client connect");  
  3.     client = (SocketChannel) selectionKey.channel();  
  4.     // 判断此通道上是否正在进行连接操作。  
  5.     // 完成套接字通道的连接过程。  
  6.     if (client.isConnectionPending()) {  
  7.         client.finishConnect();  

如果没有client.finishConnect();这句等待完成socket连接,可能会报异常:java.nio.channels.NotYetConnectedException

异步的才不会管你有没有连接成功,都会执行下面的代码。这里需要人为的干预。

如果要证明是java的nio单独使用非阻塞I/O,真没办法!!!阻塞非阻塞要查看进程。。。

不过还有种说法,叫异步非阻塞。上面那段,是用异步方式创建连接,进程当然没有被阻塞。使用了finishConnect()这是人为将程序中止,等待连接创建完成(是模仿阻塞将当前进程阻塞掉,还是模仿非阻塞不断轮询访问,不重要了反正是程序卡住没往下执行)。

所以,创建连接的过程用异步非阻塞I/O可以解释的通。那read/write的过程呢?

根据上面例子的打印结果,可以知道这个过程是同步的,没执行完是不会执行下面的代码的。至于底下是使用阻塞I/O还是非阻塞I/O,对于应用级程序来说不重要了。

阻塞还是非阻塞,对于正常的开发(创立连接,从连接中读写数据)并没有多少的提升,操作过程都类似。

那NIO凭什么成为高性能架构的基础,比起IO,性能优越在哪里,接着猜。。。

Java nio有意模仿操作系统的通道控制方式,那他的底层是不是就是直接使用操作系统的通道?

通道中的数据是以块为单位的,之前的流是以字节为单位的,同样的数据流操作外设的次数较多。代码中channel都是针对ByteBuffer对象进行read/write的,而ByteBuffer又是ByteBuffer.allocate(BLOCK);这样创建的,是一个连续的块空间。

那ByteBuffer是不是也是模拟操作系统的缓存?

缓存在io也有,如BufferedInputStream。CPU和外设的速度差很多,缓存为了提高CPU使用率,等外设将数据读入缓存后,CPU再统一操作,不用外设读一次,CPU操作一次,CPU的效率会被拉下来。。。

© 著作权归作者所有

共有 人打赏支持
kim_o
粉丝 2
博文 79
码字总数 28082
作品 0
深圳
程序员
Java NIO原理 图文分析及代码实现

Java NIO原理图文分析及代码实现 前言: 最近在分析hadoop的RPC(Remote Procedure Call Protocol ,远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术...

囚兔
2015/04/29
0
0
Java NIO原理图文分析及代码实现

前言: 最近在分析hadoop的RPC(Remote Procedure Call Protocol ,远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。可以参考:http://baik...

SunnyWu
2014/11/05
0
1
java NIO原理及通信模型

Java NIO是在jdk1.4开始使用的,它既可以说成“新IO”,也可以说成非阻塞式I/O。下面是java NIO的工作原理: 由一个专门的线程来处理所有的IO事件,并负责分发。 事件驱动机制:事件到的时候...

柳哥
2015/02/15
0
4
Java NIO原理图文分析及代码实现

前言: 最近在分析hadoop的RPC(Remote Procedure Call Protocol ,远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。可以参考:http://baik...

phacks
2015/08/19
0
0
Java NIO原理图文分析及代码实现

Java IO 在Client/Server模型中,Server往往需要同时处理大量来自Client的访问请求,因此Server端需采用支持高并发访问的架构。一种简单而又直接的解决方案是“one-thread-per-connection”。...

只想一个人静一静
2014/02/22
0
1

没有更多内容

加载失败,请刷新页面

加载更多

《看图轻松理解数据结构与算法系列》导引贴

最近学习数据结构,遇到一个很喜欢的博主,他的文章图文并茂,理解起来很容易。特此开贴记录,方便反复阅读。 博主主页 https://juejin.im/user/57c3970f79bc440063e58518/posts?sort=popul...

科陆李明
30分钟前
0
0
20.27 分发系统介绍~ 20.30 expect脚本传递参数

分发系统介绍分发系统-expect讲解(也就是一个分发的脚本)场景:业务越来越大,网站app,后端,编程语言是php,所以就需要配置lamp或者lnmp,最好还需要吧代码上传到服务器上;但是因...

洗香香
44分钟前
2
0
设计一个百万级的消息推送系统

前言 首先迟到的祝大家中秋快乐。 最近一周多没有更新了。其实我一直想憋一个大招,分享一些大家感兴趣的干货。 鉴于最近我个人的工作内容,于是利用这三天小长假憋了一个出来(其实是玩了两...

crossoverJie
51分钟前
2
0
软件架构:5种你应该知道的模式

Singleton(单例模式)、仓储模式(repository)、工厂模式(factory)、建造者模式(builder)、装饰模式(decorator)……大概每个上课听讲的程序员都不会陌生——软件的设计模式为我们提供...

好雨云帮
今天
3
0
OSChina 周二乱弹 —— 这只是一笔金钱交易

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @小小编辑:推荐歌曲《暮春秋色》- 窦唯 / 译乐队 《暮春秋色》- 窦唯 / 译乐队 手机党少年们想听歌,请使劲儿戳(这里) @我没有抓狂:跨服聊...

小小编辑
今天
1K
18

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部