In this project, you will write a distributed chat room. You will use a concurrent publisher/subscriber implementation discussed in class. We have provided you with an detailed specification for this project.
You will implement the remoting (marshalling and unmarshalling) required for a distributed application such as this one. You will develop a greater understanding for the needs of distributed applications and for the design decisions behind distributed frameworks like Java RMI. For example, you will see why Java RMI intrinsically employs threads.
This project can be conceptually divided into two parts. The first is a local chat room described by the Server and Client interfaces and by the ServerConnection class. Here is a diagram depicting this scenario.
The second is the "final" distributed chat room described by the RemoteServer, RemoteClient, and RemoteServerConnection supertypes, respectively. Here is a diagram depicting this scenario.
You should start with the local chat room, which involves implementing ServerImpl (see next section), and then proceed to the implementing the distributed chat room. The top-level cmsc433.distributed package is just the local application, and the cmsc433.distributed.remote package contains the distributed code. However, the distributed code relies on the local code because you will in essence be writing code to do remoting of calls to a Server, Client, and ServerConnection.
A client first invokes register on a Server, which returns a ServerConnection. Thereafter, all communication from the client goes through the ServerConnection. A ServerConnection can be thought of as a client's personal view of the server. Each client that registers with the server has its own unique ServerConnection. The server handles communication between clients.
There are two types of messages:
| Type | Package | Description |
|---|---|---|
| To Server | cmsc433.distributed.msgToServer | Messages sent from clients to server. |
| To Client | cmsc433.distributed.msgToClient | Messages sent from server to clients. |
You can look at the API for details on these messages and how they are used throughout the application.
In a distributed setting, you have classes that handle marshalling (stub classes) and unmarshalling (skeleton classes) of method invocations. This means that the "local" Client, Server, and ServerConnection objects still exist, but are wrapped in a way that they can be used in a distributed environment. Any class wishing to communicate with a remote version of these objects must use the stub class. Any class wishing to be access remotely must wrap itself with a skeleton class. A skeleton class has a thread that is always running and accepting remote method invocation requests for the object it is remoting. The stub and skeleton classes communicate via object streams (see the Java API for ObjectInputStream and ObjectOutputStream.) You should layer your object streams over socket communication. Port numbers must be decided between the client and server beforehand.
Under a fully distributed environment, first ServerSkel is started and listens on a specific port number. Clients are aware of this port number (through some mechanism not spelled out). Then, a client constructs a ServerStub object "bound" to that port number and feeds that ServerStub object to the SwingClient constructor. The SwingClient calls register on the ServerStub and receives back a ServerConnectionStub and uses that ServerConnectionStub to send messages to the server. Along the way a ClientSkel and ServerConnectionSkel must have been started, and also a ClientStub must have been created so the server can communicate with the SwingClient (via the ClientSkel, of course.) So, just like in the local scenario, there is a Server object, Client objects, and ServerConnection objects (one per Client); however, communication from the client to the server goes through the ServerStub to the ServerSkel, communication from the client to the ServerConnection foes through the ServerConnectionStub to the ServerConnectionSkel, and communication from the server to the client goes through the ClientStub to the ClientSkel.
Remember that your implementation should work remotely. Also, your code for doing remoting of a server should work with any server. In other words, your code will need to interoperate with my implementation.
There is a thorny issue regarding how to handle exceptions and I/O errors. If a client logs out normally, its associated socket connections are close and its associated threads are stopped. However, if a skeleton thread has an I/O error, it should still close its sockets. In general, if a client receives an I/O error, communication with that ServerConnection is no longer possible. In such a case, you should just perform cleanup -- i.e., no associated threads or sockets should remain running or open, respectively. Similarly for the server, if a client gives an I/O error, assume the client is gone and perform cleanup, taking care to update any data structures to a consistent state.
We have provided you with implementations of all the messages you will need, as well as a Swing-based GUI client. Note: Since you will be running in a distributed environment, make sure your code is thread-safe.
You will create (at least) the following four classes under cmsc433.distributed.remote:
ClientStub
The stub for a remote Client. A server uses the ClientStub to communicate with the Client skeleton.The RemoteClient interface lists only one method -- namely receive. Therefore, ClientSkel will only be handling this one method. The ClientStub's receive method can send out the message it receives as its parameter on an Object outputstream to the ClientSkel.
In the case an Exception is thrown, you should perform cleanup. I.e., you should make certain this client is no longer registered with the server and you should close any open streams.
ClientSkel
The skeleton for a remote Client. Servers communicate with the skeleton via the ClientStub. It is intended to be run as a thread.ClientSkel wraps a "real" Client and, along with ClientStub, allows remote access to that client. ClientSkel should delegate calls to receive to the actual client object it is wrapping. To do this, ClientSkel will read objects off an Object inputstream. These objects will be messages to be received by the client being wrapped. ClientSkel will call receive on the "real" client, with that message as the argument. Then ClientSkel should again look for an object to read off the inputstream.
The ClientSkel thread stops running when either an Exception has occurred or a LoggedOut Message was sent by the server to the client being wrapped. If an Exception occurs, you should "manually" send ExceptionMsg and LoggedOut messages to the client being wrapped. In all cases, be sure to close any streams before the thread finishes.
ServerConnectionStub
The stub for a remote ServerConnection. A client uses a ServerConnectionStub to communicate with the ServerConnnection skeleton. Clients get a ServerConnectionStub object by successfully calling register on a ServerStub.The RemoteServerConnection interface lists only one method -- namely receive. Therefore, ServerConnectionSkel will only be handling this one method. The ServerConnectionStub's receive method can send out the message it receives as its parameter on an Object outputstream to the ServerConnectionSkel.
In the case an Exception is thrown, you should perform cleanup. I.e., you should send ExceptionMsg and LoggedOut messages to the client and you should close any open streams.
ServerConnectionSkel
The skeleton for a remote ServerConnection. Clients communicate with the skeleton via the ServerConnectionStub. It is intended to be run as a thread.ServerConnectionSkel wraps a "real" ServerConnection and, along with ServerConnectionStub, allows remote access to that server connection. ServerConnectionSkel should delegate calls to receive to the actual server connection object it is wrapping. To do this, ServerConnectionSkel will read objects off an Object inputstream. These objects will be messages to be received by the server connection being wrapped. ServerConnectionSkel will call receive on the "real" server connection, with that message as the argument. Then ServerConnectionSkel should again look for an object to read off the inputstream.
The ServerConnectionSkel thread stops running when either an Exception has occurred or a LogOut Message was sent by the client to the server connection being wrapped. In all cases, be sure to close any streams before the thread finishes.
Why not unregister the associated client from the server? It's not necessary to do this explicitly. This will automatically happen when the server tries to send the next message to that client and fails (in ClientStub.)
Eclipse can run and manage multiple VMs. To test your chat room in a distributed setting, you can run ServerSkel and then run StartRemoteClient. The Console view under Eclipse will allow you to switch between both VMs.
ServerSkel and StartRemoteClient both accept port numbers as command-line arguments. Additionally, StartRemoteClient accepts a hostname as a second command-line argument. If you are running on a shared machine (e.g., a linuxlab server, avoid conflicts with other users, use port 433XX where XX is last two digits of your class account login. You can change the default port in ServerSkel and StartRemoteClient, or you can specify the port as a command-line argument.
Once you are confident everything works, you should test with the ServerSkel and the remote clients all running on different machines.