diff --git a/reactor/src/main/java/com/iluwatar/reactor/app/App.java b/reactor/src/main/java/com/iluwatar/reactor/app/App.java
index 947173494..fcc327b34 100644
--- a/reactor/src/main/java/com/iluwatar/reactor/app/App.java
+++ b/reactor/src/main/java/com/iluwatar/reactor/app/App.java
@@ -10,20 +10,18 @@ import com.iluwatar.reactor.framework.NioServerSocketChannel;
import com.iluwatar.reactor.framework.ThreadPoolDispatcher;
/**
- * This application demonstrates Reactor pattern. The example demonstrated is a Distributed Logging Service
- * where it listens on multiple TCP or UDP sockets for incoming log requests.
+ * This application demonstrates Reactor pattern. The example demonstrated is a Distributed Logging
+ * Service where it listens on multiple TCP or UDP sockets for incoming log requests.
*
*
- * INTENT
- *
- * The Reactor design pattern handles service requests that are delivered concurrently to an
+ * INTENT
+ * The Reactor design pattern handles service requests that are delivered concurrently to an
* application by one or more clients. The application can register specific handlers for processing
* which are called by reactor on specific events.
*
*
- * PROBLEM
- *
- * Server applications in a distributed system must handle multiple clients that send them service
+ * PROBLEM
+ * Server applications in a distributed system must handle multiple clients that send them service
* requests. Following forces need to be resolved:
*
- * The application utilizes single thread to listen for requests on all ports. It does not create
- * a separate thread for each client, which provides better scalability under load (number of clients
+ * PARTICIPANTS
+ *
+ *
Synchronous Event De-multiplexer
{@link NioReactor} plays the role of synchronous event
+ * de-multiplexer. It waits for events on multiple channels registered to it in an event loop.
+ *
+ *
+ *
Initiation Dispatcher
{@link NioReactor} plays this role as the application specific
+ * {@link ChannelHandler}s are registered to the reactor.
+ *
+ *
+ *
Handle
{@link AbstractNioChannel} acts as a handle that is registered to the reactor.
+ * When any events occur on a handle, reactor calls the appropriate handler.
+ *
+ *
+ *
Event Handler
{@link ChannelHandler} acts as an event handler, which is bound to a
+ * channel and is called back when any event occurs on any of its associated handles. Application
+ * logic resides in event handlers.
+ *
+ *
+ *
+ * The application utilizes single thread to listen for requests on all ports. It does not create a
+ * separate thread for each client, which provides better scalability under load (number of clients
* increase).
*
*
@@ -45,59 +63,60 @@ import com.iluwatar.reactor.framework.ThreadPoolDispatcher;
*/
public class App {
- private NioReactor reactor;
+ private NioReactor reactor;
- /**
- * App entry.
- * @throws IOException
- */
- public static void main(String[] args) throws IOException {
- new App().start();
- }
-
- /**
- * Starts the NIO reactor.
- * @throws IOException if any channel fails to bind.
- */
- public void start() throws IOException {
- /*
- * The application can customize its event dispatching mechanism.
- */
- reactor = new NioReactor(new ThreadPoolDispatcher(2));
-
- /*
- * This represents application specific business logic that dispatcher will call
- * on appropriate events. These events are read events in our example.
- */
- LoggingHandler loggingHandler = new LoggingHandler();
-
- /*
- * Our application binds to multiple channels and uses same logging handler to handle
- * incoming log requests.
- */
- reactor
- .registerChannel(tcpChannel(6666, loggingHandler))
- .registerChannel(tcpChannel(6667, loggingHandler))
- .registerChannel(udpChannel(6668, loggingHandler))
- .start();
- }
-
- /**
- * Stops the NIO reactor. This is a blocking call.
- */
- public void stop() {
- reactor.stop();
- }
+ /**
+ * App entry.
+ *
+ * @throws IOException
+ */
+ public static void main(String[] args) throws IOException {
+ new App().start();
+ }
- private static AbstractNioChannel tcpChannel(int port, ChannelHandler handler) throws IOException {
- NioServerSocketChannel channel = new NioServerSocketChannel(port, handler);
- channel.bind();
- return channel;
- }
-
- private static AbstractNioChannel udpChannel(int port, ChannelHandler handler) throws IOException {
- NioDatagramChannel channel = new NioDatagramChannel(port, handler);
- channel.bind();
- return channel;
- }
+ /**
+ * Starts the NIO reactor.
+ *
+ * @throws IOException if any channel fails to bind.
+ */
+ public void start() throws IOException {
+ /*
+ * The application can customize its event dispatching mechanism.
+ */
+ reactor = new NioReactor(new ThreadPoolDispatcher(2));
+
+ /*
+ * This represents application specific business logic that dispatcher will call on appropriate
+ * events. These events are read events in our example.
+ */
+ LoggingHandler loggingHandler = new LoggingHandler();
+
+ /*
+ * Our application binds to multiple channels and uses same logging handler to handle incoming
+ * log requests.
+ */
+ reactor.registerChannel(tcpChannel(6666, loggingHandler)).registerChannel(tcpChannel(6667, loggingHandler))
+ .registerChannel(udpChannel(6668, loggingHandler)).start();
+ }
+
+ /**
+ * Stops the NIO reactor. This is a blocking call.
+ *
+ * @throws InterruptedException if interrupted while stopping the reactor.
+ */
+ public void stop() throws InterruptedException {
+ reactor.stop();
+ }
+
+ private static AbstractNioChannel tcpChannel(int port, ChannelHandler handler) throws IOException {
+ NioServerSocketChannel channel = new NioServerSocketChannel(port, handler);
+ channel.bind();
+ return channel;
+ }
+
+ private static AbstractNioChannel udpChannel(int port, ChannelHandler handler) throws IOException {
+ NioDatagramChannel channel = new NioDatagramChannel(port, handler);
+ channel.bind();
+ return channel;
+ }
}
diff --git a/reactor/src/main/java/com/iluwatar/reactor/app/AppClient.java b/reactor/src/main/java/com/iluwatar/reactor/app/AppClient.java
index 033711569..c50e4d3e7 100644
--- a/reactor/src/main/java/com/iluwatar/reactor/app/AppClient.java
+++ b/reactor/src/main/java/com/iluwatar/reactor/app/AppClient.java
@@ -17,148 +17,149 @@ import java.util.concurrent.TimeUnit;
/**
* Represents the clients of Reactor pattern. Multiple clients are run concurrently and send logging
* requests to Reactor.
- *
+ *
* @author npathai
*/
public class AppClient {
- private ExecutorService service = Executors.newFixedThreadPool(4);
+ private final ExecutorService service = Executors.newFixedThreadPool(4);
- /**
- * App client entry.
- * @throws IOException if any I/O error occurs.
- */
- public static void main(String[] args) throws IOException {
- AppClient appClient = new AppClient();
- appClient.start();
- }
+ /**
+ * App client entry.
+ *
+ * @throws IOException if any I/O error occurs.
+ */
+ public static void main(String[] args) throws IOException {
+ AppClient appClient = new AppClient();
+ appClient.start();
+ }
- /**
- * Starts the logging clients.
- * @throws IOException if any I/O error occurs.
- */
- public void start() throws IOException {
- service.execute(new TCPLoggingClient("Client 1", 6666));
- service.execute(new TCPLoggingClient("Client 2", 6667));
- service.execute(new UDPLoggingClient("Client 3", 6668));
- service.execute(new UDPLoggingClient("Client 4", 6668));
- }
+ /**
+ * Starts the logging clients.
+ *
+ * @throws IOException if any I/O error occurs.
+ */
+ public void start() throws IOException {
+ service.execute(new TCPLoggingClient("Client 1", 6666));
+ service.execute(new TCPLoggingClient("Client 2", 6667));
+ service.execute(new UDPLoggingClient("Client 3", 6668));
+ service.execute(new UDPLoggingClient("Client 4", 6668));
+ }
- /**
- * Stops logging clients. This is a blocking call.
- */
- public void stop() {
- service.shutdown();
- if (!service.isTerminated()) {
- service.shutdownNow();
- try {
- service.awaitTermination(1000, TimeUnit.SECONDS);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
-
- private static void artificialDelayOf(long millis) {
- try {
- Thread.sleep(millis);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
+ /**
+ * Stops logging clients. This is a blocking call.
+ */
+ public void stop() {
+ service.shutdown();
+ if (!service.isTerminated()) {
+ service.shutdownNow();
+ try {
+ service.awaitTermination(1000, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
- /**
- * A logging client that sends requests to Reactor on TCP socket.
- */
- static class TCPLoggingClient implements Runnable {
+ private static void artificialDelayOf(long millis) {
+ try {
+ Thread.sleep(millis);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
- private int serverPort;
- private String clientName;
+ /**
+ * A logging client that sends requests to Reactor on TCP socket.
+ */
+ static class TCPLoggingClient implements Runnable {
- /**
- * Creates a new TCP logging client.
- *
- * @param clientName the name of the client to be sent in logging requests.
- * @param port the port on which client will send logging requests.
- */
- public TCPLoggingClient(String clientName, int serverPort) {
- this.clientName = clientName;
- this.serverPort = serverPort;
- }
+ private final int serverPort;
+ private final String clientName;
- public void run() {
- try (Socket socket = new Socket(InetAddress.getLocalHost(), serverPort)) {
- OutputStream outputStream = socket.getOutputStream();
- PrintWriter writer = new PrintWriter(outputStream);
- sendLogRequests(writer, socket.getInputStream());
- } catch (IOException e) {
- e.printStackTrace();
- throw new RuntimeException(e);
- }
- }
+ /**
+ * Creates a new TCP logging client.
+ *
+ * @param clientName the name of the client to be sent in logging requests.
+ * @param port the port on which client will send logging requests.
+ */
+ public TCPLoggingClient(String clientName, int serverPort) {
+ this.clientName = clientName;
+ this.serverPort = serverPort;
+ }
- private void sendLogRequests(PrintWriter writer, InputStream inputStream) throws IOException {
- for (int i = 0; i < 4; i++) {
- writer.println(clientName + " - Log request: " + i);
- writer.flush();
+ public void run() {
+ try (Socket socket = new Socket(InetAddress.getLocalHost(), serverPort)) {
+ OutputStream outputStream = socket.getOutputStream();
+ PrintWriter writer = new PrintWriter(outputStream);
+ sendLogRequests(writer, socket.getInputStream());
+ } catch (IOException e) {
+ e.printStackTrace();
+ throw new RuntimeException(e);
+ }
+ }
- byte[] data = new byte[1024];
- int read = inputStream.read(data, 0, data.length);
- if (read == 0) {
- System.out.println("Read zero bytes");
- } else {
- System.out.println(new String(data, 0, read));
- }
+ private void sendLogRequests(PrintWriter writer, InputStream inputStream) throws IOException {
+ for (int i = 0; i < 4; i++) {
+ writer.println(clientName + " - Log request: " + i);
+ writer.flush();
- artificialDelayOf(100);
- }
- }
+ byte[] data = new byte[1024];
+ int read = inputStream.read(data, 0, data.length);
+ if (read == 0) {
+ System.out.println("Read zero bytes");
+ } else {
+ System.out.println(new String(data, 0, read));
+ }
- }
+ artificialDelayOf(100);
+ }
+ }
- /**
- * A logging client that sends requests to Reactor on UDP socket.
- */
- static class UDPLoggingClient implements Runnable {
- private String clientName;
- private InetSocketAddress remoteAddress;
+ }
- /**
- * Creates a new UDP logging client.
- *
- * @param clientName the name of the client to be sent in logging requests.
- * @param port the port on which client will send logging requests.
- * @throws UnknownHostException if localhost is unknown
- */
- public UDPLoggingClient(String clientName, int port) throws UnknownHostException {
- this.clientName = clientName;
- this.remoteAddress = new InetSocketAddress(InetAddress.getLocalHost(), port);
- }
+ /**
+ * A logging client that sends requests to Reactor on UDP socket.
+ */
+ static class UDPLoggingClient implements Runnable {
+ private final String clientName;
+ private final InetSocketAddress remoteAddress;
- @Override
- public void run() {
- try (DatagramSocket socket = new DatagramSocket()) {
- for (int i = 0; i < 4; i++) {
-
- String message = clientName + " - Log request: " + i;
- DatagramPacket request = new DatagramPacket(message.getBytes(),
- message.getBytes().length, remoteAddress);
-
- socket.send(request);
+ /**
+ * Creates a new UDP logging client.
+ *
+ * @param clientName the name of the client to be sent in logging requests.
+ * @param port the port on which client will send logging requests.
+ * @throws UnknownHostException if localhost is unknown
+ */
+ public UDPLoggingClient(String clientName, int port) throws UnknownHostException {
+ this.clientName = clientName;
+ this.remoteAddress = new InetSocketAddress(InetAddress.getLocalHost(), port);
+ }
- byte[] data = new byte[1024];
- DatagramPacket reply = new DatagramPacket(data, data.length);
- socket.receive(reply);
- if (reply.getLength() == 0) {
- System.out.println("Read zero bytes");
- } else {
- System.out.println(new String(reply.getData(), 0, reply.getLength()));
- }
-
- artificialDelayOf(100);
- }
- } catch (IOException e1) {
- e1.printStackTrace();
- }
- }
- }
+ @Override
+ public void run() {
+ try (DatagramSocket socket = new DatagramSocket()) {
+ for (int i = 0; i < 4; i++) {
+
+ String message = clientName + " - Log request: " + i;
+ DatagramPacket request = new DatagramPacket(message.getBytes(), message.getBytes().length, remoteAddress);
+
+ socket.send(request);
+
+ byte[] data = new byte[1024];
+ DatagramPacket reply = new DatagramPacket(data, data.length);
+ socket.receive(reply);
+ if (reply.getLength() == 0) {
+ System.out.println("Read zero bytes");
+ } else {
+ System.out.println(new String(reply.getData(), 0, reply.getLength()));
+ }
+
+ artificialDelayOf(100);
+ }
+ } catch (IOException e1) {
+ e1.printStackTrace();
+ }
+ }
+ }
}
diff --git a/reactor/src/main/java/com/iluwatar/reactor/app/LoggingHandler.java b/reactor/src/main/java/com/iluwatar/reactor/app/LoggingHandler.java
index eed26b078..1f2694b0b 100644
--- a/reactor/src/main/java/com/iluwatar/reactor/app/LoggingHandler.java
+++ b/reactor/src/main/java/com/iluwatar/reactor/app/LoggingHandler.java
@@ -8,53 +8,54 @@ import com.iluwatar.reactor.framework.ChannelHandler;
import com.iluwatar.reactor.framework.NioDatagramChannel.DatagramPacket;
/**
- * Logging server application logic. It logs the incoming requests on standard console and returns
- * a canned acknowledgement back to the remote peer.
+ * Logging server application logic. It logs the incoming requests on standard console and returns a
+ * canned acknowledgement back to the remote peer.
*
* @author npathai
*/
public class LoggingHandler implements ChannelHandler {
- private static final byte[] ACK = "Data logged successfully".getBytes();
+ private static final byte[] ACK = "Data logged successfully".getBytes();
- /**
- * Decodes the received data and logs it on standard console.
- */
- @Override
- public void handleChannelRead(AbstractNioChannel channel, Object readObject, SelectionKey key) {
- /*
- * As this handler is attached with both TCP and UDP channels we need to check whether
- * the data received is a ByteBuffer (from TCP channel) or a DatagramPacket (from UDP channel).
- */
- if (readObject instanceof ByteBuffer) {
- doLogging(((ByteBuffer)readObject));
- sendReply(channel, key);
- } else if (readObject instanceof DatagramPacket) {
- DatagramPacket datagram = (DatagramPacket)readObject;
- doLogging(datagram.getData());
- sendReply(channel, datagram, key);
- } else {
- throw new IllegalStateException("Unknown data received");
- }
- }
+ /**
+ * Decodes the received data and logs it on standard console.
+ */
+ @Override
+ public void handleChannelRead(AbstractNioChannel channel, Object readObject, SelectionKey key) {
+ /*
+ * As this handler is attached with both TCP and UDP channels we need to check whether the data
+ * received is a ByteBuffer (from TCP channel) or a DatagramPacket (from UDP channel).
+ */
+ if (readObject instanceof ByteBuffer) {
+ doLogging(((ByteBuffer) readObject));
+ sendReply(channel, key);
+ } else if (readObject instanceof DatagramPacket) {
+ DatagramPacket datagram = (DatagramPacket) readObject;
+ doLogging(datagram.getData());
+ sendReply(channel, datagram, key);
+ } else {
+ throw new IllegalStateException("Unknown data received");
+ }
+ }
- private void sendReply(AbstractNioChannel channel, DatagramPacket incomingPacket, SelectionKey key) {
- /*
- * Create a reply acknowledgement datagram packet setting the receiver to the sender of incoming message.
- */
- DatagramPacket replyPacket = new DatagramPacket(ByteBuffer.wrap(ACK));
- replyPacket.setReceiver(incomingPacket.getSender());
-
- channel.write(replyPacket, key);
- }
+ private void sendReply(AbstractNioChannel channel, DatagramPacket incomingPacket, SelectionKey key) {
+ /*
+ * Create a reply acknowledgement datagram packet setting the receiver to the sender of incoming
+ * message.
+ */
+ DatagramPacket replyPacket = new DatagramPacket(ByteBuffer.wrap(ACK));
+ replyPacket.setReceiver(incomingPacket.getSender());
- private void sendReply(AbstractNioChannel channel, SelectionKey key) {
- ByteBuffer buffer = ByteBuffer.wrap(ACK);
- channel.write(buffer, key);
- }
+ channel.write(replyPacket, key);
+ }
- private void doLogging(ByteBuffer data) {
- // assuming UTF-8 :(
- System.out.println(new String(data.array(), 0, data.limit()));
- }
+ private void sendReply(AbstractNioChannel channel, SelectionKey key) {
+ ByteBuffer buffer = ByteBuffer.wrap(ACK);
+ channel.write(buffer, key);
+ }
+
+ private void doLogging(ByteBuffer data) {
+ // assuming UTF-8 :(
+ System.out.println(new String(data.array(), 0, data.limit()));
+ }
}
diff --git a/reactor/src/main/java/com/iluwatar/reactor/framework/AbstractNioChannel.java b/reactor/src/main/java/com/iluwatar/reactor/framework/AbstractNioChannel.java
index 24862644d..09f308731 100644
--- a/reactor/src/main/java/com/iluwatar/reactor/framework/AbstractNioChannel.java
+++ b/reactor/src/main/java/com/iluwatar/reactor/framework/AbstractNioChannel.java
@@ -10,143 +10,145 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
- * This represents the Handle of Reactor pattern. These are resources managed by OS
- * which can be submitted to {@link NioReactor}.
+ * This represents the Handle of Reactor pattern. These are resources managed by OS which can
+ * be submitted to {@link NioReactor}.
*
*
- * This class serves has the responsibility of reading the data when a read event occurs and
- * writing the data back when the channel is writable. It leaves the reading and writing of
- * data on the concrete implementation. It provides a block writing mechanism wherein when
- * any {@link ChannelHandler} wants to write data back, it queues the data in pending write queue
- * and clears it in block manner. This provides better throughput.
- *
+ * This class serves has the responsibility of reading the data when a read event occurs and writing
+ * the data back when the channel is writable. It leaves the reading and writing of data on the
+ * concrete implementation. It provides a block writing mechanism wherein when any
+ * {@link ChannelHandler} wants to write data back, it queues the data in pending write queue and
+ * clears it in block manner. This provides better throughput.
+ *
* @author npathai
*
*/
public abstract class AbstractNioChannel {
-
- private SelectableChannel channel;
- private ChannelHandler handler;
- private Map> channelToPendingWrites = new ConcurrentHashMap<>();
- private NioReactor reactor;
-
- /**
- * Creates a new channel.
- * @param handler which will handle events occurring on this channel.
- * @param channel a NIO channel to be wrapped.
- */
- public AbstractNioChannel(ChannelHandler handler, SelectableChannel channel) {
- this.handler = handler;
- this.channel = channel;
- }
-
- /**
- * Injects the reactor in this channel.
- */
- void setReactor(NioReactor reactor) {
- this.reactor = reactor;
- }
- /**
- * @return the wrapped NIO channel.
- */
- public SelectableChannel getChannel() {
- return channel;
- }
+ private final SelectableChannel channel;
+ private final ChannelHandler handler;
+ private final Map> channelToPendingWrites = new ConcurrentHashMap<>();
+ private NioReactor reactor;
- /**
- * The operation in which the channel is interested, this operation is provided to {@link Selector}.
- *
- * @return interested operation.
- * @see SelectionKey
- */
- public abstract int getInterestedOps();
-
- /**
- * Binds the channel on provided port.
- *
- * @throws IOException if any I/O error occurs.
- */
- public abstract void bind() throws IOException;
-
- /**
- * Reads the data using the key and returns the read data. The underlying channel should be fetched using
- * {@link SelectionKey#channel()}.
- *
- * @param key the key on which read event occurred.
- * @return data read.
- * @throws IOException if any I/O error occurs.
- */
- public abstract Object read(SelectionKey key) throws IOException;
+ /**
+ * Creates a new channel.
+ *
+ * @param handler which will handle events occurring on this channel.
+ * @param channel a NIO channel to be wrapped.
+ */
+ public AbstractNioChannel(ChannelHandler handler, SelectableChannel channel) {
+ this.handler = handler;
+ this.channel = channel;
+ }
- /**
- * @return the handler associated with this channel.
- */
- public ChannelHandler getHandler() {
- return handler;
- }
+ /**
+ * Injects the reactor in this channel.
+ */
+ void setReactor(NioReactor reactor) {
+ this.reactor = reactor;
+ }
- /*
- * Called from the context of reactor thread when the key becomes writable.
- * The channel writes the whole pending block of data at once.
- */
- void flush(SelectionKey key) throws IOException {
- Queue