Java tutorial
package org.reunionemu.jreunion.server; import java.io.IOException; import java.net.BindException; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.nio.ByteBuffer; import java.nio.channels.CancelledKeyException; import java.nio.channels.ClosedChannelException; import java.nio.channels.ClosedSelectorException; import java.nio.channels.SelectableChannel; 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; import javax.annotation.PostConstruct; import org.reunionemu.jreunion.events.Event; import org.reunionemu.jreunion.events.EventDispatcher; import org.reunionemu.jreunion.events.EventListener; import org.reunionemu.jreunion.events.network.NetworkAcceptEvent; import org.reunionemu.jreunion.events.network.NetworkDataEvent; import org.reunionemu.jreunion.events.network.NetworkDisconnectEvent; import org.reunionemu.jreunion.events.network.NetworkSendEvent; import org.reunionemu.jreunion.events.server.ServerEvent; import org.reunionemu.jreunion.events.server.ServerStartEvent; import org.reunionemu.jreunion.events.server.ServerStopEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; /** * @author Aidamina * @license http://reunion.googlecode.com/svn/trunk/license.txt */ @Service public class Network extends EventDispatcher implements Runnable, EventListener { private static Logger logger = LoggerFactory.getLogger(Network.class); private final ByteBuffer buffer = ByteBuffer.allocate(1024 * 64); private Selector selector; private Thread thread; public Network() throws Exception { super(); selector = Selector.open(); } @Autowired Server server; @PostConstruct public void init() { server.addEventListener(ServerEvent.class, this); thread = new Thread(this); thread.setDaemon(true); thread.setName("network"); //thread.start(); } @Override public void run() { logger.info("network thread starting"); while (true) { try { // See if we've had any activity -- either an incoming connection, // or incoming data on an existing connection int num = selector.select(); if (num == 0) { // we need synchronize here otherwise we might block again before we were able to change the selector synchronized (this) { continue; } } // If we don't have any activity, loop around and wait again // Get the keys corresponding to the activity // that has been detected, and process them one by one Set<SelectionKey> keys = selector.selectedKeys(); Iterator<SelectionKey> it = keys.iterator(); while (it.hasNext()) { // Get a key representing one of bits of I/O activity SelectionKey key = it.next(); if (!key.isValid()) continue; SelectableChannel selectableChannel = key.channel(); // What kind of activity is it? if ((key.readyOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT) { // It's an incoming connection. // Register this socket with the Selector // so we can listen for input on it SocketChannel clientSocketChannel = ((ServerSocketChannel) selectableChannel).accept(); processAccept(clientSocketChannel); } else { SocketChannel socketChannel = (SocketChannel) selectableChannel; if ((key.readyOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) { // It's incoming data on a connection, so process it boolean ok = processInput(socketChannel); // If the connection is dead, then remove it // from the selector and close it if (!ok) { LoggerFactory.getLogger(Network.class).info("Client Connection Lost"); key.cancel(); disconnect(socketChannel); } } else if ((key.readyOps() & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE) { boolean ok = processOutput(socketChannel); if (ok) { socketChannel.register(selector, SelectionKey.OP_READ); } } } } // We remove the selected keys, because we've dealt with them. keys.clear(); } catch (Exception e) { if (e instanceof ClosedSelectorException || e instanceof InterruptedException) return; LoggerFactory.getLogger(Network.class).error("Error in network", e); } } } private void processAccept(SocketChannel socketChannel) throws IOException { socketChannel.configureBlocking(false); fireEvent(NetworkAcceptEvent.class, socketChannel); // Register it with the selector, for reading selector.wakeup(); socketChannel.register(selector, SelectionKey.OP_READ); } private boolean processInput(SocketChannel socketChannel) { int result = -1; Client client = null; buffer.clear(); try { result = socketChannel.read(buffer); buffer.flip(); client = Server.getInstance().getWorld().getClients().get(socketChannel); } catch (IOException e) { LoggerFactory.getLogger(this.getClass()).error("Exception", e); } // If no data or client, close the connection if (result <= 0 || client == null) { return false; } byte[] data = new byte[result]; buffer.get(data, 0, result); fireEvent(NetworkDataEvent.class, socketChannel, data); return true; } private boolean processOutput(SocketChannel socketChannel) { Client client = Server.getInstance().getWorld().getClients().get(socketChannel); if (client == null) return false; if (!socketChannel.isOpen() || !socketChannel.isConnected()) { disconnect(socketChannel); return false; } buffer.clear(); byte[] packetBytes = client.flush(); if (packetBytes == null) return true; buffer.put(packetBytes); buffer.flip(); try { socketChannel.write(buffer); } catch (IOException e) { LoggerFactory.getLogger(this.getClass()).error("Exception", e); disconnect(socketChannel); return false; } return true; } public void disconnect(SocketChannel socketChannel) { if (socketChannel.isConnected() && socketChannel.isOpen()) { processOutput(socketChannel); } LoggerFactory.getLogger(Network.class) .info("Disconnecting {local=" + socketChannel.socket().getLocalSocketAddress() + " remote=" + socketChannel.socket().getRemoteSocketAddress() + "}\n"); fireEvent(NetworkDisconnectEvent.class, socketChannel); try { socketChannel.close(); } catch (IOException e) { LoggerFactory.getLogger(this.getClass()).warn("Exception", e); } } public void notifySend(SocketChannel socketChannel) { try { // we synchronize this to make sure we register the key before the selector gets back to sleep again. if (socketChannel.isOpen() && selector.isOpen()) { selector.wakeup(); socketChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE); } } catch (ClosedChannelException e) { //Disconnect detected } catch (CancelledKeyException e) { } catch (Exception e) { LoggerFactory.getLogger(this.getClass()).error("Exception", e); } } public void start() { thread.start(); } public boolean register(InetSocketAddress address) { try { ServerSocketChannel serverChannel = ServerSocketChannel.open(); ServerSocket serverSocket = serverChannel.socket(); serverSocket.bind(address); serverChannel.configureBlocking(false); synchronized (this) { selector.wakeup(); serverChannel.register(selector, SelectionKey.OP_ACCEPT); } } catch (Exception e) { if (e instanceof BindException) { LoggerFactory.getLogger(Network.class) .error("Port " + address.getPort() + " not available. Is the server already running?", e); return false; } } return true; } public void stop() { try { LoggerFactory.getLogger(Network.class).info("net stop"); selector.close(); thread.interrupt(); } catch (IOException e) { LoggerFactory.getLogger(this.getClass()).warn("Exception", e); } } @Override public void handleEvent(Event event) { if (event instanceof NetworkSendEvent) { NetworkSendEvent networkSendEvent = (NetworkSendEvent) event; this.notifySend(networkSendEvent.getSocketChannel()); } else if (event instanceof ServerStartEvent) { start(); } else if (event instanceof ServerStopEvent) { stop(); } } }