Networking our whiteboard with Java 1.1

Find out how easy it is to network our whiteboard with servers, sockets, and RMI

Our world grows smaller with each passing day. Corporate alliances span the globe, and, as businesses move into increasingly remote locations, a growing number of working stiffs commute via telnet. In the past, coordinating the activities of such globally de-centralized communities was a challenge to say the least, but with the advent of the Internet and the opportunities it's given rise to, all that has changed. With utilities like our whiteboard (fully ramped-up with the networking portion of the app), interaction among users in different cities (or even different countries) is a snap.

We developed the graphical aspects of the whiteboard last month (shown below), so now we're ready to add the networking features to the application. We are going to accomplish this by networking the ObservableList class. As we discussed last month, this class creates a list of the displayed elements of the whiteboard. If you need a refresher, take this opportunity to reread part 1.

The graphical whiteboard

Our whiteboard tools manipulate the whiteboard by altering the list of elements. When changes are made to the list, all interested listeners are notified. Listeners, such as the display area, can then automatically update themselves to reflect these changes.

To enable networking, we will simply replace ObservableList with a distributed ObservableList, which ensures changes are propagated over the Internet. The distributed list presents exactly the same API as the original local list and so requires no changes to (or knowledge of) any parts of the whiteboard.

In the course of this article, we will look at two different approaches to networking -- socket-based and RMI-based -- and examine the pros and cons of each approach.

The new list class

Our first step is to implement a new list class called an

IDList

. This class is essentially a combination of the traditional

Hashtable

and

Vector

classes; it is an ordered list (

Vector

) of identifier-element pairs (

Hashtable

). As objects are added to the

IDList

, they are automatically assigned an associated identifier. The list can then be manipulated by addressing the elements by their identifiers (instead of the traditional mode of addressing the elements by their index number, which can change if any element is removed or inserted).

The IDList class

When we network the IDList class, the element identifiers will be distributed to all connected whiteboards. The whiteboards can then communicate coherently about elements of the list through the use of their identifiers.

Class IDList

The interface that class IDList presents is fairly rudimentary; however, it is sufficient to serve our purposes. Let's quickly review the methods available to this class:

  • public Object addElement (Object element)

    This method adds an element to the list, assigns it a VM-unique identifier (based on the VM hashcode of the element) and returns this identifier.

    The effects of replaceElement()
  • public Object replaceElement (Object oldID, Object element)

    This method replaces the old element identified by oldID with the new element element. The new element is added to the end of the list and the identifier that it is allocated is returned. If the specified old element was not present in the list, this method returns null without adding the new element.

  • public void addElementWithID (Object id, Object element)

    This method adds an element to the list, assigning it the specified identifier id. In a networked whiteboard, the central server will allocate identifiers and so the clients will use this method to add elements to their local lists.

  • public boolean replaceElementWithID (Object oldID, Object id, Object element)

    This method replaces the old element identified by oldID with the new element element and a pre-assigned identifier id. The new element is added to the end of the list. The method returns true if successful and false otherwise.

  • public Object getIDOfElement (Object element)

    This method returns the identifier associated with the specified element, or null if the element is not present.

  • public Enumeration elements ()

    This method returns an Enumeration of the elements in this list.

  • public Object clone ()

    This method returns a clone of the list.

  • public int getUpdateCount ()

    This method returns a count of the number of modifications that have been made to this list.

I won't dissect the code; it is relatively straightforward. The only interesting piece is our use of System.identityHashCode(Object) to allocate VM-unique identifiers for each element (based on the object's internal VM handle). We could have simply used an incrementing counter to allocate identifiers; however, this approach is far more interesting.

Networking the whiteboard with sockets

Our first approach to networking the whiteboard is based entirely on TCP/IP

Socket

s and

ServerSocket

s. For a primer, look back to my article "

Stepping through a site navigator applet

," which explored writing a networked chat system. The main change from that implementation is the use of object streams (

ObjectOutputStream

and

ObjectInputStream

) to provide message encapsulation rather than the simple UTF-encoded string encapsulation employed by the chat system.

Essentially, a central multithreaded TCP/IP server will maintain a master IDList, which contains the contents of the whiteboard. When a client first connects, he or she is sent a copy of this central display list. Thereafter, all changes to the whiteboard display list are relayed through the central server. So, when a whiteboard tool manipulates the whiteboard, its modifications are sent to the central server, which validates the changes and then broadcasts the result to all connected clients.

The socket-based distributed list

Class DListClient

The DListClient class implements the client-side of our distributed list. It is basically an extension of the ObservableList class, which opens a Socket to a central server and translates all client-side calls to manipulate the display list into messages that are sent to the server. A separate thread receives messages from the central server, updating the local display list accordingly. As expected, as messages arrive from the server, the registered update listeners are notified of any changes.

The implementation of this class is comparatively simple: After setting up the sockets and streams, client-side calls to update the list all essentially follow this form:

// ObjectOutputStream objectOut;

public synchronized void addElement (Object element) { try { objectOut.writeObject (new AddElementMsg (element)); objectOut.flush (); } catch (IOException ignored) { } }

Here, the client wishes to add an element to the list. We transmit an appropriate message to the server, flush the output stream, and return. For simplicity, we simply ignore any exceptions that may occur.

