Assignment 2 — Reactor pattern
In this assignment, you will implement a pattern commonly used in a wide range of concurrent programs including servers and graphics libraries and apply it to a simple client-server application. In the following, "should" is used to denote a weak requirement; something characteristic of a good solution. "must" and "may not" denote strong requirements; failure to achieve these in your own code may cause your assignment to be failed; conversely, you may assume that all requirements on other code marked using these always hold.
Background
As a fan of the game Hangman, you want to create a networked version of the game in which multiple players can co-operate in guessing a word. Also being a fan of design patterns, you decide to use the Reactor design pattern (PDF) for the game server. Basically, the Reactor pattern consists of a (Initiation) Dispatcher that dispatches events received synchronously through a (Synchronous Event) Demultiplexer from Handles to Event Handlers. Events are dispatched by calling the Event Handlers sequentially.
Task description
Your task is to implement the Reactor pattern and a simple multiplayer Hangman server. These are two separate tasks; your Hangman implementation must work with any compliant Reactor implementation and vice versa.
Reactor
In this assignment, you must implement a simplified Reactor pattern
as described in this section. Each handle has exactly one event
handler, to which events received by the handle are dispatched
(passed) when the handle is registered to a dispatcher through its
corresponding event handler. Events are passed to event handlers
through method calls made when
Dispatcher.handleEvents()
is called. Events are not
divided into different types (e.g. accept, input, output, close); a
registered event handler must receive every message received by its
handle (in the order they are received by the handle) while it is
registered to a Dispatcher. Your Reactor implementation must
conform to the following specification (Java source code):
All the classes below are in package reactor
and the
interfaces in reactorapi
.
synchronized
may be added to method
declarations as needed.
-
public class Dispatcher
-
Implements the Dispatcher part of the Reactor pattern. All
method calls to a
Dispatcher
must be made from the same thread. -
public Dispatcher()
-
Create a new
Dispatcher
with no registeredHandler
s (differentDispatcher
s are completely separate entities).
-
Create a new
-
public void handleEvents() throws InterruptedException
-
Repeatedly waits until an event is received (an object
o
returned by aHandle.read()
on a registered handle) and dispatches it (by callinghandleEvent(o)
on the handler whose handle received the event). If interrupted byThread.interrupt()
, this method may throw anInterruptedException
. This method must wait until a message is received (on any registered handle). As soon as a new event is available, the event must be dispatched to the correspondingEventHandler
. Polling or busy-waiting for events is not allowed. Events must not be read by the Reactor from a handle before registration (although the application using it may do so).handleEvents()
returns only when there are no registered handlers.
-
Repeatedly waits until an event is received (an object
-
public void addHandler(EventHandler h)
-
Register an unregistered handler; i.e. start dispatching
incoming events for
h
. All events received on the corresponding handle (h.getHandle()
) must be (eventually) dispatched toh
, untilremoveHandler(h)
is called or anull
message is received.
-
Register an unregistered handler; i.e. start dispatching
incoming events for
-
public void removeHandler(EventHandler h)
-
Deregister a registered handler; i.e. stop dispatching
incoming events for
h
. AfterremoveHandler(h)
is called, no further events may be dispatched toh
.
-
Deregister a registered handler; i.e. stop dispatching
incoming events for
-
Implements the Dispatcher part of the Reactor pattern. All
method calls to a
-
public interface EventHandler
-
Handles events from its
Handle
. Each handler may be registered and deregistered only once and must be deregistered before program termination if registered. -
public Handle getHandle()
-
Get the
Handle
from which theEventHandler
receives events (for any givenEventHandler
, the sameHandle
must be returned every time; in other words, anEventHandler
may only listen to oneHandle
).
-
Get the
-
public void handleEvent(Object s)
-
Handle an incoming message/event represented by an object
s
, as received from theHandle
. Messages are dispatched by calling this method.
-
Handle an incoming message/event represented by an object
-
Handles events from its
-
public interface Handle
- Represents one end of a (possibly bidirectional) communications channel. Corresponds to e.g. a file handle or a network socket.
-
public Object read()
-
Wait for a message (which may be any reference including
null
) to be received from the channel and return it. These received messages are the events that are dispatched by the Reactor.Handle
implementations may also return objects representing error conditions where applicable; all errors (includingnull
) must be returned by theHandle
as objects and passed by the Reactor to the application unchanged. Further, anull
message indicates that the Handle has been closed or has a fatal error and may no longer beread()
.
-
Wait for a message (which may be any reference including
Note that the above Handle
and
EventHandler
interfaces are intended to be implemented
by the application that uses the Reactor pattern. In other words,
your Reactor implementation should not contain any implementations
of these interfaces; instead it must behave as specified above when
any Handle
and EventHandler
implementations compliant with the above specification (such as
those in your Hangman implementation) are used with your code. In
particular, this means that your Reactor implementation may not
require any additional Handle
methods.
Closing Handle
s, cleaning up the resources associated
to them and terminating pending Handle.read()
operations is the responsibility of the application using the
Reactor (in practice, the responsibility of the Handle
itself).
The application using the Reactor (including handlers called by the Dispatcher) is single-threaded.
Hangman application
The Hangman application must consist of a server implementation based on the Reactor pattern. The server communicates through TCP with its clients and uses the Reactor from the previous task to handle all incoming network communication.
The server is given the word to be guessed on startup and the number of incorrect guesses needed to lose. Initially, all letters in the word are shown to the players as dashes. The players guess one letter at a time. If the word to be guessed does not contain the guessed letter (irrespective of whether it has been guessed previously), the amount of remaining incorrect guesses is decremented; if it has reached zero, the game ends. If all letters in the word have been guessed, the game also ends. Each time a player guesses a letter, the server sends each player a message describing the state of the guessing so far; this message contains the word to be guessed with all correctly guessed letters exposed. Guessing is case sensitive. Players may connect and disconnect at any time.
Players share guesses. The server must terminate (closing all network connections) when the game ends, immediately after the message describing the last guess has been sent to all players.
The Hangman server must communicate with its clients using plain text (you may assume the clients use the server's character set) sent through TCP connections. The server must listen for incoming connections (until the game ends) on a port chosen at server startup (see below). Each connection corresponds to a player; the player opens a connection to the server when he wants to join and may close it at any time. The first line sent by the client is a single line containing the player's name (one field, 1 or more alphabetic characters), and in response, it receives the line:
<guessed word> <number of remaining tries>
Every time a guess is made, the server sends to all clients a line of the form:
<guessed letter> <guessed word>
<remaining tries> <name of last guesser>
The guessed word is the word to be guessed with unknown characters
marked by dashes ("-
"). Each response is a single line
with 2 or 4 fields (as above) separated by (1 or more) spaces
(leading/trailing spaces may exist). Lines are always terminated as
in the server's native encoding. You may assume this encoding is a
superset of ASCII and that all inputs are valid ASCII.
Clients send guesses (at any time) as single lines containing a single lower-case alphabetic character through the TCP connection (any extraneous characters and newlines should be ignored).
Example session:
I/O for User Alice (Alice's input in bold):
Alice ----------- 10 z z ----------- 9 Alice e e -------e--- 9 Alice o -o-----e--- 9 Bob n n -on----en-- 9 Alice r r -on--rren-- 9 Alice y -on--rren-y 9 Bob i -on--rren-y 8 Bob c conc-rrency 8 Bob u u concurrency 8 Alice
I/O for user Bob (Bob's input in bold):
Bob -------e--- 9 o o -o-----e--- 9 Bob n -on----en-- 9 Alice r -on--rren-- 9 Alice y y -on--rren-y 9 Bob i i -on--rren-y 8 Bob c c conc-rrency 8 Bob u concurrency 8 Alice
In this example, Bob connects after Alice has made her first two guesses.
Execution
The Hangman server must start by the command (assuming Java is in the path):
java hangman.HangmanServer <word to guess> <number of failed attempts before termination>
The Hangman server must print the number of the TCP port it is listening on on a single line on standard output (leading and trailing spaces are allowed) when it can accept connections (and nothing else if everything works correctly). The word to guess consists of 1 or more lower-case alphabetic characters.
Concurrency and synchronisation requirements
Synchronisation must be done solely using the basic Java
synchronisation primitives (those built into the language and
java.lang
, not
java.util.concurrent
). Inefficient solutions, such as
polling and busy-waiting, will be rejected. Use of unsafe methods
such as java.lang.Thread.destroy()
will also lead to
rejection.
The Hangman server may only (directly) use one thread (the
main
thread); any other threads must be created and
managed by the Reactor. Similarly, the Hangman server may not use
any synchronisation mechanisms directly.
Error handling
Handling errors correctly is considered an important part of proper
programming style. In particular, programs that handle network
connections and other forms of I/O must be prepared to handle cases
where an I/O channel fails and closes without warning. The Reactor
implementation described above does not specify the mechanism used
to inform EventHandler
s of errors in a
Handle
.
The Reactor and Hangman implementations are not required to perform input format checking and are allowed to malfunction if passed incorrect data (e.g. upper-case guesses). However, detecting and recovering from at least the most probable user errors may be useful for testing.
Style requirements
The program should be written in clear, reasonably object-oriented Java. We will not accept programs that are hard to read, violate basic object-oriented programming principles or contain a large amount of repeated code with minor variations. Cryptic method and variable names should not be used.
Explanatory comments are required for code that is not self-explanatory to a programmer fluent in Java. However, you should not clutter your program by stating the obvious. As an example, Javadoc comments describing the syntax of trivial methods (such as accessors) will be tolerated but are discouraged.
Language
Comments, variable and method names and reports may be written in English, Swedish or Finnish. We recommend that you use only English; if you use Finnish or Swedish, you must specify this when submitting.
Report
Your submission must contain a report describing briefly:
- The reasoning behind your solution, in particular how your reactor implements serialisation of events.
- What measures you have taken to ensure your solution is correct (e.g. model checking, proofs, testing).
- Any unexpected behaviour you may have encountered during testing and how you have investigated and resolved it.
The report should not exceed 2 A4 pages of 10-12 point text. Please focus on explaining your reasoning rather than what your solution does in detail.
Hints
-
telnet
andnetcat
can be used as clients for Hangman servers. - If your program gets stuck, you can get a dump of the current position of all the threads by sending SIGQUIT to the Java process (usually Ctrl+\ in Unix and Ctrl+Break in Windows).
-
You can use
System.err
for debug output. You will be sent the debug output from the tests run on your solution (up to 10 MB). - The server's text formatting conventions are used to remove the necessity to keep track of character encoding and suchlike; in other words, don't worry about converting.
-
You will find the
java.net.ServerSocket
andjava.net.Socket
classes very useful. -
The following code fragment may be useful when using sockets:
try { in=new BufferedReader(new InputStreamReader( socket.getInputStream())); out=new PrintStream(socket.getOutputStream()); } catch(Exception e) { throw new RuntimeException("Internal socket error"); }
You may also use these exampleHandle
s. - This simple Reactor use example can be used to check that your Reactor implementation is at least somewhat compliant.
- Questions and requests for clarification should be sent to the newsgroup opinnot.tik.rinnakkaisohjelmointi; questions sent there will probably be answered much quicker than if they are sent to a specific assistant.
Submission
Before the deadline (2007-11-12 23:59, resubmission 2008-01-25 23:59), you must submit a gzipped tar archive containing your solution using the form below. The archive must contain the following files (all other files will be ignored):
reactor/ |
The Java source code (version 1.4 or earlier) of your Reactor
implementation (the directory corresponds to the
reactor package).
|
hangman/ |
The Java source code (version 1.4 or earlier) of your Hangman
implementation (the directory corresponds to the
hangman package).
|
report.pdf
|
The report describing your solution (as PDF). |
Your program will be compiled and run with the standard library and testing code included in the classpath.
You may work in pairs. The grading does not depend on whether you are working alone or in a pair.
Results will be sent to <student
number>@students.hut.fi
.
Page last updated by Jan Lönnberg 2008-01-11.