CSE586 Project 1
A location-independent naming service


Introdcution:

     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 Naming 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 Naming API:

     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 Plan:

     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:

    Locally-bound Remote objects can be looked up by name.
    Locally-bound Remote objects can be bound(), rebound() and unbound() to and from names in a local registry.
    Locally-bound Remote objects should be advertised (with a discovery response) when requested with the discovery protocol.
    Remotely-bound Remote objects, whose names are not known, can be found using a discovery protocol.
    Remotely-bound Remote objects, whose names are known, can be found using a discovery protocol.
    Remotely-bound Remote can not be bound(), rebound() and unbound() to and from names in a local registry.
    Remotely-bound Remote objects should not be advertised (with a discovery response) when requested with the discovery protocol.

     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 DiscoveryClient
     You 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 Discovery Protocol:

     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.

The Marshalling Proceedure:

     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.

The Approach:

     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.

Requirements:

     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.

Resources:

RMI Specification
Getting Started Using RMI
Applying the Factory Pattern to RMI
Javadocs for the 1.3.1 API