除了第一篇小结中讲的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数组与集合小结 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格式电子书 下载(二) 更新 http://download.csdn.net/source/2824040 Java SE实践教程 pdf格式电子书 下载(三) 更新 http://download.csdn.net/source/2824042 Java SE实践教程 pdf格式...
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...
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格式电子书 下载(二) 更新 http://download.csdn.net/source/2824040 Java SE实践教程 pdf格式电子书 下载(三) 更新 http://download.csdn.net/source/2824042 Java SE实践教程 pdf格式...
Java NetIO 一步步从BIO - > BIO+多线程- >单线程+NIO(非阻塞IO) - > NIO(IO多路复用+单线程) - > NIO(IO多路复用+多线程) Java中的NIO其实是NewIO(另一种解释是NO-blocking IO,确实,在Java...小结:ServerSocketCh
前言 第一篇 网站基础知识 第1章 网站架构及其演变过程2 1.1 软件的三大类型2 1.2 基础的结构并不简单3 1.3 架构演变的起点5 1.4 海量数据的解决方案5 1.4.1 缓存和页面静态化5 ...22.3 小结309
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电子书下载 带书签目录 完整版 原书名:Pro Android C++ with the NDK 原出版社: Apress 作者: (美)Onur Cinar 译者: 于红 佘建伟 冯艳红 ...14.4 小结 344
进行大文件上传时,显示上传进度是很好的用户体验,可以有效的缓解用户急躁的情绪。今天Android IT 分享一个好的显示上传进度的解决方案。 我们用到以下两个类就可实现带进度条的文件上传:...import java.nio.charset
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 用...