analytics

Sunday, May 8, 2011

Java RMI

Java Remote Method Invocation

This is a technical literature study which purpose is to describe the basic parts of Java Remote Method Invocation.
Remote Method Invocation, abbreviated as RMI, provides support for
distributed objects in Java, i.e. it allows objects to invoke methods on remote objects. The
calling objects can use the exact same syntax as for local invocations.
The Java RMI model has two general requirements. The first requirement is that the RMI
model shall be simple and easy to use and the second requirement it that the model shall fit into the Java language in a natural way.

Distributed Object Application

An RMI application is often composed of two separate programs, a server and a client. The server creates remote objects and makes references to those objects
accessible. Then it waits for clients to invoke methods on the objects. The client gets remote references to remote objects in the server and invokes methods on those remote objects. The RMI model provides an distributed object application to the programmer. It
is a mechanism that the server and the client use to communicate and pass information
between each other. A distributed object application has to handle the following properties:

· Locate remote objects: The system has to obtain references to remote objects. This
can be done in two ways. Either by using RMI’s naming facility, the rmiregistry, or by
passing and returning remote objects.

· Communicate with remote objects: The programmer doesn’t have to handle
communication between the remote objects since this is handled by the RMI system.
The remote communication looks like an ordinary method invocation for the
programmer.

· Load class byte codes for objects that are passed as parameters or return values:
All mechanisms for loading an object’s code and transmitting data is provided by the
RMI system.

Figure A-1, below, illustrates an RMI distributed application. In this example the RMI
registry is used to obtain references to a remote object. First the server associates a name with a remote object in the RMI registry (see note 1 in figure A-1). When a client wants access to a remote object it looks up the object, by its name, in the registry (see note 2 in figure A-1). Then the client can invoke methods on the remote object (see note 3 in figure A-1) at the server.

Figure A-1. An Illustration of a distributed object application.


Interfaces and Classes

Since Java RMI is a single-language system, the programming of distributed application in RMI is rather simple [CDK01, p194]. All interfaces and classes for the RMI system are defined in the java.rmi package [SUN02 ,p6]. Figure A-2, below, illustrates the relationship between some of the classes and interfaces. The RemoteObject class implements the Remote interface while the other classes extend RemoteObject.


Figure A-2. Interfaces and Classes in the java.rmi package.

The Remote Interface
A remote interface is defined by extending the Remote interface that is provided in the
java.rmi package. The remote interface is the interface that declares methods that clients can invoke from a remote virtual machine [SUN02 ,p6]. The remote interface must satisfy the following conditions:

· It must extend the interface Remote.
· Each remote method declaration in the remote interface must include the exception
RemoteException (or one of it’s superclasses) in it’s thrown clause.

The RemoteObject Class
RMI server functions are provided by the class RemoteObject and its subclasses
RemoteServer, UnicastRemoteObject and Activatable. Here is a short description of what the diffrent classes handle:

· RemoteObject provides implementations of the methods hashCode, equals and
toString in the class java.lang.Object.
· The classes UnicastRemoteObject and Activatable create remote objects and export
them, i.e. the classes make the remote objects available to remote clients.

The RemoteException Class
The class RemoteException is a superclass of the exceptions that the RMI system throws
during a remote method invocation. Each remote method that is declared in a
remote interface must specify RemoteException (or one of it’s superclasses) in it’s throws
clause to ensure the robustness of applications in the RMI system.
When a remote method invocation fails, the exception RemoteException is thrown.
Communication failure, protocol errors and failure during marshalling or unmarshalling of parameters or return values are some reasons for RMI failure.
RemoteException is an exception that must be handled by the caller of the remote method, i.e. it is a checked exception. The compiler ensures that the programmer have handled these exceptions.

Three-layer architecture


RMI consists of three layers:

stubs & skeletons: this layer intercepts method calls made by the client to the
interface reference and redirects them to a remote RMI service

