/* * This file is part of aion-emu <aion-emu.com>. * * aion-emu is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * aion-emu is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with aion-emu. If not, see <http://www.gnu.org/licenses/>. */ package com.aionemu.commons.network; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.channels.SelectionKey; import java.nio.channels.ServerSocketChannel; import java.util.ArrayList; import java.util.List; import org.apache.log4j.Logger; import com.aionemu.commons.options.Assertion; /** * NioServer instance that handle connections on specified addresses. * * @author -Nemesiss- */ public class NioServer { /** * Logger for NioServer */ private static final Logger log = Logger.getLogger(NioServer.class.getName()); /** * The channels on which we'll accept connections */ private final List<SelectionKey> serverChannelKeys = new ArrayList<SelectionKey>(); /** * Dispatcher that will accept connections */ private Dispatcher acceptDispatcher; /** * Useful int to load balance connections between Dispatchers */ private int currentReadWriteDispatcher; /** * Read Write Dispatchers */ private Dispatcher[] readWriteDispatchers; /** * DisconnectionThreadPool that will be used to execute DisconnectionTask. */ private final DisconnectionThreadPool dcPool; /** * */ private int readWriteThreads; /** * */ private ServerCfg[] cfgs; /** * Constructor. * * @param readWriteThreads * - number of threads that will be used for handling read and write. * @param dcPool * - ThreadPool on witch Disconnection tasks will be executed. * @param cfgs * - Server Configurations */ public NioServer(int readWriteThreads, DisconnectionThreadPool dcPool, ServerCfg... cfgs) { /** * Test if this build should use assertion and enforce it. If NetworkAssertion == false javac will remove this * code block */ if(Assertion.NetworkAssertion) { boolean assertionEnabled = false; assert assertionEnabled = true; if(!assertionEnabled) throw new RuntimeException( "This is unstable build. Assertion must be enabled! Add -ea to your start script or consider using stable build instead."); } this.dcPool = dcPool; this.readWriteThreads = readWriteThreads; this.cfgs = cfgs; } public void connect() { try { this.initDispatchers(readWriteThreads, dcPool); /** Create a new non-blocking server socket channel for clients */ for(ServerCfg cfg : cfgs) { ServerSocketChannel serverChannel = ServerSocketChannel.open(); serverChannel.configureBlocking(false); /** Bind the server socket to the specified address and port */ InetSocketAddress isa; if("*".equals(cfg.hostName)) { isa = new InetSocketAddress(cfg.port); log .info("Server listening on all available IPs on Port " + cfg.port + " for " + cfg.connectionName); } else { isa = new InetSocketAddress(cfg.hostName, cfg.port); log.info("Server listening on IP: " + cfg.hostName + " Port " + cfg.port + " for " + cfg.connectionName); } serverChannel.socket().bind(isa); /** * Register the server socket channel, indicating an interest in accepting new connections */ SelectionKey acceptKey = getAcceptDispatcher().register(serverChannel, SelectionKey.OP_ACCEPT, new Acceptor(cfg.factory, this)); serverChannelKeys.add(acceptKey); } } catch(Exception e) { log.fatal("NioServer Initialization Error: " + e, e); throw new Error("NioServer Initialization Error!"); } } /** * @return Accept Dispatcher. */ public final Dispatcher getAcceptDispatcher() { return acceptDispatcher; } /** * @return one of ReadWrite Dispatcher or Accept Dispatcher if readWriteThreads was set to 0. */ public final Dispatcher getReadWriteDispatcher() { if(readWriteDispatchers == null) return acceptDispatcher; if(readWriteDispatchers.length == 1) return readWriteDispatchers[0]; if(currentReadWriteDispatcher >= readWriteDispatchers.length) currentReadWriteDispatcher = 0; return readWriteDispatchers[currentReadWriteDispatcher++]; } /** * Initialize Dispatchers. * * @param readWriteThreads * @param dcPool * @throws IOException */ private void initDispatchers(int readWriteThreads, DisconnectionThreadPool dcPool) throws IOException { if(readWriteThreads <= 0) { acceptDispatcher = new AcceptReadWriteDispatcherImpl("AcceptReadWrite Dispatcher", dcPool); acceptDispatcher.start(); } else { acceptDispatcher = new AcceptDispatcherImpl("Accept Dispatcher"); acceptDispatcher.start(); readWriteDispatchers = new Dispatcher[readWriteThreads]; for(int i = 0; i < readWriteDispatchers.length; i++) { readWriteDispatchers[i] = new AcceptReadWriteDispatcherImpl("ReadWrite-" + i + " Dispatcher", dcPool); readWriteDispatchers[i].start(); } } } /** * @return Number of active connections. */ public final int getActiveConnections() { int count = 0; if(readWriteDispatchers != null) { for(Dispatcher d : readWriteDispatchers) count += d.selector().keys().size(); } else { count = acceptDispatcher.selector().keys().size() - serverChannelKeys.size(); } return count; } /** * Shutdown. */ public final void shutdown() { log.info("Closing ServerChannels..."); try { for(SelectionKey key : serverChannelKeys) key.cancel(); log.info("ServerChannel closed."); } catch(Exception e) { log.error("Error during closing ServerChannel, " + e, e); } notifyServerClose(); /** Wait 5s */ try { Thread.sleep(1000); } catch(Throwable t) { log.warn("Nio thread was interrupted during shutdown", t); } log.info(" Active connections: " + getActiveConnections()); /** DC all */ log.info("Forced Disconnecting all connections..."); closeAll(); log.info(" Active connections: " + getActiveConnections()); dcPool.waitForDisconnectionTasks(); /** Wait 5s */ try { Thread.sleep(1000); } catch(Throwable t) { log.warn("Nio thread was interrupted during shutdown", t); } } /** * Calls onServerClose method for all active connections. */ private void notifyServerClose() { if(readWriteDispatchers != null) { for(Dispatcher d : readWriteDispatchers) for(SelectionKey key : d.selector().keys()) { if(key.attachment() instanceof AConnection) { ((AConnection) key.attachment()).onServerClose(); } } } else { for(SelectionKey key : acceptDispatcher.selector().keys()) { if(key.attachment() instanceof AConnection) { ((AConnection) key.attachment()).onServerClose(); } } } } /** * Close all active connections. */ private void closeAll() { if(readWriteDispatchers != null) { for(Dispatcher d : readWriteDispatchers) for(SelectionKey key : d.selector().keys()) { if(key.attachment() instanceof AConnection) { ((AConnection) key.attachment()).close(true); } } } else { for(SelectionKey key : acceptDispatcher.selector().keys()) { if(key.attachment() instanceof AConnection) { ((AConnection) key.attachment()).close(true); } } } } }