`
Mojarra
  • 浏览: 128579 次
  • 性别: Icon_minigender_1
社区版块
存档分类
最新评论

Java NIO小结 (二)

阅读更多

 

除了第一篇小结中讲的Java New/IO的几个基本新特性外,New I/O中一个最突出的特性就是Non-blocking I/O了,这个特性是针对原java.net包中socket编程的一个极大的补充和拓展。究竟non-blocking如何使用?有何特点?与socket的blocking IO相比,有哪些优势?

 

JDK1.4 以前,在调用 ServerSocket.accept() 方法等待一个连接入的套接字时,该方法一直是等待的,直到有客户端的套接字接入才返回一个套接字,或者抛出 IOException 。套接字读取输入流时到缓存中的时候,必须等到套接字输入流的输入数据是可用的,或者出现异常,或者读取输入流的末尾。试想,假如客户端套接字发送报文给服务端套接字的过程中,因网络的问题,数据没有发送过来或者有延迟,那么服务器端套接字在读取数据时,会一直处于等待状态。常见的框架代码如下,

 

ServerSocket server = new ServerSocket(1299);
while (true) {
	 Socket socket = server.accept();
	 (new RequestHandlerThread(socket)).start();
}
 

 

在面向线程的阻塞 IO 编程模式下, ServerSocket 接受到一个套接字后,会启动一个线程负责套接字之间的通讯,但是,如果出现 IO 阻塞,这个线程也会处于“阻塞”状态,如果 IO 阻塞没有被及时排除,会引起僵尸线程,甚至整个系统不可用。

 

而在多路复用 Non-blocking 模式下, socket 编程采用事件模式,主要用 Selector 来读取感兴趣的事件,用 Channel 注册感兴趣的事件和负责发送、接收数据, SelectionKey 是对事件的封装。这种编程模式的框架代码如下,

 

// 打开selctor和服务器端SocketChannel,并且配置为non-blocking模式,然后绑定到9000端口。 
Selector sel = Selector.open();
 ServerSocketChannel ssc = ServerSocketChannel.open();
 ssc.configureBlocking(false);
 ssc.socket().bind(new InetSocketAddress(9000));

// 注册OP_ACCEPT事件,让selector在下一次select操作中选择OP_ACCEPT事件
 ssc.register(sel, SelectionKey.OP_ACCEPT);

while (selector.select()>0){
                Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
		while (keys.hasNext()) {
			SelectionKey key = keys.next();
			keys.remove();
			if (key.isValid()) {
				if (key.isAcceptable()) {
					 // ....   接受SocketChannel,并注册OP_READ事件
				} else if (key.isReadable()) {
					// ... 从SocketChannel读取数据
				}
				else if (key.isWritable()){
					 // ... 向SocketChannel 写入数据
				}
                              // 把当前已处理的key放入到selector的cancelling selectionkey集合当中,在下一次select操作中,该key被移去。 
                              key.cancel();
			}
		}
}
 

 

Selector 是包含多个 Selectable Channel 的转接器,这些通道可以设置成非阻塞模式去执行多路 I/O 操作。这些通道必须先被创建,然后设置成非阻塞模式,并注册到 Selector 之中。注册通道时指定一些特定的 I/O 操作,这些 I/O 操作将会为 selector 所检测,并返回一个代表这个注册的 SelectionKey

 

一旦通道被注册到一个 Selector, 选择操作才可以执行去发现有没有通道已经处于可以执行这些先前被生命注册 I/O 操作的状态。如果一个通道已经就绪,返回一个 SelectionKey 并把这个 key 添加到 Selector Selected-Key 集。在下一次选择操作过程中,会把这个带有相关通道 key 检索出来去执行相关的 I/O 操作。

 

一个 SelectionKey 标明一个通道可以准备执行什么操作,仅仅是一个提示,而不是保证。因此,这个操作可以放到一个线程中去执行,而不会引起线程阻塞。这对于编写多路复用 I/O 的代码是非常重要的,假使这个提示在后来被证明是不正确的,也可以在代码中忽略掉它。

 

Selector 有三个主要的方法,

Select() 以阻塞的方式从当前的 selector 中选取那些已经准备好 I/O 操作 Channel key ,至少有一个 channel 被选取后才返回,或者 weekup 被调用,或者当前线程被终止执行。

 

selectNow() ,与 select() 方法类似,只不过是非阻塞的,如果当前 selector 中没有已准备 I/O 操作的 key ,该方法立即返回 0 。之前如有 weekup() 被调用, SelectNow() 会清除掉前者的执行效果。

 

weekup() ,让第一个还没有返回的选择操作立即返回,如果另外一个线程被 select() 或者 select(long) 方法调用阻止,调用 weekup() 方法,会让 select() 或者 select(long) 调用立即返回。如果当前进程中没有选择操作,会导致下轮 select(), select(long) 方法调用立即返回,除非同时有 selectNow() 方法被调用。在两此成功的选择操作过程中间调用多次 weekup() 方法与只调用一次的效果是相同的。

 

Non-blocking 模式下,用 Channel 读取输入缓冲中的数据时,读取到的字节数依赖于当前 Channel 的状态。比如下面的代码。

ByteBuffer buff = ByteBuffer.allocate(128);

socketChannel.read(buff);
 

 

如果 Channel 缓冲区没有数据,那么读取了 0 字节,如果 Channel 的缓冲区里有 50 个“立即可读取”字节,则读取 50 个字节,假如已经到了流的末尾,则返回 -1 。因为 Channel 是直接从缓冲区读取数据,而不是从流读取,所以, Channel 的这种工作方式效率比从流直接读取效率高。

Channel 写数据的时候,也是先写入 Channel 的写入缓冲区,而不是直接写入 Channel 的输出流。具体写入的字节数由 Channel 的输出缓冲区的状态决定。考察下面的代码。

 

byte out[] = new byte[256];
for (int i=0; i<256; i++) 
     out[i]=i;

socketChannel.write(ByteBuffer.wrap(out));
 

 

假如输出缓冲区还剩下 50 个空余字节,那么本次写入操作只能写 50 个字节。如果输出缓冲区剩余 500 个空余字节,则能写入 256 个字节。

 

至于 Channel 的从输入流读数据到输入缓冲,什么时候把输出缓冲写入到输出流,由 Channle 的实现细节决定。以后会对 Channel 中的缓冲于流的机制做深入的研究。使用 Channel 时,一定要记住,虽然看起来和 InputStream OutputStream 使用方式看起来一样,但其内部机制是有天壤之别的。

 

末尾,以表格的形式对 socket 编程中用到的主要方法的阻塞特性作一个小结。

ServerSocketChannel.accept()

非阻塞模式 / 阻塞模式

ServerSocket.accept()

阻塞模式

Socket input stream read

阻塞模式

Socket output stream write

阻塞模式

Channel input Buffer read

非阻塞模式 / 阻塞模式

Channel output buffer write

非阻塞模式 / 阻塞模式

Selector.select()

阻塞模式

Selector.select(long l)

阻塞模式

Selector.selectNow()

非阻塞模式 / 阻塞模式

 

分享到:
评论

相关推荐

    Java基础知识点总结.docx

    Java数组与集合小结 305 递归 309 对象的序列化 310 Java两种线程类:Thread和Runnable 315 Java锁小结 321 java.util.concurrent.locks包下常用的类 326 NIO(New IO) 327 volatile详解 337 Java 8新特性 347 Java...

    Java SE实践教程 pdf格式电子书 下载(四) 更新

    Java SE实践教程 pdf格式电子书 下载(二) 更新 http://download.csdn.net/source/2824040 Java SE实践教程 pdf格式电子书 下载(三) 更新 http://download.csdn.net/source/2824042 Java SE实践教程 pdf格式...

    疯狂JAVA讲义

    1.9 本章小结 22 本章练习 22 第2章 理解面向对象 23 2.1 面向对象 24 2.1.1 结构化程序设计简介 24 2.1.2 程序的三种基本结构 25 2.1.3 面向对象程序设计简介 27 2.1.4 面向对象的基本特征 28 2.2 UML...

    Java SE实践教程 源代码 下载

    1.3 小结 35 第2章 对象无处不在——面向对象的基本概念 37 2.1 讲解 38 2.1.1 什么是面向对象 38 2.1.2 面向对象的基本概念 38 2.1.3 Java对面向对象的支持 41 2.2 练习 42 2.2.1 JavaBeans技术开发可重用...

    Java SE实践教程 pdf格式电子书 下载(一) 更新

    Java SE实践教程 pdf格式电子书 下载(二) 更新 http://download.csdn.net/source/2824040 Java SE实践教程 pdf格式电子书 下载(三) 更新 http://download.csdn.net/source/2824042 Java SE实践教程 pdf格式...

    JavaNetIO

    Java NetIO 一步步从BIO - &gt; BIO+多线程- &gt;单线程+NIO(非阻塞IO) - &gt; NIO(IO多路复用+单线程) - &gt; NIO(IO多路复用+多线程) Java中的NIO其实是NewIO(另一种解释是NO-blocking IO,确实,在Java...小结:ServerSocketCh

    看透springMvc源代码分析与实践

    前言 第一篇 网站基础知识 第1章 网站架构及其演变过程2 1.1 软件的三大类型2 1.2 基础的结构并不简单3 1.3 架构演变的起点5 1.4 海量数据的解决方案5 1.4.1 缓存和页面静态化5 ...22.3 小结309

    Java CP/IP Socket编程

    5.6.4 Selector小结..........144 5.7 数据报(UDP)信道..........144 5.8 练习..........149 1. 使用定长的写缓冲区改写TCPEchoClientNonblocking.java。..........149 2.使用Buffer和DatagramChannel编写一个...

    Android C++高级编程:使用NDK_Onur Cinar, 于红PDF电子书下载 带书签目录 完整版

    Android C++高级编程:使用NDK_Onur Cinar, 于红PDF电子书下载 带书签目录 完整版 原书名:Pro Android C++ with the NDK 原出版社: Apress 作者: (美)Onur Cinar 译者: 于红 佘建伟 冯艳红 ...14.4 小结 344

    Android 大文件上传时处理上传进度问题小结

    进行大文件上传时,显示上传进度是很好的用户体验,可以有效的缓解用户急躁的情绪。今天Android IT 分享一个好的显示上传进度的解决方案。 我们用到以下两个类就可实现带进度条的文件上传:...import java.nio.charset

    pro_android_cpp_with_the_ndk.pdf

    1.5 小结 第2章 深入了解AndroidNDK 2.1 AndroidNDK提供的组件 2.2 AndroidNDK的结构 2.3 以一个示例开始 2.3.1 指定AndroidNDK的位置 2.3.2 导入示例项目 2.3.3 向项目中添加原生支持 2.3.4 运行项目 2.3.5 用...

Global site tag (gtag.js) - Google Analytics