您的位置:首页 > 编程语言 > Java开发

Java NIO

2013-10-23 11:40 302 查看
NIO是New IO的缩写,顾名思义,是用于输入输出的新的API,那么,这个NIO相较于旧的IO有什么差别呢?

1、“阻塞”的通信机制

在原有的IO下,我们要与A进行通信时,会怎么做呢?先创建一个线程,然后建立连接,然后不断轮询等待接收消息。当需要与另一个B进行通信时,仍然先创建一个线程,然后建立连接(accept),不断轮询等待接收消息(read)… 在这种情况下,若通信的对象变多时,需要的线程就相应增长,并且每个线程都需要不断轮询等待,若没有消息接受时,则阻塞线程。由于消息传送的频率往往不高,所以,线程大部分时间都处于阻塞状态。而阻塞态的线程仍占用着系统资源,多个线程的管理及线程状态的切换(阻塞-就绪-运行)是比较耗时的。

由于这种方式在一个通信任务没有完成之前,是无法返回的,所以以上的这种方式称作“阻塞”的通信机制。

2、观察者模式

“阻塞”的通信机制,从本质上来看,就是每个通信的线程都需要询问“是否有我的数据?”,如果没有则将该线程阻塞,当有数据到来时,再唤醒线程。每个线程都在观察某个事物,等待自己的事件发生。这个描述,与观察者模式类似。我们可以使用观察者模式来进一步优化“阻塞”的通信机制。我们需要监听多个端口时,传统方式是建立多个线程进行监听。而现在,我们将每个端口封装为一个通道,所有传入该端口的数据都会出现在通道中,假设有3个需要监听的端口,则有3个通道,我们对这三个通道进行监听,使用Selector来注册我们对各个端口监听的行为。比如,对第一个端口(通道),需要监听他是否有可读数据,对于第二个端口(通道)需要监听他是否有accept请求…
Selector记录了我们对各个通道的兴趣点,然后统一的对各个端口进行监听。当有相应的事件发生时,则调用相关的处理函数。这样,就完成了只用一个监听线程完成多个端口监听的任务。这种方式,就是“非阻塞”的通信机制。







3、nio 的关键

我们来想象一下读取文件的方式。首先,若直接从一个文件中读取数据,每次读取一个字符,若文件中有10个字符,那么就需要进行10次IO,而每一次IO都是很耗时的。为了减少IO的次数,我们使用了buffer,假定每次读取的数据都先存入buffer中,每次读取5个字符。那么,从buffer中间接读取字符,尽管同样是10次,但只需要2次的IO就够了,减少了IO次数,提高了效率。

为了能够完成端到端的传输,需要有一定的媒介进行。就像计算机系统中,数据传输是通过总线一样,nio中,数据的传输是通过“通道”进行的。例如,需要将文件A的内容传到(写入)文件B,那么,通过buffer与通道,可以如下操作:





在这里,通道作为中间的传输媒介,Buffer则附加在通道的两端,作为数据的“来源”与目标(真正的应该是File A与File B)。使用通道,则一方(File A)则只需要通过Buffer往通道里写,而另一方(File B)则只需要通过从通道里读即可,而无需关注二者的差异。

在NIO中,通道的一端绑定了相应的目的或者源头,例如文件、socket等,而另一端则是让使用buffer来获取或者写入数据。如下图:





缓冲区的工作与通道紧密联系。通道是I/O 传输发生时通过的入口,而缓冲区是这些数据传输的来源或目标。

NIO中,有以下几个关键类型

1)缓存buffer

NIO中,提供了各个基本类型的buffer,提供给我们以不同的方式进行读写。例如可以用ByteBuffer进行字节(8bit为单位)的数据读写,使用CharByte进行字符(16bit为单位)的数据读写。

2)通道Channel

NIO中有以下几种通道:SocketChannel、ServerSocketChannel、DiagramChannel和FileChannel。

FileChannel只能通过FileOutputStream或者FileInputStream的getChannel调用得到,是单向的,即一端与相应的文件绑定,另一端进行write或者read。

ServerSocketChannel用于服务器端的通道创建,绑定了一个端口号后,所有接受的数据都会在该通道中,配合后面的Selector,可以直接获取封装好(SelectionKey)的某个客户发送的数据。

虽说通道既可读又可写,但实际上,一个绑定了某个源或者目的的通道,是只有读或写的功能的。

3)选择器Selector

他是使得多元I/O成为可能的关键,使用Selector来注册对通道的某些行为的关注,使之可以同时管理监控多个通道,当监控的时间发生时,唤醒并调用统一的事件处理函数进行相应。

选择器Selector:选择器类管理着一个被注册的通道集合的信息和它们的就绪状态。通道是和选择器一起被注册的,并且使用选择器来更新通道的就绪状态。当这么做的时候,可以选择将被激发的线程挂起,直到有就绪的的通道。

可选择通道SelectableChannel:这个抽象类提供了实现通道的可选择性所需要的公共方法。它是所有支持就绪检查的通道类的父类。

选择键SelectionKey:选择键封装了特定的通道与特定的选择器的注册关系。选择键对象被SelectableChannel.register() 返回并提供一个表示这种注册关系的标记。选择键包含了两个比特集(以整数的形式进行编码),指示了该注册关系所关心的通道操作,以及通道已经准备好的操作。

附上网上的一个NIO,Server和Client的代码:

[java]
view plaincopyprint?

<span style="font-size:18px">package nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

