Java NIO 深入研究

开发 后端
本文主要针对Java NIO一些概念进行深入研究。

No-Block 和Block IO 的区别:

一个典型的网络通讯步骤为: open (新建socket Chanel )--> connect( 尝试建立连接) --> accept( 连接被接受) --> read( 读取请求)send (输出结果)--> close( 连接关闭) 。

对于一个No-Block 的网络IO ,上面的每一步都是会马上返回的,当然返回的结果可能为null ,可能不为null ,这个要看下上文(context )决定。一般情况下,我们都是需要不为null 的结果,这个就需要我们在适当的时机,执行适当的步骤,这样就会得到我们想要的结果。何为适当的时机?这个下面会讲。

对于一个block 的网络IO ,上面的每一步执行的时候,如果没到适当的时机,当前线程就会被block 住,直到适当的时机,返回给你确定的结果。

当然对与No-Block 或者Block IO ,上面的每一步都有可能会抛出IOException 异常的。

NIO 编程接触的几个关键概念:

Buffer :是一块连续的内存块,是 NIO 数据读或写的中转地。Buffer 这篇blog 暂时略过不讲。

Chanel :数据的源头或者数据的目的地,用于向 buffer 提供数据或者读取 buffer 数据 ,异步 I/O 支持。

注意chanel 有2 类,一种叫SocketChanel, 一种叫ServerSocketChanel ,看名字我们就知道,一类是普通的socket chanel ,client 端和服务器端都用的,一类是专门用在server 端的。当然这个界限也不是绝对的,互为client 和server 的情况也是存在的。

Selector : chanel 事件的侦听者, 它能检测一个或多个通道 (channel) 上的事件,并将事件分发出去。使用一个 select 线程就能监听多个通道上的事件,并基于事件驱动触发相应的响应。

SelectionKey : chanel 上发生的事件, 包含了事件的状态信息和时间以及对应的 chanel 。

Chanel 的状态:

可连( Connectable ):当一个 Chanel 完成 socket 连接操作已完成或者已失败放弃时。

能连( Acceptable ):当一个 Chanel 已经准备好接受一个新的 socket 连接时。

可读( Readable ):当一个 Chanel 能被读时。

可写( Writable ):当一个 Chanel 能被写时。

 

结合对照上面的网络通讯步骤我们可以有以下推导出的结论:

当一个 Server Chanel 是 Connectable 时, client 端尝试 connect 才会成功。

当一个 Server Chanel 是 Acceptable 时, client 的连接请求被真正受理,一个新的 chanel 会被生成,并且记录了 localAdrress 和 remoteAddress. 为进一步读写做准备。

当一个 Chanel 是 Readable 时,我们从这个 Chanel 中读取数据才会成功。

当一个 Chanel 是 Writable 时,我们往这个 Chanel 中写数据才会成功。

记住一点,对于一个 No-Block 的 Chanel 来说,上面 4 个操作都会马上返回或者抛出 IOException ,但是是不是成功就难说了,前面就说了,我们在一个 Chanel 做操作的时候,我们要密切关注 Chanel 的当前状态。只有在知道 Chanel 的当前状态时,我们才能在这个 Chanel 上做最适当的操作。

聪明的你可能马上就会想到,要是你操作的 Chanel 的状态的转换信息能被你抓取,这些问题就迎刃而解了。对啦, NIO 就是这样设计的。一个 Chanel 可以注册一个 Selector (就像一个事件侦听器),而且你还要告知你想要要侦听的状态。用一段代码来说明下:

  1. selector = SelectorProvider.provider().openSelector();  
  2. serverChannel1 = ServerSocketChannel.open();  
  3. serverChannel1.configureBlocking(false);  
  4. InetSocketAddress isa = new InetSocketAddress("localhost"9999);  
  5. serverChannel1.socket().bind(isa);  
  6. serverChannel1.register(selector, SelectionKey.OP_ACCEPT); 

这段代码的意思就是我们打开了一个 ServerChanel ,侦听本机的 9999 端口,并且新建了一个 Selector, 然后这个 ServerChanel 注册了这个 Selector ,并且指定了它感兴趣的状态类型是 OP_ACCEPT. 这样有什么效果呢?

注意红色那句,这句意思是selector要求serverChannel1状态为acceptable的时候把这个消息告诉selector。

效果就是:

当这个 ServerChanel 状态为 Acceptable 时, Selector 就会收到一个消息,这个消息当然就是一个 SelectionKey 对象。调用 Selector 的 selectedKeys ()方法,我们就能得到所有 Chanel 发送过来的消息。

因为 SelectionKey 包含 事件的状态,时间以及对应的 Chanel ,很自然的,我们遍历这个 Set<SelectionKey>, 根据 SelectionKey 的状态,就能在相应的 Chanel 做正确的操作。比如,能读的时候我们就读,能写的时候我们就写。

***讲讲 Server 端和 Client 编程的一般步骤:

对于 Client 来一般是这样的:

  1. InetSocketAddress isa = new InetSocketAddress(host, port);   
  2. SocketChannel sc = null;   
  3. sc = SocketChannel.open();   
  4. sc.connect(isa);   
  5. sc.write(data);   
  6. …   
  7. Sc.read(buff);  

构造一个 InetSocketAddress 对象 --> open --> connect --> write --> read

注意这里用的不是 No-Block 的方式,因为 client 如果没有得到 server 端的正确回应的话就采取下一步操作无疑是没有意义的。

Server 端:

 

  1. selector = SelectorProvider.provider ().openSelector();   
  2. serverChannel = ServerSocketChannel.open ();   
  3. serverChannel .configureBlocking( false );   
  4. InetSocketAddress isa = new InetSocketAddress( "localhost" , 9999 );   
  5. serverChannel .socket().bind(isa);   
  6. serverChannel .register( selector , SelectionKey. OP_ACCEPT );  

构造一个 Selector --> 打开一个 serverSocketChanel --> 设定 serverSocketChanel 为 no-block-->bind serverSocketChanel 到一个 host 和 port --> register Selector 并告知感兴趣的状态类型转换。

在 SelectionKey Set 上遍历操作:

 

 

  1. while (true) {   
  2. selector.select();   
  3. Iterator selectedKeys = this.selector.selectedKeys().iterator();   
  4. while (selectedKeys.hasNext()) {   
  5. SelectionKey key = (SelectionKey) selectedKeys.next();   
  6. selectedKeys.remove();   
  7. if (!key.isValid()) {   
  8. continue;   
  9. }   
  10. if (key.isAcceptable()) {   
  11. accept(key);   
  12. else if (key.isReadable()) {   
  13. read(key);   
  14. else if (key.isWritable()) {   
  15. write(key);   
  16. }   
  17. }   
  18. }  

在这个循环里面我们会根据 SelectionKey 的状态,采取不同的操作的。当连接被 accepted 时, 一个新的 chanel 会被生成,并且记录了 localAdrress 和 remoteAddress. 为进一步读写做准备。

accept 函数如下:

  1. public void accept(SelectionKey key) throws IOException {   
  2. ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();  
  3. SocketChanel socketChannel1 = serverSocketChannel.accept();   
  4. socketChannel1.configureBlocking(false);   
  5. socketChannel1.register(selector, SelectionKey.OP_READ);   
  6. }  

这里新的 Chanel 被构建,***同样会注册到 selector , 同时要求当这个 Chanel 为 Readable 时,一个 SelectionKey 被放入到 Selector 中。这样上面循环会用 read(key) 来处理这个 SelectionKey。

原文链接:http://tangay.iteye.com/blog/848485

 【编辑推荐】

  1. Java数据缓存实现的核心机制
  2. Java NIO TCP编程
  3. Java NIO性能测试
  4. Java NIO 经典实例代码
  5. Java NIO聊天窗口实例
责任编辑:林师授 来源: tangay的博客
相关推荐

2017-06-06 11:29:23

Java异常研究与分析

2016-01-12 18:04:55

Java异常研究

2011-06-01 10:58:54

Android Service

2022-05-11 09:03:05

CSS容器文本换行

2023-11-03 08:25:28

数据结构Java

2010-11-23 16:35:59

MySQL删除多表数据

2018-12-24 15:00:58

混合云多云云采用

2010-11-18 17:24:27

Oracle旋转ins

2009-10-20 10:17:50

综合布线系统验收

2010-06-21 13:07:14

2022-04-19 08:28:34

main函数

2010-05-31 17:45:50

MySQL行锁

2010-11-26 11:57:35

MySQL结果字符串

2012-01-18 11:24:18

Java

2016-03-02 15:09:57

Java虚拟机体系结构

2013-01-07 13:38:56

Android开发布局长度单位

2009-12-08 18:45:17

PHP检查函数可用

2021-05-25 09:00:00

Kubernetes容器集群

2009-11-11 13:49:02

路由器技术

2010-05-19 14:45:46

点赞
收藏

51CTO技术栈公众号