Helsinki University of Technology

T-106.5600 Concurrent Programming (5 cr) L

Autumn 2007, periods I-II

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.

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 Handles, 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 EventHandlers 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 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

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.

Student number 1
Student number 2
File
Language English
Finnish
Swedish
Hours of work required to complete the assignment (total for both students)

Page last updated by Jan Lönnberg 2008-01-11.