The listening thread is similarly simple:

// ObjectInputStream objectIn;

void processMsgs () throws IOException, ClassNotFoundException { while (true) { DListMsg msg = (DListMsg) objectIn.readObject (); if (msg instanceof ElementAddedMsg) { Object id = ((ElementAddedMsg) msg).getID (); Object element = ((ElementAddedMsg) msg).getElement (); list.addElementWithID (id, element); fireUpdate (); } ...

We loop, receiving messages from the server, which we then decode and execute. In this case, we receive a message indicating that someone (possibly us) has added an element to the central list. We add the element with the server-assigned identifier to our local copy of the display list and call fireUpdate() to notify listeners of the change.

Class DListServer

The server side of the socket-distributed list, class DListServer, follows the form of a typical multithreaded server. A main thread opens a ServerSocket, which then waits for client connections. For every connection that arrives, we create a new Thread which independently processes the connection. Each thread receives messages from the remote client, applies these to the central display list, and then broadcasts appropriate modification messages to all associated clients. So, the main loop has roughly the following form:

// ObjectInputStream objectIn; // IDList list;

void processMsgs (ObjectInputStream objectIn) throws IOException, ClassNotFoundException { while (true) { DListMsg msg = (DListMsg) objectIn.readObject (); ... if (msg instanceof ReplaceElementMsg) { Object oldID = ((ReplaceElementMsg) msg).getID (); Object element = ((ReplaceElementMsg) msg).getElement (); Object id = list.replaceElement (oldID, element); if (id != null) broadcastMsg (new ElementReplacedMsg (oldID, id, element)); } else ...

Here, we receive a request to replace an element on the list. We attempt to apply this change to the central list; if we succeed, we broadcast a message to all clients indicating the change they should perform and the identifier of the new element. Two clients attempting to replace an object at the same time is one obvious point of failure. In this case, the first message recieved by the server is executed and broadcast, the second is ignored.

Note that we don't expend any effort in reusing threads or minimizing the critical sections of our code. We don't expect a great number of people to be connected to the whiteboard at one time, so it is not necessary that our server be bulletproof. In a critical production environment where we had more breadth on the space-time continuum, we might look into those issues a bit more.

The messages

The messages that we send are all encapsulated as Objects that can be easily transmitted by the object streams. The following classes comprise the messages:

  • DListMsg

    DListMsg, which extends Serializable and declares no methods, is the superinterface of all messages. All messages must implement this interface and, thus, by inheritance must also implement Serializable, which means that they can be transmitted by the object streams.

  • InitMsg

    InitMsg is the first message sent from the server to a client. It contains the current display list.

  • AddElementMsg

    A client sends this message to the server when it wishes to add an element to the display list. The message contains the new element to be added.

  • ElementAddedMsg

    The server sends this message to all clients in response to an AddElementMsg. The message contains the new element, as well as its unique identifier. Upon receipt of this message, clients can add the new element to their display lists.

  • ReplaceElementMsg

    A client sends this message to the server when it wishes to replace an element of the display list. The message contains the new element, as well as the old element's identifier.

  • ElementReplacedMsg

    The server sends this message to all clients in response to a successful ReplaceElementMsg. The message contains the identifier to be removed, the new element, and its identifier.

  • QuitMsg

    The client sends this message to the server before it quits, allowing the server to perform a more graceful shutdown of the network connection.

Using the socket-distributed list

To apply this distributed list to our whiteboard, we simply modify the WB class to create an instance of DListClient in place of the original ObservableList. This client connects to DListServer, which must be running on the machine from which our applet originates.

For convenience, I include such a modified whiteboard class, called SocketWB, in the source distribution. Before you can put to use what you've learned thus far, you must first compile all of the source files. Remember, this uses lots of JDK 1.1 features, so you must compile with JDK 1.1. Here's what you need to do:

  1. Download the complete source, which is located in the Resources section of the article, and unzip (or untar, depending on which configuration you're using) the file.

  2. Change to the step directory.

  3. Compile the code using the following command:

    javac org\merlin\step\dec\*.java org\merlin\step\dec\socket\*.java

(If you're using Unix, be sure to use the forward slash and not the backslash in this command sequence.)

Next, you must start the server running on the machine that serves your Web pages. You must have a Web server, and you must serve the whiteboard as an applet from here. That's not 100 percent true; if you know what you're doing you can explore other options, but this is the easy route.

  1. Change to the step directory.

  2. Enter the following command:

    java org.merlin.step.dec.socket.DListServer

Finally, view the applet. You can perform this step from anywhere on the Internet that can create a direct socket connection to the server. Simply enter the following command:

appletviewer http://server/step/socket.html

If all goes well, you should be able to have several clients merrily drawing away on a whiteboard, all courtesy of our distributed list class.

Note: This applet will run only in the HotJava browser or Windows 95/NT implementation of Internet Explorer 4.0. If you are using either of these browsers, click on the link below to launch the applet.

http://www.javaworld.com/jw-12-1997/step/socket.html

If you are not using either of the above browsers, you can download the source from the Resources section and launch the applet locally using appletviewer.

In addition, because the applet attempts to create a socket connection with your machine, it may not succeed through some firewalls.

Now, let us do it all again....

1 2 3 Page
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more