Java's RMI relies on a shared directory service (rmiregistry) to bind remote objects to names. Clients can look up remote objects bound to a particular name and make remote method invocations. This may be convenient for small client/server based systems, but in a distributed system this acts as both a bottleneck and a single point of failure.
One limitiation of the rmiregistry implementation is that all participants
in a distributed system must be aware of the URL for the naming service in
order to locate any objects there. If any particpant does not know the
address of the naming service it will not be able to locate (or publish) any
remote object. Also, if the rmiregistry were ever to stop, or
the machine hosting it to crash, a participant no longer has the means to
locate a remote object. In this project you will implement both a replacement for
the RMI "Naming" service that does not have these disadvantages. You will implement some
sample programs that demonstrates this new service.
| The rmiregistry is based on a client/server system. The clients send a request to a known destination and await a response. The dependance between client and server is very clear. One is useless without the other. | ![]() |
![]() |
Creating a distributed system eliminates this client/server dependancy. In such a system, a client does not need to know the exact location to send a request to. It can broadcast a request to a network and await its response. Knowledge of a services exact location is no longer an issue. |
The API for the RMI naming service that a programmer sees is the defined by the five methods of the Naming class. We will replace Sun's the implementation of this class with our own, making it possible for existing application to use the new naming service without having to modify them to do so.
Below, is a snippet from Sun's Hello World example for RMI.
/** Snippet **/
HelloImpl obj = new HelloImpl();
// Bind this object instance to the name "HelloServer"
Naming.rebind("//myhost/HelloServer", obj);
/** Snippet **/
This is code will throw an exception if an rmiregistry containing bindings for the requested object cannot be contacted.
Both the client and the server need to agree ahead of time as what rmiregistry they will share. We will eliminate the
need for that step and make the act of locating a common naming service automatic.
The terminology used is explained first.
A Remote object
is any object that implements the java.rmi.Remote interface and is capable of being exported from the local VM. A Remote object can be localted in the local VM, or it can be
located a remote VM. A locally-bound object is Remote object located in the local VM and is bound to a name in some local registry.
A remotely-bound object is Remote object located in a remote VM and is bound to a name in some remote registry.
That said, our goal is to implement a new naming service with the following behavoir:
A skeleton class, derived directly from the JDK 1.3.1 source code, is provided for the Naming API. You should modifiy this class so that each method invocation is delegated to your implementation of the discovery based registry. You implementation will be used in place of the default implementation using the -Xbootclasspath/p option.
java -Xbootclasspath/p:your_project_path DiscoveryClientYou should be able to use this option to run any exising RMI based program with your name service. You can try this with the Hello World example from sun.
The broadcast protocol used for discovery will be text based and use ip multicast as a trasnport. Multicast sockets can be created in Java using the MulticastSocket class. There are two example program that use MulticastSockets here. The multicast protocol we will use is quite simple, it will consist of a single request/response pair. Both the response and the request are treated as a string containing details separated by commas. (quotes represent string literals and are not part of the protocol)
request ::= "NAMING-REQUEST" ","
signature "," {name | "*"}
response ::= "NAMING-RESPONSE" ","
signature "," name, "," host "," port "," key
signatue ::= a unique client identifier (string)
name ::= a remote object name (string)
host ::= a hostname or ip (string)
port ::= a port number (string)
key ::= a key, that when combined with the host & port
allows a client to access a remote object at
that location (string)
string ::= any sequence of characters that does not include a comma
A request can be used to inquire about a specific service, in order to obtain a response that provides information to locate and access that service.
An example request and response might look like the following,
"NAMING-REQUEST,01234,SpecificService" "NAMING-RESPONSE,01234,SpecificService,pollux,9999,XQEWRS"01234 is the clients signature which can be any random string. A username might be a good choice. When a client recieves a response, it can identify which request it was for by matching the signature to requests it has sent (so that it does not respond to its own request)
SpecificService is the name of a specific service the client is interested in. pollux is the name of the host where the service is. 9999 is the port where the service is listening. XQEWRS is a key that is needed to access the service. When the client attempts to connect the service, it needs to first present this key.
It is possible for a client to query about unknown services by using "*" as the service name. A number of responses can be sent in reply to a wildcard request.
These requests and responses will be packed into a DatagramPacket
and broadcast to the 230.0.0.0:1099 multicast group. These packets shouldn't leave our subnet
so the TTL should not be more than 32 hops. The maximum datagram length your implementation should
support is 4096 bytes. This should be sufficent for all messages sent and received.
Once the discovery protocol yields an address and a key that information must be used to obtain a reference remote object. This happens through a marshalling proceedure which is based on the Java Serialization mechanism. This means that ONLY serialized data should be exchanged between a host and a client.
The host for a service is listening for incoming TCP stream connections on some port. A client that has discovered that service should connect to that host and port and prepare to present the key. It should simply serialize the key and send it to the host. The host will then either accept the key, or reject it. If the host rejects the key, the connection to the client will simply be closed. If the host accepts the key, then a stub for the Remote object for that service, located in the host, needs to be transferred to the client, wrapped in a MarshalledObject

The client can deserialize a MarshalledObject from the reponse, obtain a stub for the Remote object representing the requested service and begin to use it. After trasnferring the stub the host will close the connection to the client. Using the MarshalledObject allows the stub to be annotated with the location of the class files for its definition, making it possible to use RMIs dynamic stub loading capabilities. The illustration above shows this process. Our version of marshalling relies on the Serialzation mechanism to format all of the data automatically, so all your implementation needs to do is to follow this negotiation.
You naming service implementation should act as the server in this marshalling process for any locally-bound objects.
You naming service implementation should act as the client if a lookup() is done for an object which is discovered and is
remotely-bound.
Here are some suggestions about the best way to approach this problem.
First, make sure you understand RMI. You should know what a stub is, and when it is needed. You should know what the Naming class is for. You should be comfotable with simple RMI programs (like the Hello World example).
Second, make sure you understand you understand what you are being asked to do. You need to understand what the discovery protocol should do and what the marshalling proceedure should do.
Next, break the problem down. Think about what is needed to accomplish this. Think of a design for your implementation that will allow you to build it and test it incrementatlly.
You can test your discovery implementation using the MulticastSender and MulticastReceiver
programs that are provided.
A skeleton project is provided with that will help you organize your code. You must use this format for the project. It requires all implementation code to placed into one package and all test and demo programs to be placed in another package. The Makefile is taken care of and all you'll need to do is to place your code in the correct places. A README file is included that explains this further.