remote reference layer: this layer understands how to interpret and manage
references made from clients to the remote service objects.
In JDK 1.1, this layer connects clients to remote service objects that are running and
exported on a server. The connection is a one-to-one (unicast) link.
In the Java 2 SDK, this layer was enhanced to support the activation of dormant
remote service objects via Remote Object Activation. When a method call is made
to the proxy for an activatable object, RMI determines if the remote service
implementation object is dormant. If it is dormant, RMI will instantiate the object
and restore its state from a disk file. Once an activatable object is in memory, it
behaves just like JDK 1.1 remote service implementation objects.

transport layer: based on TCP/IP. Provides connectivity via sockets. An internal
protocol, the Java Remote Method Protocol (JRMP) is used for communication.

The General RMI Architecture


  • The server must first bind its name to the registry
  • The client lookup the server name in the registry to establish remote references.
  • The Stub serializing the parameters to skeleton, the skeleton invoking the remote method and serializing the result back to the stub.

The Stub and Skeleton




  • A client invokes a remote method, the call is first forwarded to stub.
  • The stub is responsible for sending the remote call over to the server-side skeleton
  • The stub opening a socket to the remote server, marshaling the object parameters and forwarding the data stream to the skeleton.
  • A skeleton contains a method that receives the remote calls, unmarshals the parameters, and invokes the actual remote object implementation.

Steps for Developing an RMI System

1. Define the remote interface
2. Develop the remote object by implementing the remote interface.
3. Develop the client program.
4. Compile the Java source files.
5. Generate the client stubs and server skeletons.
6. Start the RMI registry.
7. Start the remote server objects.
8. Run the client


Step 1:  Defining the Remote
             To create an RMI application, the first step is the defining of a remote interface between the client and server objects.

/* SampleServer.java */
import java.rmi.*;

public interface SampleServer extends Remote
{
  public int sum(int a,int b) throws RemoteException;
}

Step 2: Develop the remote object
  • The server is a simple unicast remote server.
  •  Create server by extending java.rmi.server.UnicastRemoteObject.
  • The server uses the RMISecurityManager to protect its resources while engaging in remote communication.
 /* SampleServerImpl.java */
import java.rmi.*;
import java.rmi.server.*;
import java.rmi.registry.*;

public class SampleServerImpl extends UnicastRemoteObject
                             implements SampleServer
{
  SampleServerImpl() throws RemoteException
  {
     super();
  }
  • Implement the remote methods
 /* SampleServerImpl.java */
  public int sum(int a,int b) throws RemoteException
  {
     return a + b;
  }
}

  •   The server must bind its name to the registry, the client will look up the server name.
  •   Use java.rmi.Naming class to bind the server name to registry. In this example the name call “SAMPLE-SERVER”.
  •  In the main method of your server object, the RMI security manager is created and installed.
 
/* SampleServerImpl.java */
  public static void main(String args[])
  {
      try
      {
        System.setSecurityManager(new RMISecurityManager());
        //set the security manager

        //create a local instance of the object
        SampleServerImpl Server = new SampleServerImpl();

        //put the local instance in the registry
        Naming.rebind("SAMPLE-SERVER" , Server);

        System.out.println("Server waiting.....");
      }
      catch (java.net.MalformedURLException me)       {
        System.out.println("Malformed URL: " + me.toString());   }
      catch (RemoteException re)  {
         System.out.println("Remote exception: " + re.toString());  }
  }

