Distributed Computing-Java RMI-Under the Hood
06 Jul 1998Distributed-Computing
-
The RMI Server creates an instance of the ‘Server Object’ which extends UnicastRemoteObject
-
The constructor for UnicastRemoteObject “exports” the Server Object - basically making it available to service incoming RMI calls. A TCP socket which is bound to an arbitrary port number is created and a thread is also created that listens for connections on that socket.
-
The server registers the server object with the registry. This operation actually hands the registry (Note: The RMI Registry is an RMI server itself) the client-side “stub” for that server object. This stub contains the information needed to “call back” to the server when it is invoked (such as the hostname/port of the server listening socket).
-
A client obtains this stub by calling the registry, which hands it the stub directly. (This is also where the “codebase” comes in: If the server specified a “codebase” to use for clients to obtain the classfile for the stub, this will be passed along to the client via the registry. The client can then use the codebase to resolve the stub class - that is, to load the stub classfile itself). That is all that the RMIRegistry really does: It holds onto remote object stubs which it can hand off to clients when requested.
-
When the client issues a remote method invocation to the server, the stub class creates a “RemoteCall” which basically (a) opens a socket to the server on the port specified in the stub itself, and (b) Sends the RMI header information as described in the RMI spec.
-
The stub class marshalls the arguments over the connection by using methods on RemoteCall to obtain the output stream which basically returns a subclass of ObjectOutputStream which knows how to deal with passing objects which extend java.rmi.Remote, which serializes Java objects over the socket
-
The stub class calls RemoteCall.executeCall which causes the RMI to happen.
-
On the server side, when a client connects to the server socket, a new thread is forked to deal with the incoming call. The original thread can continue listening to the original socket so that additional calls from other clients can be made.
-
The server reads the header information and creates a RemoteCall of its own to deal with unmarshalling the RMI arguments from the socket.
-
The server calls the “dispatch” method of the skeleton class (the server-side “stub” generated by rmic), which calls the appropriate method on the object and pushes the result back down the wire (using the same ‘RemoteCall’ interface which the client used to marshall the arguments). If the server object threw an exception then the server catches this and marshalls that down the wire instead of the return value.
-
Back on the client side, the return value of the RMI is unmarshalled (using the RemoteCall created in step 5 above) and returned from the stub back to the client code itself. If an exception was thrown from the server that’s unmarshalled and re-thrown from the stub.
Now, it should be obvious how multiple clients call the same server object:
They all get stubs which contain the same hostname/port number as the server-side socket which is listening for calls on the object.
When a client connects, the server forks a new thread to deal with the incoming request, but keeps listening to that original socket (in another thread) so that other calls can be made.
There doesn’t appear to be any synchronization within the server side components - if multiple clients simultaneously call into the server object they can all manipulate the server object state at the same time. You can of course make methods in your server object “synchronized” to synchronize access to them.
Now, the RMI specification defines both a “single op” and a “stream” protocol for RMI calls. The former allows a single RMI call to be made on a socket which is then closed; the latter allows multiple RMI calls to be issued on the same socket one after the other.
Sun’s RMI implementation appears to be using this latter mechanism which means that a single client-side stub object will open a single socket to the server for all of its communication.
Multiple stubs within the same JVM or different JVMs on the same host will each have their own socket to the server (which might all dispatch calls to the same server-side object). The net result is that each time you obtain a stub (from the registry) for a particular server-side object, you will eventually create a new socket to talk to the server.
So basically:
-
ON THE SERVER: A single ‘UnicastRemoteObject’ which ends up creating multiple threads to listen for calls on this single object - one thread per socket (basically meaning one thread per client)
-
ON THE CLIENT: One socket to the server per stub, meaning that if the client has multiple stubs pointing to the same server object it will have multiple sockets open to that server.