The virtual laboratory consists of one central server and many clients -- one client per logged in user. The laboratory client is the interface supporting a single user. The central server stores and distributes artifacts used in sessions in progress. The server is unaware of the content and purpose of a single artifact. Its function is concentrated to accept and store new or modified artifacts received from clients and to distribute replicated versions of those artifacts to further clients. When a session is in progress the server stores every aspect of that session. Every client stores a replicated version of the session (see figure 4.3), which is an exact copy of the session maintained by the server. Every change produced by a user causes a transmission of the changed element to the server. The server updates his own representation of the session and transmits the changed artifacts to other clients that update and redisplay those elements.
The actual transport of artifacts between clients and the server is performed via Java RMI (Remote Method Invocation). RMI allows implementiion of remote objects whose methods are executed over the network. Both, the server and the client, are classes that are implemented as remote objects. Three things are needed to make a class remotely accessible:
Remote executable methods of remote objects must be defined by a remote interface that extends the interface java.rmi.Remote. An interface is actually only a set of method declarations. The method bodies are implemented by the actual remote object. The remote interface can however be instantiated like a regular class. A remote object is always referred to by its remote interface, not the implementation class.
For example, the server in our system is an instance of the Server class (fig 4.4). This class implements the ServerPort interface. The arrow with the broken line in figure 4.4 represents the association of the Server class with the ServerPort interface. That interface defines alls methods that are remotely accessible. A client would obtain an instance of the ServerPort interface, which is used to execute methods that are implemented by the Server class.
The server object is making its own remote interface public accessible through the RMI remote object registry. The RMI registry is a separately running process on the server machine that provides a bootstrap mechanism for obtaining references to remote objects. Each remote object like our Server has to be registered under a name to the registry. The registry contains a database that maps those names to the remote objects. A client can now obtain the remote object by providing the name together with the host name and the port number of the RMI registry. This is done by calling the static method lookup of the java.rmi.Naming class:
As is evident by this example, the RMI address of a remote object is
represented as an URL of the form:
The client also provides remote methods in a similar fashion. Clients are instances of the Client class. Remote methods are again defined by a remote interface, which is in this case the Connection interface. The server stores one instance of this remote interface for every connected client. There is however a difference compared to the client in how the server is obtaining the clients remote interface. The client doesn't need to provide the RMI registry. The RMI registry is only used to establish a first remote connection in one direction (from client to server). The server is using the fact that the client already knows the server's remote interface. The client's instance of its remote interface can be passed via a remote server method calls. Exactly this is done by the client. He forwards his own Connection interface as a method argument to the server by calling the server sided remote method loginUser. The server can now use the Connection interface to call methods on the client's side. A connection in both directions is now established.
The ServerPort interface defines following remote methods, which are grouped into four categories:
The Connection interface is the client's pendant to the ServerPort Interface. It defines client sided remote methods that are called by the server:
Java spawns a separate thread for every received remote method call. The simultaneous access of artifacts is possible that way and also time-consuming server requests don't block all other clients. Inconsistency in the state of an artifact, as seen by the clients, is possible if clients access and change an artifact at the same time. For example, let's consider a scenario of a server with two connected clients. Both clients change the same artifact at almost the same time (see figure 4.5). Client A is sending the new version of the object to the server first. While the server is receiving the object, client B is sending his version of the object. Both clients end up with a different version of artifact after the server has finished the distribution.
One solution to prevent this behavior is to declare all server methods that deal with artifact distribution, like addElement and removeElement, as synchronized. Synchronized methods can only be executed by a single thread (which represents a client in this case) at a time. This does however reduce the response time of the server. It is unnecessary to block the transmission of other artifacts that don't correspond with the currently received artifact.
Therefore, a mutual exclusion mechanism in form of locks has been implemented. Clients must request a lock for an artifact before they can make changes. Further lock requests of other clients for that artifact are refused until the lock is released by the lock holder. Whenever locks are used, there is the question if deadlocks can occur. One necessary condition for deadlocks is the hold and wait situation . That means that a client may have locked an artifact while waiting for another locked artifact. This is however never the case in our system. Clients lock elements only to perform operations on them. Those operations don't need to wait for the lock release of another artifact. The lock is released after an artifact is transmitted to the server.
In practice the client locks and unlocks objects by calling setLock and releaseLock defined by the ServerPort interface. It is possible that those methods are called simultaneously by several clients. The server maintains a Lock object (the Lock class is shown in figure 4.7) for every artifact. This object encapsulates the lock owner of the according artifact. The lock owner is set and reset by the server through the methods setLock and releaseLock of the Lock object. Those methods should not be confused with the equally named methods of the ServerPort interface. The Lock object methods are implemented synchronized in order to prevent random states caused by race conditions.