Step 3: Develop the client program

  •    In order for the client object to invoke methods on the server, it must first look up the name of server in the registry. You use the java.rmi.Naming class to lookup the server name.
  •      The server name is specified as URL in the from                ( rmi://host:port/name )
  •    Default RMI port is 1099.
  •     The name specified in the URL must exactly match the name that the server has bound to the registry. In this example, the name is “SAMPLE-SERVER”
  •     The remote method invocation is programmed using the remote interface name (remoteObject) as prefix and the remote method name (sum) as suffix.
 
import java.rmi.*;
import java.rmi.server.*;
public class SampleClient 
{
   public static void main(String[]  args)
   {
      // set the security manager for the client
      System.setSecurityManager(new RMISecurityManager());
      //get the remote object from the registry
      try
        {
          System.out.println("Security Manager loaded");
          String url = "//localhost/SAMPLE-SERVER";
          SampleServer remoteObject = (SampleServer)Naming.lookup(url);
          System.out.println("Got remote object");
          System.out.println(" 1 + 2 = " + remoteObject.sum(1,2) );
        }
        catch (RemoteException exc) {
          System.out.println("Error in lookup: " + exc.toString()); }
        catch (java.net.MalformedURLException exc) {
          System.out.println("Malformed URL: " + exc.toString());   }
        catch (java.rmi.NotBoundException exc)  {
          System.out.println("NotBound: " + exc.toString());
        }
   }
}

Step 4 & 5: Compile the Java source files & Generate the client stubs and server skeletons
 
  •   Assume the program compile and executing at elpis on ~/rmi
  •   Once the interface is completed, you need to generate stubs and skeleton code. The RMI system provides an RMI compiler (rmic) that takes your generated interface class and procedures stub code on its self.
elpis:~/rmi> set CLASSPATH=”~/rmi”
elpis:~/rmi> javac SampleServer.java
elpis:~/rmi> javac SampleServerImpl.java
elpis:~/rmi> rmic SampleServerImpl
                        elpis:~/rmi> javac SampleClient.java

Step 6: Start the RMI registry

  •      The RMI applications need install to Registry. And the Registry must start manual by call rmiregisty.
  •     The rmiregistry us uses port 1099 by default. You can also bind rmiregistry to a different port by indicating the new port number as : rmiregistry
         elpis:~/rmi> rmiregistry
  •        Remark: On Windows, you have to type in from the command line:
         > start rmiregistry


Steps 7 & 8: Start the remote server objects & Run the client

  •       Once the Registry is started, the server can be started and will be able to store itself in the Registry.
  •    Because of the grained security model in Java 2.0, you must setup a security policy for RMI by set java.security.policy to the file policy.all
 elpis:~/rmi> java –Djava.security.policy=policy.all SampleServerImpl
elpis:~/rmi> java –Djava.security.policy=policy.all SampleClient

Java Policy File

  • In Java 2, the java application must first obtain information regarding its privileges. It can obtain the security policy through a policy file. In above example, we allow Java code to have all permissions,  the contains of the policy file policy.all is:
  grant {
         permission java.security.AllPermission;
  };
  •       Now, we given an example for assigning resource permissions:
  grant {
         permission java.io.filePermission “/tmp/*”, “read”, “write”;
         permission java.net.SocketPermission “somehost.somedomain.com:999”,”connect”;
         permission java.net.SocketPermission “*:1024-65535”,”connect,request”;
         permission java.net.SocketPermission “*:80”,”connect”;
  };



Sample program

• Client invokes a remote method with strings as parameter
• Server returns a string containing the reversed input string and a message

Define the remote interface (HelloInterface.java)

import java.rmi.*;
public interface HelloInterface extends Remote {
public String say(String msg) throws RemoteException;
}

We define an interface for a remote class that includes one remote method: 
say accepts a String as an input parameter and returns a String as a result
Note that the interface extends Remote and that each method (there’s only one here)
throws RemoteException. These classes are defined in the java.rmi package, so we import it.

Define the remote class (Hello.java)

import java.rmi.*;
import java.rmi.server.*;
public class Hello
extends UnicastRemoteObject
implements HelloInterface {
private String message;
public Hello(String msg) throws RemoteException {
message = msg;
}
public String say(String m) throws RemoteException {
// return the input message - reversed input plus our standard message
return new StringBuffer(m).reverse().toString() + "\n" + message;
}
}

The remote class defines two methods:
Hello is our constructor and will be used by the server to set define the standard
message that will always be sent back to the client.
say is a method that takes a string as the input. It returns a string containing the
input with the characters reversed followed by a newline followed by our standard
message (set in the constructor).

Note that:
- the class extends UnicastRemoteObject
this will allow the methods to be invoked remotely
- the class implements the interface we defined
- each method must throw RemoteException because remote procedure calls might fail
- the say method accepts a String and returns a String. String is defined to be
Serializable. If you were to accept or return an object you defined, it would have to
be defined to implement Serializable.

The server (HelloServer.java)

import java.rmi.*;
import java.rmi.server.*;
public class HelloServer {
public static void main(String argv[]) {
if (System.getSecurityManager() == null)
System.setSecurityManager(new RMISecurityManager());
try {
// instantiate the service with the standard message & tell registry
Naming.rebind("Hello", new Hello("Hello, world!"));
System.out.println("Server is running...");
} catch(Exception e) {
System.out.println("Hello Server failed: " + e);
}
}
}

The server is a plain program that is responsible for:

1. Creating and installing a security manager. We do this with:
System.setSecurityManager(new RMISecurityManager());
The security manager makes sure that classes that get loaded do not perform
operations that they are not allowed to perform.

2. Registering at least one remote object with the object registry.
We do this with:
Naming.rebind(object_name, object);
where object_name is a String that names the remote object
and object is the remote object that we are registering.
All that our server does is:
set the security maanger
create the remote object
register it
print a message saying “Server is running…”

The client (HelloClient.java)

import java.rmi.*;
public class HelloClient {
public static void main(String argv[]) {
try {
if (args.length < 0) {
System.err.println(“usage: java HelloClient string …\n”);
System.exit(1);
}
HelloInterface hello = (HelloInterface)Naming.lookup("//localhost/Hello");
for (int i=0; i < args.length; ++I)
System.out.println(hello.say(args[i]));
} catch (Exception e) {
System.out.println(”Helloclient exception: " + e);
}
}
}

The client invokes the remote method.
To do this, the client must have a handle (reference) to the remote object.
The object registry allows the client to get this reference to a remote object.
To get the reference to the remote object, we need to know:
 
1. Internet name (or address) of machine that is running the object registry for
which the remote object is registered (can omit if localhost)

2. Port on which the object registry is running (can omit if it is 1099 - default)

3. Name of the object within the object registry.
The Naming.lookup method obtains an object handle from the object registry
running on localhost on the default port (we could have just looked up “Hello” in
this case).
The result of Naming.lookup must be cast to the type of the Remote Interface
------
The remote invocation of the object is the call to hello.say(“abc”). It returns a
String which we print. Remember again, that we can get a String only because
String is a Serializable class.

The policy (policy)
grant {
permission java.security.AllPermission;
}

The policy file controls which clients have which permissions. The one in this
example grants everyone global permission (not a good one to use in a production
environment!).
The policy file is not needed when programming with JDK < 1.2
The Java security manager contains a number of check methods (e.g
checkConnect). In the default manager, these are implemented by calling a
checkPermission method. The type of permission is specified by a parameter of
type Permission that is passed to checkPermission. The method checks whether
the permission is implied by a list of granted permissions.
To specify or change the security policy, there is a class named Policy. A program
can get its policy by calling Policy.getPolicy() or set it (if it has permissions) with
Policy.setPolicy(). The security policy is typically specified by a policy
configuration file which is reaad when the program starts.

Compile

• Compile the interface and classes:
javac HelloInterface.java Hello.java
javac HelloServer.java HelloClient.java
• Create the class files for stubs:
rmic Hello
• rmic creates:
Hello_Skel.class skeleton - server-side stub
Hello_Stub.class client-site stub

We first compile the remote interface and classes using javac. Then we generate the
class files for the client and server stubs with the rmi compiler:
rmic Hello
This produces two class files:

Hello_Skel.class is the skeleton (a name for a server-stub)
Hello_Stub.class is the client stub function

Run

• Start the object registry (in the background):
rmiregistry &
• Start the server (in the background):
java -Djava.security.policy=policy
HelloServer &
• Run the client:
java HelloClient testing abcdefgh
• See the output:
gnitset
Hello, world!
hgfedcba
Hello, world!

Now we’re ready to run the program.
If the registry is not running, it must be started. Otherwise the server will not be able
to register the object and the client will not be able to look it up.
We start it in the background with:
rmiregistry &
If we wanted it to listen on a different port than 1099, we would specify the port
number on the command line. For example, to listen on port 5511:
rmiregistry 5511 &
Next we start the server (also in the background) by running:
java -Djava.security.policy=policy HelloServer &
where the -D flag sets the name of the file containing our security policy (our file is
named policy and is in the current directory).
Finally, we can run the client with:
java HelloClient testing abcdefg

NOTE: be sure to kill the registry and server processes when you’re done.

No comments:

Post a Comment