This homework is designed for you to learn multi-threaded client-server programming in Java using TCP/IP. In this assignment, you will be dealing with most aspects of designing a full-fledged multi-threaded server. As such, this assignment is likely to be complex and require you to understand threads and sockets very well. Nevertheless, I think you will find the additional effort to be fun and well worth the effort.
A server is a program that can compute and render some pictures on a window, where the nature of the picture is given by a set of parameters. A client is a program that can establish a connection with the server and request initiation of one such computation by specifying the values of the parameters. The server may be performing computations on behalf of more than one client, and each such computation will be performed in a separate thread. The exact nature of the computation and the picture rendered is up to you, but we have provided some suggestions for some easy-to-generate pictures that may also look interesting, depending on the values of the parameters.
The server program is initially started. It sets up a TCP server socket and blocks, waiting for incoming connections. (You need to choose an appropriate port number for the server socket -- say 4023.) A client connects to the server, and sends a message
initiate X Y w other-paramswhere the meaning of other-params depends on the computation and rendering task performed by the server.
X and
Y provide the coordinates where the
computation is to be started. The nature of the computation must be such
that it starts rendering at (X,Y) on the screen, and as the computation
progresses, the rendering moves away from this point. The w
parameter
determines how the screen is overwritten by the computation. In
particular, assuming a pixel model for the screen, let v_o be the old
value of a pixel, while the current computation computes a new value
v_c.
Then the new value of the pixel is given by w*v_c + (1-w)*v_o. The
parameter w is constrained to be in the range 0 <= w <= 1.
In order to wait for incoming connections, the server program invokes
the
accept method (look at the
java.net package, especially the
ServerSocket
and SocketImpl classes),
which blocks until a client connects to the
server. When a client makes a connection, the accept
method returns with
a new Socket object.
This new socket object is to be used for reading the
information sent by the client. NOTE that the original socket on which
the
accept method is invoked is not the same as the socket returned by the
accept method. That original socket is set up only to accept
connections.
Being a multi-threaded server, you need to ensure that your program
always
has at least one thread that is able to accept incoming requests. For
instance, as soon as an accept method returns, you may spawn a new
thread
that performs another accept. Alternatively, the new thread spawned can
serve the request, while the parent thread performs another accept. In
either case, it is the responsibility of the server to respond to the
client with an integer identifier for the newly initiated computation,
which will be used by the client to perform additional operations on
this
computation.
Note that since multiple computations can be taking place simultaneously within the server, multiple threads can access and update the screen simultaneously. To ensure correct results, you need to ensure mutual exclusion of these threads when they are in critical sections. For instance, you will use a synchronized method for updating a pixel, which will ensure that the reading of v_o and updating with v_n are done atomically.
Once a computation is started, a client can close down the connection at that point. Alternatively, it may send additional commands to the server to initiate additional computations, or to kill an existing computation, or to request notification under certain conditions. The two new commands have the following forms:
stop computationId notify computationId distanceThe first command is used to terminate an existing computation within the server by providing the id of the computation. (Note: a ``computationId'' is used here, because a single client can send multiple ``initiate'' messages to the server on a single connection.) To implement this function in the server, there are two possibilities. In one case, you may use a polling method within the server, wherein the thread perfoming the computation is periodically polling its associated socket to see if the client has requested additional operations to be performed, and perform them as required. The polling mechanism, which is a synchronous mechanism, is simpler to implement, but has the overhead of checking constantly for input. It also gets complicated if the same connection is being used by a client to initiate multiple computations within the server. An alternative approach is to rely on asynchronous mechanisms for communication among threads. In this case, the thread performing the computation is off on its own, and does not check the socket. A separate thread awaits additional messages on the socket. It then communicates with the thread performing the computation using asynchronous mechanisms such as a thread interrupt.
The notify command is used by a client to request that it be notified when the computation reaches a point that is distance pixels away from the point where the computation is started. Implementation of notification also requires inter-thread communication on the server side. The server will send a
notification computationIdmessage to the client when the notification condition is met.
Finally, in a server, you are always concerned about resource usage, and in particular, in ensuring that resources are not being wasted. For this purpose, you will terminate any computation as soon as the client that requests the computation disconnects from the server either normally or abnormally. These conditions will typically lead to exceptions on the server side, and you will implement handlers for these exceptions to deal with these conditions. You will also set an upper limit for the time spent on any computation. After this time, the computation is automatically killed, and the server disconnects from the client that requested the computation. Finally, limit the number of active connections to some fixed number k (choose a number around 10) and active computations to a number j > k. Additional client requests will be accepted, but the client will be sent a negative computation id, which indicates that the computation could not be started on the server.
In the simplest case, the server program draws ellipses starting with both axes being of a random size, and the color of the ellipse being a random value. Successive axes and color of the successive ellipses drawn will change gradually, and the center of the ellipse moves away from the origin along some trajectory. (Use a high-degree polynomial or a spiral for the trajectory so as to get interesting patterns.) It is important for the change in size, color and center of the ellipse to change very slowly, so that the picture looks like an animation of an ellipse that wobbles, changes color gradually and moves away.
In The Java Programming Language by Arnold and Gosling (Addison-Wesley, 1996) read chapter 9 for information on threads. Or in Just Java by van der Linden (Prentice-Hall, 1997), read the section on threads in chapter 5 (pages 179-205), and chapter 8, especially pages 309-328; you might also want to skim chapter 11. For reference, you may want to refer to chapter 17 of The Java Language Specification by Gosling, Joy, and Steele (Addison-Wesley, 1996).
A description of the Networking Enhancements in JDK 1.1 comes with the JDK 1.1 documentation. This includes information on Extending Sockets in JDK 1.1.
You might also look at the Threads of Control chapter in The Java Tutorial Object-Oriented Programming for the Internet by Mary Campione and Kathy Walrath (Addison-Wesley, 1996).
If this subject interests you, you might want to get the book Concurrent Programming in Java: Design Principles and Patterns by Doug Lea (Addison-Wesley, 1997).
This homework, like the others in this seminar, will be open-ended and somewhat ill-defined. we'll try to give you some idea of the difficulty/effort involved in each part. Do what you have time for.
Bring a printout of your code to class, and be prepared to discuss it with other people in the class.