/**
* NIO服务端
* @author 小路
*/
public class NIOServer {
//通道管理器
private Selector selector;

/**
* 获得一个ServerSocket通道,并对该通道做一些初始化的工作
* @param port 绑定的端口号
* @throws IOException
*/
public void initServer(int port) throws IOException {
// 获得一个ServerSocket通道
ServerSocketChannel serverChannel = ServerSocketChannel.open();
// 设置通道为非阻塞
serverChannel.configureBlocking(false);
// 将该通道对应的ServerSocket绑定到port端口
serverChannel.socket().bind(new InetSocketAddress(port));
// 获得一个通道管理器
this.selector = Selector.open();
//将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_ACCEPT事件,注册该事件后,
//当该事件到达时,selector.select()会返回,如果该事件没到达selector.select()会一直阻塞。
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
}

/**
* 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理
* @throws IOException
*/
@SuppressWarnings("unchecked")
public void listen() throws IOException {
System.out.println("服务端启动成功!");
// 轮询访问selector
while (true) {
//当注册的事件到达时,方法返回;否则,该方法会一直阻塞
System.out.println("开始阻塞");
selector.select();
System.out.println("解除阻塞");
// 获得selector中选中的项的迭代器,选中的项为注册的事件
Iterator ite = this.selector.selectedKeys().iterator();
while (ite.hasNext()) {
SelectionKey key = (SelectionKey) ite.next();
// 删除已选的key,以防重复处理
ite.remove();
// 客户端请求连接事件
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key
.channel();
// 获得和客户端连接的通道
SocketChannel channel = server.accept();
// 设置成非阻塞
channel.configureBlocking(false);

//在这里可以给客户端发送信息哦
channel.write(ByteBuffer.wrap(new String("向客户端发送了一条信息")
.getBytes()));
//在和客户端连接成功之后,为了可以接收到客户端的信息,需要给通道设置读的权限。
channel.register(this.selector, SelectionKey.OP_READ);

// 获得了可读的事件
} else if (key.isReadable()) {
read(key);
}

}

}
}

/**
* 处理读取客户端发来的信息 的事件
* @param key
* @throws IOException
*/
public void read(SelectionKey key) throws IOException {
// 服务器可读取消息:得到事件发生的Socket通道
SocketChannel channel = (SocketChannel) key.channel();
// 创建读取的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
channel.read(buffer);
byte[] data = buffer.array();
String msg = new String(data).trim();
System.out.println("服务端收到信息:" + msg);
ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes());
channel.write(outBuffer);// 将消息回送给客户端
}

/**
* 启动服务端测试
* @throws IOException
*/
public static void main(String[] args) throws IOException {
NIOServer server = new NIOServer();
server.initServer(8001);
server.listen();
}

}
</span>

[java]
view plaincopyprint?

<span style="font-size:18px">package nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

/**
* NIO客户端
* @author 小路
*/
public class NIOClient {
//通道管理器
private Selector selector;

/**
* 获得一个Socket通道,并对该通道做一些初始化的工作
* @param ip 连接的服务器的ip
* @param port 连接的服务器的端口号
* @throws IOException
*/
public void initClient(String ip, int port) throws IOException {
// 获得一个Socket通道
SocketChannel channel = SocketChannel.open();
// 设置通道为非阻塞
channel.configureBlocking(false);
// 获得一个通道管理器
this.selector = Selector.open();

// 客户端连接服务器,其实方法执行并没有实现连接,需要在listen()方法中调
//用channel.finishConnect();才能完成连接
channel.connect(new InetSocketAddress(ip, port));
//将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_CONNECT事件。
channel.register(selector, SelectionKey.OP_CONNECT);
}

/**
* 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理
* @throws IOException
*/
@SuppressWarnings("unchecked")
public void listen() throws IOException {
// 轮询访问selector
while (true) {
selector.select();
// 获得selector中选中的项的迭代器
Iterator ite = this.selector.selectedKeys().iterator();
while (ite.hasNext()) {
SelectionKey key = (SelectionKey) ite.next();
// 删除已选的key,以防重复处理
ite.remove();
// 连接事件发生
if (key.isConnectable()) {
SocketChannel channel = (SocketChannel) key.channel();
// 如果正在连接,则完成连接
if (channel.isConnectionPending()) {
channel.finishConnect();

}
// 设置成非阻塞
channel.configureBlocking(false);

//在这里可以给服务端发送信息哦
channel.write(ByteBuffer.wrap(new String("向服务端发送了一条信息")
.getBytes()));
//在和服务端连接成功之后,为了可以接收到服务端的信息,需要给通道设置读的权限。
channel.register(this.selector, SelectionKey.OP_READ);

// 获得了可读的事件
} else if (key.isReadable()) {
read(key);
}

}

}
}

/**
* 处理读取服务端发来的信息 的事件
* @param key
* @throws IOException
*/
public void read(SelectionKey key) throws IOException {
//和服务端的read方法一样
// 服务器可读取消息:得到事件发生的Socket通道
SocketChannel channel = (SocketChannel) key.channel();
// 创建读取的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
channel.read(buffer);
byte[] data = buffer.array();
String msg = new String(data).trim();
System.out.println("服务端收到信息:" + msg);
ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes());

}

/**
* 启动客户端测试
* @throws IOException
*/
public static void main(String[] args) throws IOException {
NIOClient client = new NIOClient();
client.initClient("localhost", 8001);
client.listen();
}

}
</span>

原文链接:http://blog.csdn.net/burningsheep/article/details/12918189
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: