/*
* 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);
}
}
}
}
}