1、缓冲区Buffer
在NIO库中,所有数据都是用缓冲区(Buffer)处理的。缓冲区实质上是一个数组,通常它是一个字节数组(ByteBuffer),也可以使用其他种类的数组。但是一个缓冲区不仅仅是一个数组,缓冲区提供了对数据的结构化访问以及维护读写位置(limit、position)等信息。
2、通道Channel
Channel是一个通道,可以通过它读取和写入数据。通道与流的不同之处在于通道是双向的,流只是一个方向上移动(一个流必须是InputStream或者OutputStream的子类),而且通道可以同时用于读写。
3、多路复用器Selector
多路复用器提供选择已经就绪的任务的能力。简单来讲,Selector会不断地轮询注册在其上的Channel,如果某个Channel上面有新的TCP连接接入、读和写事件,这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以获得就绪Channel的集合,进行后续的I/O操作。
介绍完NIO几个核心概念,下面来看看服务端与客户端的通信序列图,并用NIO实现一个EchoServer,以对整个流程有更好的理解。
package io.nio; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; 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 NioEchoServer { public static void main(String[] args) { MultiplexerEchoServer server = new MultiplexerEchoServer(8080); new Thread(server).start(); new Thread(new Power(server)).start(); } private static class MultiplexerEchoServer implements Runnable { private Selector selector; private ServerSocketChannel server; private volatile boolean running; public MultiplexerEchoServer(int port) { try { running = true; selector = Selector.open(); server = ServerSocketChannel.open(); server.configureBlocking(false); server.socket().bind(new InetSocketAddress(port), 1024); server.register(selector, SelectionKey.OP_ACCEPT); System.out.println("Server start in:" + port); } catch (IOException e) { e.printStackTrace(); System.exit(1); } } public void stop() { running = false; } @Override public void run() { while (running) { try { selector.select(1000); Set<SelectionKey> readyKeys = selector.selectedKeys(); Iterator<SelectionKey> it = readyKeys.iterator(); SelectionKey key = null; while (it.hasNext()) { key = it.next(); it.remove(); try { handle(key); } catch (Exception e) { if (key != null) { key.cancel(); if (key.channel() != null) { key.channel().close(); } } } } } catch (IOException e) { e.printStackTrace(); } } } private void handle(SelectionKey key) throws IOException { if (key.isValid()) { if (key.isAcceptable()) { ServerSocketChannel ssc = (ServerSocketChannel) key.channel(); SocketChannel sc = ssc.accept(); sc.configureBlocking(false); sc.register(selector, SelectionKey.OP_READ); } if (key.isReadable()) { SocketChannel sc = (SocketChannel) key.channel(); ByteBuffer buffer = ByteBuffer.allocate(1024); int readBytes = sc.read(buffer); // 大于0:读到了字节、0:没有读到字节、-1:链路已经关闭 if (readBytes > 0) { buffer.flip(); byte[] bytes = new byte[buffer.remaining()]; buffer.get(bytes); String msg = new String(bytes, "UTF-8"); System.out.println("Server received:" + msg); doWrite(sc, msg); } else if (readBytes < 0) { key.cancel(); sc.close(); } } } } private void doWrite(SocketChannel channel, String response) throws IOException { if (response != null && response.trim().length() > 0) { byte[] bytes = response.getBytes(); ByteBuffer buffer = ByteBuffer.allocate(bytes.length); buffer.put(bytes); buffer.flip(); channel.write(buffer); } } } private static class Power implements Runnable { private MultiplexerEchoServer server; public Power(MultiplexerEchoServer server) { this.server = server; } @Override public void run() { BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); try { String msg = in.readLine(); while (msg != null && !"shutdown".equalsIgnoreCase(msg)) { msg = in.readLine(); } server.stop(); } catch (IOException e) { e.printStackTrace(); } } } }
package io.nio; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; 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; import java.util.Set; public class NioEchoClient { public static void main(String[] args) { EchoClient client = new EchoClient("127.0.0.1", 8080); new Thread(client).start(); new Thread(new Writer(client)).start(); } private static class EchoClient implements Runnable { private String host; private int port; private Selector selector; private SocketChannel client; private volatile boolean running; public EchoClient(String host, int port) { try { this.running = true; this.host = host == null ? "127.0.0.1" : host; this.port = port; this.selector = Selector.open(); this.client = SocketChannel.open(); client.configureBlocking(false); } catch (IOException e) { e.printStackTrace(); System.exit(1); } } @Override public void run() { try { doConnect(); } catch (IOException e) { e.printStackTrace(); System.exit(1); } while (running) { try { selector.select(1000); Set<SelectionKey> readyKeys = selector.selectedKeys(); Iterator<SelectionKey> it = readyKeys.iterator(); SelectionKey key = null; while (it.hasNext()) { key = it.next(); it.remove(); try { handle(key); } catch (Exception e) { e.printStackTrace(); if (key != null) { key.cancel(); if (key.channel() != null) { key.channel().close(); } } System.exit(1); } } } catch (Exception e) { e.printStackTrace(); System.exit(1); } } if (selector != null) { try { selector.close(); } catch (IOException e) { e.printStackTrace(); } } } private void handle(SelectionKey key) throws IOException { if (key.isValid()) { SocketChannel sc = (SocketChannel) key.channel(); if (key.isConnectable()) { if (sc.finishConnect()) { sc.register(selector, SelectionKey.OP_READ); } else { System.exit(1); } } if (key.isReadable()) { ByteBuffer buffer = ByteBuffer.allocate(1024); int readBytes = sc.read(buffer); // 大于0:读到了字节、0:没有读到字节、-1:链路已经关闭 if (readBytes > 0) { buffer.flip(); byte[] bytes = new byte[buffer.remaining()]; buffer.get(bytes); String msg = new String(bytes, "UTF-8"); System.out.println("Client received:" + msg); } else if (readBytes < 0) { key.cancel(); sc.close(); } } } } private void doConnect() throws IOException { if (client.connect(new InetSocketAddress(host, port))) { client.register(selector, SelectionKey.OP_READ); } else { client.register(selector, SelectionKey.OP_CONNECT); } } public void doWrite(String msg) { byte[] bytes = msg.getBytes(); ByteBuffer buffer = ByteBuffer.allocate(bytes.length); buffer.put(bytes); buffer.flip(); try { client.write(buffer); System.out.println("Cliend send:" + msg); } catch (IOException e) { e.printStackTrace(); } } public void stop() { running = false; } } private static class Writer implements Runnable { private EchoClient client; public Writer(EchoClient client) { this.client = client; } @Override public void run() { BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); try { String msg = in.readLine(); while (msg != null && !"quit".equalsIgnoreCase(msg)) { client.doWrite(msg); msg = in.readLine(); } client.stop(); } catch (IOException e) { e.printStackTrace(); } } } }