package bgu.bio.com.reactor;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import bgu.bio.com.protocol.ServerProtocolFactory;
import bgu.bio.io.file.properties.PropertiesUtils;
/**
* An implementation of the Reactor pattern.
*/
public class Reactor implements Runnable {
private static final Logger logger = Logger.getLogger("bio.reactor");
private final int _port;
private final int _poolSize;
private final ServerProtocolFactory _protocolFactory;
private final TokenizerFactory _tokenizerFactory;
private volatile boolean _shouldRun = true;
private ReactorData _data;
/**
* Creates a new Reactor
*
* @param poolSize
* the number of WorkerThreads to include in the ThreadPool
* @param port
* the port to bind the Reactor to
* @param protocol
* the protocol factory to work with
* @param tokenizer
* the tokenizer factory to work with
* @throws IOException
* if some I/O problems arise during connection
*/
public Reactor(int port, int poolSize, ServerProtocolFactory protocol,
TokenizerFactory tokenizer) {
_port = port;
_poolSize = poolSize;
_protocolFactory = protocol;
_tokenizerFactory = tokenizer;
}
/**
* Instantiates a new reactor with a UTF-8 char set.
*
* @param port the port
* @param poolSize the pool size
* @param protocol the protocol
*/
public Reactor(int port, int poolSize, ServerProtocolFactory protocol){
final Charset charset = Charset.forName("UTF-8");
TokenizerFactory tokenizerMaker = new TokenizerFactory() {
public StringMessageTokenizer create() {
return new FixedSeparatorMessageTokenizer("\n", charset);
}
};
_port = port;
_poolSize = poolSize;
_protocolFactory = protocol;
_tokenizerFactory = tokenizerMaker;
}
/**
* Create a non-blocking server socket channel and bind to to the Reactor
* port
*/
private ServerSocketChannel createServerSocket(int port) throws IOException {
try {
ServerSocketChannel ssChannel = ServerSocketChannel.open();
ssChannel.configureBlocking(false);
ssChannel.socket().bind(new InetSocketAddress(port));
return ssChannel;
} catch (IOException e) {
logger.severe("Port " + port + " is busy");
throw e;
}
}
/**
* Main operation of the Reactor:
* <UL>
* <LI>Uses the <CODE>Selector.select()</CODE> method to find new requests
* from clients
* <LI>For each request in the selection set:
* <UL>
* If it is <B>acceptable</B>, use the ConnectionAcceptor to accept it,
* create a new ConnectionHandler for it register it to the Selector
* <LI>If it is <B>readable</B>, use the ConnectionHandler to read it,
* extract messages and insert them to the ThreadPool
* </UL>
*/
public void run() {
// Create & start the ThreadPool
ExecutorService executor = Executors.newFixedThreadPool(_poolSize);
Selector selector = null;
logger.setLevel(Level.SEVERE);
ServerSocketChannel ssChannel = null;
try {
selector = Selector.open();
ssChannel = createServerSocket(_port);
} catch (IOException e) {
logger.info("cannot create the selector -- server socket is busy?");
return;
}
_data = new ReactorData(executor, selector, _protocolFactory,
_tokenizerFactory);
ConnectionAcceptor connectionAcceptor = new ConnectionAcceptor(
ssChannel, _data);
// Bind the server socket channel to the selector, with the new
// acceptor as attachment
try {
ssChannel.register(selector, SelectionKey.OP_ACCEPT,
connectionAcceptor);
} catch (ClosedChannelException e) {
logger.info("server channel seems to be closed!");
return;
}
logger.info("Reactor is ready on port " + getPort());
while (_shouldRun && selector.isOpen()) {
// Wait for an event
try {
selector.select();
} catch (IOException e) {
logger.info("trouble with selector: " + e.getMessage());
continue;
}
// Get list of selection keys with pending events
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
// Process each key
while (it.hasNext()) {
// Get the selection key
SelectionKey selKey = it.next();
// Remove it from the list to indicate that it is being
// processed. it.remove removes the last item returned by next.
it.remove();
// Check if it's a connection request
if (selKey.isValid() && selKey.isAcceptable()) {
logger.info("Accepting a connection");
ConnectionAcceptor acceptor = (ConnectionAcceptor) selKey
.attachment();
try {
acceptor.accept();
} catch (IOException e) {
logger.info("problem accepting a new connection: "
+ e.getMessage());
}
continue;
}
// Check if a message has been sent
if (selKey.isValid() && selKey.isReadable()) {
ConnectionHandler handler = (ConnectionHandler) selKey
.attachment();
logger.info("Channel is ready for reading");
handler.read();
}
// Check if there are messages to send
if (selKey.isValid() && selKey.isWritable()) {
ConnectionHandler handler = (ConnectionHandler) selKey
.attachment();
logger.info("Channel is ready for writing");
handler.write();
}
}
}
stopReactor();
}
/**
* Returns the listening port of the Reactor
*
* @return the listening port of the Reactor
*/
public int getPort() {
return _port;
}
/**
* Stops the Reactor activity, including the Reactor thread and the Worker
* Threads in the Thread Pool.
*/
public synchronized void stopReactor() {
if (!_shouldRun)
return;
_shouldRun = false;
_data.getSelector().wakeup(); // Force select() to return
_data.getExecutor().shutdown();
try {
_data.getExecutor().awaitTermination(2000, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
// Someone didn't have patience to wait for the executor pool to
// close
e.printStackTrace();
}
}
public static void execute(int port, int poolSize,
ServerProtocolFactory protocolMaker) {
TokenizerFactory tokenizerMaker = new TokenizerFactory() {
public StringMessageTokenizer create() {
return new FixedSeparatorMessageTokenizer("\n", Charset
.forName("UTF-8"));
}
};
Reactor reactor = new Reactor(port, poolSize, protocolMaker,
tokenizerMaker);
Thread thread = new Thread(reactor);
thread.start();
logger.info("Reactor is ready on port " + reactor.getPort());
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* Main program, used for demonstration purposes. Create and run a
* Reactor-based server for the Echo protocol. Listening port number and
* number of threads in the thread pool are read from the command line.
*/
public static void main(final String args[]) {
if (args.length != 1) {
System.err.println("Usage: java Reactor <config file>");
System.exit(1);
}
try {
Properties props = new Properties();
FileInputStream inStream = new FileInputStream(args[0]);
props.load(inStream);
inStream.close();
ServerProtocolFactory protocolMaker = (ServerProtocolFactory) PropertiesUtils
.instantiateFromProps(props.getProperty("protocol"),
"protocol", props,
ServerProtocolFactory.class);
final Charset charset = Charset.forName(props.getProperty(
"encoding", "UTF-8"));
TokenizerFactory tokenizerMaker = new TokenizerFactory() {
public StringMessageTokenizer create() {
return new FixedSeparatorMessageTokenizer("\n", charset);
}
};
int port = Integer.parseInt(props.getProperty("port", "57999"));
int poolSize = Runtime.getRuntime().availableProcessors();
if (props.containsKey("pool.size")){
Integer.parseInt(props.getProperty("pool.size"));
}
Reactor reactor = new Reactor(port, poolSize, protocolMaker,
tokenizerMaker);
Thread thread = new Thread(reactor);
thread.start();
logger.info("Reactor is ready on port " + reactor.getPort());
thread.join();
} catch (Exception e) {
e.printStackTrace();
}
}
}