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(); } } }