![]() |
![]() |
|
02152 Concurrent Systems Fall 2008 |
RMI Details |
Home | Plan | Material |
All the following is from the Java 1.5.0 specification and thus may require Java version 1.5 (5.0) or above.
First of all, an application that serves methods through RMI, must implement a remote interface of type java.rmi.Remote. This interface defines the methods that will be served through RMI and all methods must be declared to throw a java.rmi.RemoteException. E.g.,
public interface SomeRemoteInterface extends Remote { <interfaces of served methods> }
In general, a class that implements a remote interface should at least do the following:
An RMI server program needs to create the initial remote objects and export them to the RMI runtime, which makes them available to receive incoming remote invocations. The general setup procedure should do the following:
public class SomeClass implements SomeRemoteInterface
This declaration states that the class implements the SomeRemoteInterface and therefore can be used for a remote object. The class definition could also be declared to extend the UnicastRemoteObject, as done in the exercise. Here another approach is shown, which does not require the remote object to extend this class.
E.g.,
public SomeClass { super(); }
Which creates an instance of SomeClass and invokes the parent constructor with super();
NOTE: In the exercise we extend the remote object from UnicastRemoteObject. If you choose to follow that method, instead of the alternative that is applied here, then the empty parent constructor super() will make the remote object listen to a dynamic port. In case you are behind a firewall, it most likely does not let the dynamic ports through and therefore you should instead call the parent constructor with an integer argument, being the port the server will listen for connections on and then open that port in your firewall! So, if your remote object extends the UnicastRemoteObject, remember to invoke the super constructor with an integer argument:
public SomeClass { super(42000); }
Sort of self-explanatory. Implement all methods required by the <remote interface> being implemented.
The main method's first task is to create and install a security manager which protects access to system resources from untrusted downloaded code running within the Java virtual machine. A security manager determines whether downloaded code has access to the local file system or can perform any other privileged operations.
If an RMI program does not install a security manager, RMI will not download classes (other than from the local class path) for objects received as arguments or return values of remote method invocations. This restriction ensures that the operations performed by downloaded code are subject to a security policy.
Here's the code that creates and installs a security manager:
if (System.getSecurityManager() == null) { System.setSecurityManager(new SecurityManager()); }
In the lab exercise, we created a remote object implementing the UnicastRemoteObject. That is not the method we apply here. Instead, the UnicastRemoteObject offers a static method, exportObject(Remote obj, int port) which returns a Remote object that may be casted to the type of the interface implemented. The port argument specifies the port the server will be listening on and the special value 0 makes it listen to a dynamic port, chosen at runtime. Beware using a dynamic port for listening, if you are behind a firewall. If so, rather choose a specific port and make sure you open that port in your firewall! So we may create our remote objects as follows:
SomeRemoteInterface stub = UnicastRemoteInterface.exportObject(new SomeClass(), 42000);
Note that the type of the variable stub must be SomeRemoteInterface, not SomeClass, because the stub for a remote object only implements the remote interfaces that the exported remote object implements. Register at least one remote object with the RMI registry. In the exercise, the RMI registry is started externally by the rmiregistry command, optionally called with an argument, being the port used for listening for queries for remote objects. This may be done within the remote objects main method as well, by using the java.rmi.registry.LocateRegistry and java.rmi.registry.Registry APIs. Thus, to start the registry, we may write:
Registry registry = LocateRegistry.createRegistry(42000); //
It is not required that the registry is running on the same port as the remote object; it is however convenient, as we may then only open up a single port in our firewall, in case such is present.
In case one prefers to run the registry externally, e.g., with the rmiregistry command, we should still get a reference to the registry in our code. We may do so by instead writing:
Registry registry = LocateRegistry.getRegistry(42000); // Get a stub for the registry listening on port 42000
Once we have a reference to the registry running, we may bind our server to the registry, to allow stubs to be requested and downloaded by clients. We do so, by introducing a name the remote object is identified by in the registry, and pass the remote object to the registry as follows: Â
registry.rebind("SomeTask", stub);
That was an overview of the behavior of the remote object and its implementation. For reference, a suggestion for the implementation of the remote object might be:
import java.rmi.RMISecurityManager; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.rmi.server.UnicastRemoteObject; public class SomeClass implements SomeRemoteInterface { private static final int PORT = 1111; public SomeClass() { super(); } public static void main(String[] args) { try { if (System.getSecurityManager() != null) System.setSecurityManager(new RMISecurityManager()); SomeRemoteInterface stub = (SomeRemoteInterface) UnicastRemoteObject.exportObject(new SomeClass(),PORT); Registry reg = LocateRegistry.createRegistry(PORT); String name = "SomeTask"; reg.rebind(name, stub); System.out.println("Bound server " + name + " to registry "); } catch (Exception e) { e.printStackTrace(); } } <implementation of methods served, according to the SomeRemoteInterface specification> }
A client that uses the methods of some remote object, served by RMI, can be divided into three basic steps:
Here, these tasks are thought of as computed in the main method of the client.
Similar to the server, we may initially create a security manager:
if (System.getSecurityManager() == null) { System.setSecurityManager(new SecurityManager()); }
For a client to utilize the methods of the remote object, again the registry should be obtained, now accessed remotely, using the java.rmi.registry.LocateRegistry and java.rmi.registry.Registry APIs. Say the registry and the remote object are running on the URL rmi.served.somewhere.com:
String server = "rmi.served.somewhere.com"; int port = 42000; Registry registry = LocateRegistry.getRegistry(server, port);
The port must of course match the port on which the registry is running on the remote host and any firewall on/before the remote host must allow connections on that specific port.
Finally, a stub for the remote object, serving the methods defined by the interface, e.g., SomeRemoteInterface, is obtained with:
String name = "SomeTask"; SomeRemoteInterface remObj = (SomeRemoteInterface) registry.lookup( name );
Note, that the remote object must be handled as the interface SomeRemoteInterface, not SomeClass which is not a known type to the client application. After the remote object is obtained, the methods offered by the interface, may then be invoked, causing remote method invocations on the object residing on the remote host.
To compile the server and client classes, do so as usual with java.
The server and client programs run with a security manager installed. When you run either program, you need to specify a security policy file so that the code is granted the security permissions it needs to run. Here is an example policy file to use with the server program:
grant codeBase "file:/absolute/path/to/server/codebase/" { permission java.security.AllPermission; };
grant codeBase "file:/absolute/path/to/client/codebase/" { permission java.security.AllPermission; };
If the server does not create a registry it self, then start the RMI registry with:
> rmiregistry [portno] &
Then start the server with
> java -cp <path/to/server/classpath> \ -Djava.rmi.server.codebase=file:/<path/to/server/classpath> \ -Djava.security.policy=server.policy \ -Djava.rmi.server.hostname=<external IP or hostname for the server> \ SomeClass
When the server is running, it's time to start the client. Do so by typing:
> java -cp <path/to/client/classpath> \ -Djava.rmi.server.codebase=file:/<path/to/client/classpath> \ -Djava.security.policy=client.policy \ SomeClient
For most of you, you will probably not require the security files and the property java.security.policy on the command line, when executing. Although, it is mentioned here, to make it less probable that you will struggle with the security managers, should you require them in your assignment.
Similarly the java.rmi.server.codebase property is mentioned here, to make it less probable that it should cause errors, if you do not specify this property. You may want to strip down the server and client commands to:
> java -cp <path/to/server/classpath> \ -Djava.rmi.server.hostname=<external IP or hostname for the server> \ SomeClass
> java -cp <path/to/client/classpath> SomeClient
http://java.sun.com/docs/books/tutorial/rmi/index.html
http://java.sun.com/j2se/1.5.0/docs/guide/rmi/
Written by:
Bjarne Wahlgreen
Hans Henrik Løvengreen, Nov 7, 2008 |