Java RMI Internals
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.