Java NIO(New IO)是非阻塞的IO,所以NIO也被当成是non-blocking IO的简称,在jdk1.4以后的版本中提供支持。标准的IO基于字节流和字符流进行操作的,而NIO是基于通道(Channel)和缓冲区(Buffer)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。
Java NIO提供了与标准IO不同的IO工作方式:
Channels and Buffers(通道和缓冲区):标准的IO基于字节流和字符流进行操作的,而NIO是基于通道(Channel)和缓冲区(Buffer)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。
Asynchronous IO(异步IO):Java NIO可以让你异步的使用IO,例如:当线程从通道读取数据到缓冲区时,线程还是可以进行其他事情。当数据被写入到缓冲区时,线程可以继续处理它。从缓冲区写入通道也类似。
Selectors(选择器):Java NIO引入了选择器的概念,选择器用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个的线程可以监听多个数据通道。
服务器采用非阻塞的NIO方式,还是传统的多线程方式?目前还没有证据表明谁优谁劣。下面的基于NIO非阻塞服务器端的简单实现仅供学习之用,工作环境请勿模仿,否则后果自负哦。
服务器端实现
package com.xieyincai.net; 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 NioNonBlockingServer { private final static int PORT = 8000; public static void main(String[] args) { // TODO Auto-generated method stub // 服务器通道 ServerSocketChannel serverSocketChannel = null; // 选择器 Selector selector = null; try { serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.bind(new InetSocketAddress(PORT)); // 配置为非阻塞模式,默认为阻塞模式。 serverSocketChannel.configureBlocking(false); // 打开一个Selector对象 selector = Selector.open(); // 向给定的选择器selector注册此通道 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } while(true) { try { int n = selector.select(); // 选择器还无事可做,继续。。。 if(n == 0) continue; } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = selectionKeys.iterator(); while (iterator.hasNext()) { SelectionKey key = iterator.next(); // 已经取出key,从迭代器中把当前key删除。 iterator.remove(); try { if (key.isAcceptable()) { ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel(); SocketChannel clientChannel = serverChannel.accept(); System.out.println("CLIENT:\t" + clientChannel.socket().getInetAddress() + " : " + clientChannel.socket().getPort()); // 设置为非阻塞模式 clientChannel.configureBlocking(false); // 注册到selector选择器上 clientChannel.register(selector, SelectionKey.OP_READ); } if(key.isReadable()) { SocketChannel clientChannel = (SocketChannel) key.channel(); ByteBuffer buffer = ByteBuffer.allocate(4096); // 获取客户端发过来的数据 clientChannel.read(buffer); String content = new String(buffer.array(), "UTF-8"); // 打印获取的客户端数据 System.out.println(content); // 数据前面加“ECHO:\t” content = "ECHO:\t" + content; // content编码,再包裹成ByteBuffer。 buffer = ByteBuffer.wrap(content.getBytes("UTF-8")); // 最后有意义的一步,发送。 clientChannel.write(buffer); // 完成使命,关闭通道。 clientChannel.close(); } } 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(); } } } } } }
客户端实现
package com.xieyincai.net; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketAddress; public class SimpleTCPClient { private final static String HOSTNAME = "localhost"; private final static int PORT = 8000; public static void main(String[] args) { // TODO Auto-generated method stub Socket socket = new Socket(); try { SocketAddress address = new InetSocketAddress(HOSTNAME, PORT); socket.connect(address); OutputStream out = socket.getOutputStream(); InputStream in = socket.getInputStream(); String data = String.valueOf(System.nanoTime()); System.out.println(data); out.write(data.getBytes("UTF-8")); out.flush(); byte[] buffer = new byte[4096]; int length = in.read(buffer); String result = new String(buffer, 0, length, "UTF-8"); System.out.println(result); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { try { socket.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
服务器端输出:
CLIENT: /127.0.0.1 : 51979 77667180634774 CLIENT: /127.0.0.1 : 51995 77670570107738 CLIENT: /127.0.0.1 : 52004 77671463497729 CLIENT: /127.0.0.1 : 52007 77672178722326 CLIENT: /127.0.0.1 : 52009 77673039094364
客户端输出:
77673039094364 ECHO: 77673039094364