package me.test.jdk.java.nio; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.nio.charset.CharsetDecoder; import java.nio.charset.CoderResult; import java.nio.charset.CodingErrorAction; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class NioEchoServer { public static void main(String[] args) { new Thread(new Manager()).start(); } /** * Start server, regist Selector, loop selector and dispatch msg. */ private static class Manager implements Runnable { private ExecutorService exec = null; @Override public void run() { try { ServerSocketChannel channel = ServerSocketChannel.open(); channel.configureBlocking(false); channel.socket().bind(new InetSocketAddress("localhost", 9999)); Selector selector = Selector.open(); channel.register(selector, SelectionKey.OP_ACCEPT); while (true) { // blocking for accept selector.select(); Iterator<SelectionKey> it = selector.selectedKeys().iterator(); while (it.hasNext()) { SelectionKey curKey = it.next(); it.remove(); System.out.println(curKey + ": valid = " + curKey.isValid() + ", acceptable = " + curKey.isAcceptable() + ", connectable = " + curKey.isConnectable() + ", readable = " + curKey.isReadable() + ", writeable = " + curKey.isWritable() ); if (!curKey.isValid()) { continue; } if (curKey.isAcceptable()) { accept(curKey); } else if (curKey.isReadable()) { read(curKey); } } } } catch (IOException e) { e.printStackTrace(); } } private void accept(SelectionKey key) throws IOException { ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel(); SocketChannel clientChannel = serverChannel.accept(); clientChannel.configureBlocking(false); clientChannel.register(key.selector(), SelectionKey.OP_READ); System.out.println("accept connection : " + clientChannel.socket()); } @SuppressWarnings({ "rawtypes", "unchecked" }) private void read(SelectionKey curKey) { // 当有消息时,需要直到构建一个完整的业务数据封包(这里是一个UTF-8编码的单个字符),才传递个一个新的Worker进行处理。 SocketChannel clientChannel = (SocketChannel) curKey.channel(); try { // CASE1:断开连接 if (!clientChannel.isConnected()) { clientChannel.close(); System.out.println("when read, closed."); } else if (!clientChannel.isOpen()) { System.out.println("when read, closed."); } else { SelectionKey regKey = clientChannel.keyFor(curKey.selector()); Map holders = (Map) regKey.attachment(); ByteBuffer byteBuf; CharBuffer charBuf; CharsetDecoder cd; if (holders == null) { holders = new HashMap(); byteBuf = ByteBuffer.allocate(32); holders.put("byteBuf", byteBuf); charBuf = CharBuffer.allocate(2); holders.put("charBuf", charBuf); cd = StandardCharsets.UTF_8 .newDecoder() // .onMalformedInput(CodingErrorAction.REPLACE) .onUnmappableCharacter(CodingErrorAction.REPLACE); holders.put("cd", cd); regKey.attach(holders); } else { byteBuf = (ByteBuffer) holders.get("byteBuf"); charBuf = (CharBuffer) holders.get("charBuf"); cd = (CharsetDecoder) holders.get("cd"); } // 该一回合,可能只读取到部分数据。不能尝试直到读取完毕所有数据, // 否则,若client持续不断的发送数据,则会因此线程一直占有而无法为其他Client提供服务。 int i = clientChannel.read(byteBuf); if (i != 0) { byteBuf.flip(); // 将 byte[] 按照UTF-8的编码解析为一个个字符 while (true) { charBuf.clear(); CoderResult cr = cd.decode(byteBuf, charBuf, i == -1 ? true : false); System.out.println("i = " + i + ", cr " + cr + ", byteBuf=" + byteBuf + ", charBuf=" + charBuf); if (cr.isMalformed()) { byteBuf.position(byteBuf.position() + cr.length()); charBuf.put(cd.replacement()); } // CASE3:正常读取 if (charBuf.position() > 0) { charBuf.flip(); if (exec == null) { exec = new ThreadPoolExecutor(1, 2, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<Runnable>()); } for (int j = 0; j < charBuf.limit(); j++) { exec.execute(new Worker(charBuf.get(j), clientChannel)); // new Worker(charBuf.get(j), clientChannel).run(); } } if (!cr.isError()) { break; } } byteBuf.compact(); } // CASE2:到达流的末尾???可能么? if (i == -1) { if (clientChannel.isConnected()) { clientChannel.close(); System.out.println("when read, reach end, closing."); } } } } catch (IOException e) { if (clientChannel.isConnected()) { try { clientChannel.close(); } catch (IOException e1) { e1.printStackTrace(); } } e.printStackTrace(); } } } /** * 可以当作一个Servlet、Action、Controller etc。 处理一次用户消息。 */ private static class Worker implements Runnable { private SocketChannel channel; private char c; /** * 将指定的字符返回给客户端。 * * @param c * 一个字符。真实场景下可能是一个业务数据封包。之后可能会做很多耗时、耗资源的操作。 * @param channel */ public Worker(char c, SocketChannel channel) { this.c = c; this.channel = channel; } @Override public void run() { try { if ('.' == c) { System.out.println("read terminal command '.' : closing.~~" + channel.socket() + "\n"); // 要关闭socket,否则会Client不会会一直等下去。 channel.socket().close(); channel.close(); } else { channel.write(ByteBuffer.wrap(String.valueOf(c).getBytes("UTF-8"))); System.out.println(">>> '" + c + "'"); } } catch (IOException e) { e.printStackTrace(); } } } }