package ch.ethz.syslab.telesto.server.network;
import java.io.IOException;
import java.net.InetSocketAddress;
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.concurrent.ArrayBlockingQueue;
import ch.ethz.syslab.telesto.common.network.Connection;
import ch.ethz.syslab.telesto.common.util.Log;
import ch.ethz.syslab.telesto.profile.BenchmarkLog;
import ch.ethz.syslab.telesto.server.db.Database;
public class ConnectionHandler extends Thread {
private static Log LOGGER = new Log(ConnectionHandler.class);
private Database database = new Database();
private ServerSocketChannel socket;
private DataHandler[] workers;
private Selector selector = Selector.open();
private ArrayBlockingQueue<Connection> clientQueue = new ArrayBlockingQueue<Connection>(10000);
private boolean running = true;
public ConnectionHandler(InetSocketAddress address, int workerCount, BenchmarkLog log) throws IOException {
// Setting up data workers
workers = new DataHandler[workerCount];
for (int i = 0; i < workerCount; i++) {
workers[i] = new DataHandler(clientQueue, i, log);
}
// Setting up selector
socket = ServerSocketChannel.open().bind(address);
socket.configureBlocking(false);
socket.register(selector, SelectionKey.OP_ACCEPT);
LOGGER.config("Listening on %s:%d", address.getHostName(), address.getPort());
// Setting up database
database.initialize();
}
@Override
public void start() {
super.start();
for (DataHandler worker : workers) {
worker.start();
}
}
@Override
public void run() {
try {
eventLoop();
} catch (IOException e) {
e.printStackTrace();
LOGGER.severe(e, "Error while selecting active channels");
}
}
public void shutdown() {
LOGGER.info("Shutting down IO loop...");
running = false;
LOGGER.info("Waiting for client queue to be emptied...");
while (!clientQueue.isEmpty()) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// Ignore
}
}
LOGGER.info("Terminating workers...");
for (DataHandler worker : workers) {
worker.shutdown();
}
LOGGER.info("Closing connections...");
for (SelectionKey key : selector.keys()) {
try {
key.channel().close();
} catch (IOException e) {
// Ignore
}
}
}
private void eventLoop() throws IOException {
while (running) {
int availableChannels = selector.select();
Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
LOGGER.finest("Selected channels: %d", availableChannels);
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isReadable()) {
try {
read(key);
} catch (IOException e) {
key.cancel();
LOGGER.info("Client disonnected: %s", key.attachment());
}
} else if (key.isAcceptable()) {
try {
accept(key);
} catch (IOException e) {
key.cancel();
LOGGER.info("Error while accepting connection: %s", e);
}
}
keyIterator.remove();
}
}
}
private void read(SelectionKey key) throws IOException {
SocketChannel channel = (SocketChannel) key.channel();
Connection connection = (Connection) key.attachment();
int bytesRead;
bytesRead = connection.readFromChannel();
if (bytesRead > 0) {
LOGGER.fine("Read %s bytes from; %s", bytesRead, channel.getRemoteAddress());
clientQueue.add(connection);
} else if (bytesRead < 0) {
LOGGER.info("Disconnected %s", channel.getRemoteAddress());
connection.disconnect();
}
}
private void accept(SelectionKey key) throws IOException {
SocketChannel channel = ((ServerSocketChannel) key.channel()).accept();
if (channel == null) {
LOGGER.warning("Accepted channel is null");
return;
}
LOGGER.info("Accepted new connection from %s", channel.getRemoteAddress());
channel.configureBlocking(false);
ServerConnection connection = new ServerConnection(channel, database);
connection.setSelectionKey(channel.register(selector, SelectionKey.OP_READ, connection));
}
}