NIO和旧IO的区别
NIO的涉及到的类
例子
一点学习NIO的心得,抛砖引玉,若有错误请高手指正。
NIO和旧IO的区别
NIO是传输是基于字节块的,而原来的IO是基于字节流的。NIO的优势不在于传输速度上,而是在其处理方式上。NIO使用selector来轮询可以使用的channel。selector的时下由底层jvm提供支持的。在连接(channel)很多个情况下,selector可以用来关心IO可用性,而线程高效的处理业务逻辑。
NIO相关类
- Channel:对底层I/O接口的一个抽象,可以打开底层I/O,比如socket,文件,磁盘等等。可以在在多个线程中共享
- Selector: 对可以选择的channel的一个多路选择器,可以从多个channel中选择出可以使用channel,所以selector和channel是一对多。
- Buffer:缓存块。其实普通IO我们也有Buffer封装类来加速IO处理。NIO定义了普通类型的buffer,比如ByteBuffer,IntBuffer,Longbuffer等。
例子
-
参考java网络编程中的Chargen协议,以socket为例,演示NIO的用法。CharGen主要是实现对访问的客户端返回顺序的字符串序列。
客户端socket相对简单些,步骤如下:
- 创建读取Channel 和ByteBuffer
- 创建写入Channel ,将读取的内容写入
package nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
public class CharGenClient {
public static void main(String[] args) {
// TODO Auto-generated method stub
SocketChannel channel = null;
WritableByteChannel writeChannel = null;
try {
//使用工厂方法声明一个Channel,指定网络链接
channel = SocketChannel.open(new InetSocketAddress("127.0.0.1",5536));
//将Channel绑定到指定端口(客户客户端的socket,让系统默认绑定一个本地端口)
//channel.bind();
//分配一个缓冲区,以便接收服务端送来的数据块
ByteBuffer buffer = ByteBuffer.allocate(80);
//创建一个写channel,将接收到到数据写到console
writeChannel = Channels.newChannel(System.out);
//轮询读取服务其的数据
while(channel.read(buffer)!=-1) {
//限定Buffer的实际数据大小
buffer.flip();
//写入到输出channel
writeChannel.write(buffer);
//清空buffer,以便下次读取
buffer.clear();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if(channel != null) {
try {
//关闭channel
writeChannel.close();
channel.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
接着实现服务端。步骤如下:
- 定义服务端的Channel,并绑定到指定端口。并声明该channel为非阻塞的。
- 定义selector,将服务端的channel注册到selector,并指定关注事件类型为selectionkey.OP_ACCEPT。
- 简单的定义的轮询selector.select(); 这个事件会阻塞,直到有相关可以处理的SelectionKey.channel唤醒。
- 遍历selector.selectedKey(),根据Key的事件进行相关处理
- 如果是接收事件,则通过selectionKey获取对应的ServerSocketChannel。利用这个channel生成SocketChannel。并注册到刚刚的Selector 中,并关注写事件
- 如果是写事件,则分配ByteBuffer供后续Channel读取。
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;
import java.util.Set;
public class CharGenServer {
/**
* 服务端CharGen,接收客户端的访问,顺序输出字符串,直到客户端终止链接。使用NIO实现。</br>
* 1、初始化服务端的Channel,以便监听来的请求</br>
* 2、根据来的请求生成客户端Channel,返回字符串
* </br>
* 使用到的类包括:Channel、Buffer、Selector、SelectorKey等
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
//初始化需要输出的内容,在内存中缓存
byte[] rotation = new byte[95*2];
for(byte i= ' ';i<= '~';i++) {
rotation[i-' '] = i;
rotation[i+95-' '] = i;
}
ServerSocketChannel server = null;
SocketChannel client = null;
Selector selector =null;
try {
//获取服务端socket的channel
server = ServerSocketChannel.open();
//channel和指定的端口绑定,对外提供服务
server.bind(new InetSocketAddress(5536));
//设置服务端channel为非阻塞的,默认是阻塞的。
server.configureBlocking(false);
//工厂方法调用selector
selector = Selector.open();
//将服务端channel注册到selector,并指定关注类型为accept
server.register(selector, SelectionKey.OP_ACCEPT);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return;
}
//select开始轮询
while(true) {
try {
//这是个阻塞方法,会等待该select对应的keys中至少一个Channel可以进行I/O操作。
selector.select();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
break;
}
//获取可以操作的keys,只允许从keyset中删除,不能直接添加。非线程安全的。
Set<SelectionKey> readyKeys = selector.selectedKeys();
//遍历seletionkey
Iterator<SelectionKey> iterator = readyKeys.iterator();
while(iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();//从Set删除待处理的key
try {
//判断key所属channel属于那类型的‘事件’
if(key.isAcceptable()) {
//获取key对应的channel
ServerSocketChannel s = (ServerSocketChannel)key.channel();
//因为服务端的channel是非阻塞的,所以稳妥点的化,需要判断返回的socketchannel是否为null。
client = s.accept();
System.out.println("Accepted connection from " + client);
client.configureBlocking(false);
//针对客户端的channel注册selector,以及关注的事件,以便提高处理效率,在链接空闲时,不占用系统资源
SelectionKey key2 = client.register(selector, SelectionKey.OP_WRITE);
//分配缓存,这个是NIO处理的基本单元。
ByteBuffer buffer = ByteBuffer.allocate(74);
buffer.put(rotation,0,72);
buffer.put((byte)'\r');
buffer.put((byte)'\n');
buffer.flip();
//将分配的缓存对象作为selectionKey的附件,以便后续的channel可以使用
key2.attach(buffer);
}else if (key.isWritable()) {
client = (SocketChannel)key.channel();
//从附件中获取待处理的缓存
ByteBuffer buffer = (ByteBuffer)key.attachment();
if(!buffer.hasRemaining()) {
buffer.rewind();//重置buffer的position到0
byte first = buffer.get();//获取第一个字符
buffer.rewind();//重置buffer的position到0
int position = first - ' ' + 1;
buffer.put(rotation, position, 72);
buffer.put((byte)'\r');
buffer.put((byte)'\n');
buffer.flip();//设置缓存中有效值的截止位置
}
client.write(buffer);//将数据写入channel
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
key.cancel();
try {
key.channel().close();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}/*finally {//不能放在finally中close channel,因为服务端的channel还不能关闭
key.channel().close();
}*/
}
}
}
}