package com.github.nettybook.ch0; import java.net.InetAddress; 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.nio.channels.spi.SelectorProvider; import java.util.Iterator; import java.util.LinkedList; import java.util.Set; /** * The client object. This is currently only used to queue the data * waiting to be written to the client. */ class EchoClient { private LinkedList<ByteBuffer> outq; EchoClient() { outq = new LinkedList<ByteBuffer>(); } // Return the output queue. public LinkedList<ByteBuffer> getOutputQueue() { return outq; } // Enqueue a ByteBuffer on the output queue. public void enqueue(ByteBuffer bb) { outq.addFirst(bb); } } public class EchoServer { private Selector selector; /** * Accept a new client and set it up for reading. */ private void doAccept(SelectionKey sk) { ServerSocketChannel server = (ServerSocketChannel) sk.channel(); SocketChannel clientChannel; try { clientChannel = server.accept(); clientChannel.configureBlocking(false); // Register this channel for reading. SelectionKey clientKey = clientChannel.register(selector, SelectionKey.OP_READ); // Allocate an EchoClient instance and attach it to this selection key. EchoClient echoClient = new EchoClient(); clientKey.attach(echoClient); InetAddress clientAddress = clientChannel.socket().getInetAddress(); System.out.println("Accepted connection from " + clientAddress.getHostAddress() + "."); } catch (Exception e) { System.out.println("Failed to accept new client."); e.printStackTrace(); } } /** * Read from a client. Enqueue the data on the clients output * queue and set the selector to notify on OP_WRITE. */ private void doRead(SelectionKey sk) { SocketChannel channel = (SocketChannel) sk.channel(); ByteBuffer bb = ByteBuffer.allocate(8192); int len; try { len = channel.read(bb); if (len < 0) { disconnect(sk); return; } } catch (Exception e) { System.out.println("Failed to read from client."); e.printStackTrace(); return; } // Flip the buffer. bb.flip(); EchoClient echoClient = (EchoClient) sk.attachment(); echoClient.enqueue(bb); // We've enqueued data to be written to the client, we must // not set interest in OP_WRITE. sk.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE); } /** * Called when a SelectionKey is ready for writing. */ private void doWrite(SelectionKey sk) { SocketChannel channel = (SocketChannel) sk.channel(); EchoClient echoClient = (EchoClient) sk.attachment(); LinkedList<ByteBuffer> outq = echoClient.getOutputQueue(); ByteBuffer bb = outq.getLast(); try { int len = channel.write(bb); if (len == -1) { disconnect(sk); return; } if (bb.remaining() == 0) { // The buffer was completely written, remove it. outq.removeLast(); } } catch (Exception e) { System.out.println("Failed to write to client."); e.printStackTrace(); } // If there is no more data to be written, remove interest in // OP_WRITE. if (outq.size() == 0) { sk.interestOps(SelectionKey.OP_READ); } } private void disconnect(SelectionKey sk) { SocketChannel channel = (SocketChannel) sk.channel(); InetAddress clientAddress = channel.socket().getInetAddress(); System.out.println(clientAddress.getHostAddress() + " disconnected."); try { channel.close(); } catch (Exception e) { System.out.println("Failed to close client socket channel."); e.printStackTrace(); } } private void startServer() throws Exception { selector = SelectorProvider.provider().openSelector(); // Create non-blocking server socket. ServerSocketChannel ssc = ServerSocketChannel.open(); ssc.configureBlocking(false); // Bind the server socket to localhost. InetSocketAddress isa = new InetSocketAddress(InetAddress.getLocalHost(), 8888); ssc.socket().bind(isa); // Register the socket for select events. SelectionKey acceptKey = ssc.register(selector, SelectionKey.OP_ACCEPT); // Loop forever. for (;;) { selector.select(); Set readyKeys = selector.selectedKeys(); Iterator i = readyKeys.iterator(); while (i.hasNext()) { SelectionKey sk = (SelectionKey) i.next(); i.remove(); if (sk.isAcceptable()) { doAccept(sk); } if (sk.isValid() && sk.isReadable()) { doRead(sk); } if (sk.isValid() && sk.isWritable()) { doWrite(sk); } } } } // Main entry point. public static void main(String[] args) { EchoServer echoServer = new EchoServer(); try { echoServer.startServer(); } catch (Exception e) { System.out.println("Exception caught, program exiting..."); e.printStackTrace(); } } }