CS 696 Emerging Technologies: Java Distributed Computing Spring Semester, 1999 RMI Intro |
||
---|---|---|
© 1999, All Rights Reserved, SDSU & Roger Whitney San Diego State University -- This page last updated 04-Feb-99 |
RMI - Introduction by Example
A First Program - Hello World
Modified from "Getting Started Using RMI"
The Remote Interface
public interface Hello extends java.rmi.Remote { String sayHello() throws java.rmi.RemoteException; }
The Server Implementation
import java.net.InetAddress; import java.rmi.Naming; import java.rmi.RemoteException; import java.rmi.RMISecurityManager; import java.rmi.server.UnicastRemoteObject; public class HelloServer extends UnicastRemoteObject implements Hello { public HelloServer() throws RemoteException { } public String sayHello() { return "Hello World from " + getHostName(); } protected static String getHostName() { try { return InetAddress.getLocalHost().getHostName(); } catch (java.net.UnknownHostException who) { return "Unknown"; } }
public static void main(String args[]) { // Create and install a security manager System.setSecurityManager(new RMISecurityManager()); try { // Create and register the server object HelloServer serverObject = new HelloServer(); Naming.rebind("rmi://eli.sdsu.edu/HelloServer", serverObject ); // Signal successful registration System.out.println("HelloServer bound in registry"); } catch (Exception e) { System.out.println("HelloServer err: "); e.printStackTrace(); } } }
The Client Code
import java.rmi.*; import java.net.MalformedURLException; public class HelloClient { public static void main(String args[]) { try { Hello remote = (Hello) Naming.lookup( "rmi://eli.sdsu.edu/HelloServer"); String message = remote.sayHello(); System.out.println( message ); } catch ( NotBoundException error) { error.printStackTrace(); } catch ( MalformedURLException error) { error.printStackTrace(); } catch ( UnknownHostException error) { error.printStackTrace(); } catch ( RemoteException error) { error.printStackTrace(); } } }Note the multiple catches are to illustrate which exceptions are thrown
Running The Example
Server Side
Step 0 . Install a policy file for socket permissions
grant { permission java.net.SocketPermission "*:1024-65535", "connect,accept,resolve"; permission java.net.SocketPermission "*:1-1023", "connect,resolve"; };
Step 2 . Generate Stubs and Skeletons (to be explained later)
The rmi compiler generates the stubs and skeletons
Step 4. Register the server object with the rmiregistry by running HelloServer.main()
Client Side
The client can be run on the same machine or a different machine than the server
Step 1 . Compile the source code
Basic Issues
Multiple JVMs Running
After testing (running and rerunning and rerunning and ...) your server you may end up with many JVMs that will not quit once you log out.
It is very easy to accumulate lots of used orphaned processes
Get rid of them!
I use the command:
/usr/bin/ps -o pid,stime,comm -uwhitney
Which will find all my processes
Put the following in a file, make it executable
Running the file will then kill all your Java processes
kill ´/usr/bin/ps -o pid,comm -u$USER | egrep java | awk '{print $1}'´
Port Contention
Only one server can use a port at a time!
Not everyone can use the same port number for the RMI registry on rohan. The RMI HelloServer example runs the RMI registry on the default port 1099
You will need to find a port that is unused for your server!
Ports you use must be in the range: 5001-65536
Last year some students would start one RMI registry and use if for several days.
Run RMI registry on open Port
The following program will start the RMI registry on an open port
import java.net.ServerSocket; import sdsu.util.ProgramProperties; import java.io.IOException; /** * This class starts the rmi registry on a random * open port. User can suggest a port using the -p flag */ public class StartRMIRegistry { public static void main( String[] args ) { try { ProgramProperties flags = new ProgramProperties( args ); int suggestedPort = flags.getInt( "p", 0); int port = getPort( suggestedPort ); String randomPortRegistry = "rmiregistry " + port; Runtime.getRuntime().exec( randomPortRegistry ); System.out.println( "rmiregistry running on port " + port ); System.exit( 0 ); } catch (IOException error ) { System.out.println( "Had trouble trying to find a port\n " + error); } }
/** * Return an open port on current machine. Try the * suggested port first. * If suggestedPort is zero, just select a random port */ private static int getPort( int suggestedPort ) throws IOException { ServerSocket openSocket; try { openSocket = new ServerSocket( suggestedPort ); } catch (java.net.BindException portbusy) { // find random open port openSocket = new ServerSocket( 0 ); } int port = openSocket.getLocalPort(); openSocket.close(); return port; } }
Clients & RMI Registry Ports
The full syntax of the argument to Naming.lookup() is:
"rmi://host:port/ServerLabel"The "rmi:" is optional
The default port is 1099
The following are equivalent
Hello remote = (Hello) Naming.lookup( "rmi://eli.sdsu.edu:1099/HelloServer");
Hello remote = (Hello) Naming.lookup( "//eli.sdsu.edu:1099/HelloServer");
Hello remote = (Hello) Naming.lookup( "rmi//eli.sdsu.edu/HelloServer"); Hello remote = (Hello) Naming.lookup( "//eli.sdsu.edu/HelloServer");
Program Configuration
Location of server object is hard coded into both server and client
Hello remote = (Hello) Naming.lookup( "//eli.sdsu.edu/HelloServer");
Naming.rebind("//eli.sdsu.edu/HelloServer", serverObject);
This is typical student/teacher code, but is still bad!
Use command line flags and configuration files!
Java Command Line Flags
Java uses Properties for configuring a program
java.util.Properties
java -Dkey1=value1 -Dkey2=value2 className
String value = System.getProperty( key); String defaultValue = "Hi Mom"; value = System.getProperty( key, defaultValue);
SDSU Library Tools
sdsu.util.ProgramProperties
Parses normal key-value pairs on command line
Reads configuration files in Properties or sdsu.util.LabeledData format
If a key occurs in both command line and configuration file then the command line value is used
import java.io.IOException; import sdsu.util.ProgramProperties; public class Tester { public static void main( String[] args) throws IOException { String shortConfigFileName = "config.properties"; ProgramProperties options; options = new ProgramProperties( args, shortConfigFileName ); String defaultHost = "eli.sdsu.edu"; String host = options.getString( "host", defaultHost ); int port = options.getInt( "port", 1099 ); System.out.println( "host: " + host + " port: " + port ); } }config.properties #contents of config.properties # file goes in directory the program is started from host=rohan.sdsu.edu port=1099
sdsu.io.LocalRepository
Maps persistent objects to keys
Objects are stored in files
File names are:
public class SampleRepository { public static void main( String[] args ) throws IOException { // Open an existing Repository Repository files = new LocalRepository( "SimpleTest"); // Add an object to the repository // test will be saved in the file: letter.Properties Properties test = new Properties(); test.put( "hi", "mom" ); test.put( "bye", "dad" ); files.put( "letter", test ); // read file grades.Table and converts contents // to a table Table grades = (Table) files.get( "grades" ); } }
A Reasonable Implementation
The Client import java.rmi.*; import java.rmi.registry.Registry; import java.net.MalformedURLException; import java.io.IOException; import sdsu.util.ProgramProperties; public class HelloClient { public static void main(String args[]) { try { String server = getHelloHostAddress( args); Hello remote = (Hello) Naming.lookup( server ); String message = remote.sayHello(); System.out.println( message ); } catch ( Exception error) { error.printStackTrace(); } } private static String getHelloHostAddress( String args[] ) throws IOException { ProgramProperties flags = new ProgramProperties( args, "config" ); String host = flags.getString( "host" ); int defaultPort = Registry.REGISTRY_PORT; int port = flags.getInt( "port", defaultPort ); String serverLabel = flags.getString( "label" ); return "rmi://" + host + ":" + port + "/" + serverLabel; } }
The Server
import java.io.IOException; import java.net.InetAddress; import java.rmi.Naming; import java.rmi.RemoteException; import java.rmi.RMISecurityManager; import java.rmi.registry.Registry; // for port number import java.rmi.server.UnicastRemoteObject; import sdsu.util.ProgramProperties; public class HelloServer extends UnicastRemoteObject implements Hello { public HelloServer() throws RemoteException { } public String sayHello() throws RemoteException { return "Hello World from " + getHostName(); } protected static String getHostName() { try { return InetAddress.getLocalHost().getHostName(); } catch (java.net.UnknownHostException who) { return "Unknown"; } }
//The changed Part
private static String getHelloHostAddress( String args[] ) throws IOException { ProgramProperties flags = new ProgramProperties( args, "config" ); int defaultPort = Registry.REGISTRY_PORT; int port = flags.getInt( "port", defaultPort ); String serverLabel = flags.getString( "label" ); // can only bind to local host, protocol defaults // to local host, do not add a host here return "rmi://" + ":" + port + "/" + serverLabel ; } public static void main(String args[]) { // Create and install a security manager System.setSecurityManager(new RMISecurityManager()); try { String serverAddress = getHelloHostAddress( args ); HelloServer serverObject = new HelloServer(); Naming.rebind( serverAddress, serverObject); System.out.println("HelloServer bound in registry"); } catch (Exception e) { System.out.println("HelloServer err: "); e.printStackTrace(); } } }
Why the Null Server Constructor?
public HelloServer() throws RemoteException { }
HelloServer's parent class constructor throws RemoteException
Since a constructor calls is parent class constructor, RemoteException can be thrown in HelloServer's constructor
So it is a compile error not to either handle the error or explicitly state it will be thrown
Why Hello Interface?
public class HelloClient { public static void main(String args[]) { try { String server = getHelloHostAddress( args); Hello remote = (Hello) Naming.lookup( server );
Naming.lookup() does not return the remote server but a proxy to the remote server
This is an example of programming to an interface!
Design Principle 1
Program to an interface, not an implementation
Use abstract classes (and/or interfaces in Java) to define common interfaces for a set of classes
Declare variables to be instances of the abstract class not instances of particular classes
Benefits of programming to an interface
Proxies & Stubs
How do HelloClient and HelloSever communicate?
Client talks to a Stub that relays the request to the server over a network
Server responds via a skeleton that relays the response to the Client
HelloServer_Stub // Stub class generated by rmic, do not edit. // Contents subject to change without notice. public final class HelloServer_Stub extends java.rmi.server.RemoteStub implements Hello, java.rmi.Remote { private static final long serialVersionUID = 2; private static java.lang.reflect.Method $method_sayHello_0; static { try { $method_sayHello_0 = Hello.class.getMethod("sayHello", new java.lang.Class[] {}); } catch (java.lang.NoSuchMethodException e) { throw new java.lang.NoSuchMethodError( "stub class initialization failed"); } } // constructors public HelloServer_Stub(java.rmi.server.RemoteRef ref) { super(ref); } // methods from remote interfaces // implementation of sayHello() public java.lang.String sayHello() throws java.rmi.RemoteException { try { Object $result = ref.invoke(this, $method_sayHello_0, null, 6043973830760146143L); return ((java.lang.String) $result); } catch (java.lang.RuntimeException e) { throw e; } catch (java.rmi.RemoteException e) { throw e; } catch (java.lang.Exception e) { throw new java.rmi.UnexpectedException("undeclared checked exception", e); } } }
JDK 1.2, Stubs, Skeletons
Rmi in JDK 1.1.x required both stubs and skeletons. When JDK 1.2 rmi clients talk to JDK 1.2 servers, the skeleton is not needed. If you only have JDK 1.2 rmi clients and JDK 1.2 servers you can use the –v1.2 option in the rmic command.
The following command will only generate a stub
The stub only works with JDK 1.2 clients
rmic –v1.2 ServerClassServer Side needs the Stub
The stub is used by the client. If the stub class is not available on the server side, you will get an error when you register the server.
There are ways to have the server download the stub to the client
This is why the server needs access to the stub class.
Some Implications of Proxies and Registering Objects
The following code will give us the above picture
rmiregistry portNumber & javac HelloServer.java rmic HelloServer java HelloServer & javac BankServer.java rmic BankServer java BankServer &
Two semi hidden problems (issues)
RMIRegistry and Stubs
Registry load the stub using
Class.forName( nameHere ).newInstance();
where nameHere is the full class name of the stub
So the stub code must be in the classpath of the registry!!
Several ways to insure this is the case:
Class examples
I use the package whitney.rmi.examples.basic for the examples in the class
On rohan I place them in:
~whitney/languages/java/whitney/rmi/examples/basic
which is the same as:
~whitney/rmi/examples/basicI make sure that my classpath points to the directory containing the whitney directory
Some Package Problems
Sample program - HelloServer
Assume the following is in a file HelloServer.java
package whitney.rmi.examples.basic; //stuff removed public class HelloServer extends UnicastRemoteObject implements Hello { //more stuff removed
To compile:
javac HelloServer.java
To run the rmic compiler:
rmic whitney.rmi.examples.basic.HelloServerTo run HelloServer main to register object:
java whitney.rmi.examples.basic.HelloServer
Some Package Solutions
I alias commands!
For rmic I put in a file rmice:
rmic whitney.rmi.examples.basic.$1Make the file executable and place the file in my bin directory which is in my path
I alias:
java whitney.rmi.examples.basic.$1
for running the programs.
Policy File
The policy file is part of the new Java security model introduced in JDK 1.2 (Java 2)
The general form for SocketPermission is:
grant { permission java.net.SocketPermission "host", "actions"; };
where:
host = (hostname | IPaddress ) [:portrange] portrange = portnumber | -portnumber | portnumber-portnumber | portnumber- actions = action | action,actions action = accept | connect | listen | resolveHost host can be:
Pattern |
Meaning |
* *.edu *.sdsu.edu |
All
machines
All machines in edu domain All machines in the sdsu.edu domain |
Port Range
Pattern |
Meaning |
-N N- N-M N |
ports
N and below
ports N and above ports N through M the port N |
|
Meaning |
accept connect listen resolve |
accept
connections on listed port(s)
connect to listed port(s) listen on listed port(s) resolve host names |
grant { permission java.net.SocketPermission "localhost", "connect,accept,listen"; permission java.net.SocketPermission "rohan.sdsu.edu:1024-", "connect,accept,resolve"; permission java.net.SocketPermission "rohan.sdsu.edu:-1024", "connect"; };
Policy File
Policy files can set a number of permissions beside SocketPermission.
See http://java.sun.com/products/jdk1.2/docs/guide/security/permissions.html for more details.
System wide Policy File
java –Djava.security.policy=policyFileURL anApp
java –Djava.security.policy==policyFileURL anApploads only the specified policy file. No other policy files are loaded. Using this flag we can set a different policy file per server object.
appletviewer –J-Djava.security.policy=policyFileURL appletloads the policy file in the appletviewer.
Policy File Gotcha’sPolicy File Flag not Used
The -Djava.security.policy flag will be ignored if the "policy.allowSystemProperty" in the file {JavaHome}/jre/lib/security/java.security is set to false.
The default value is set to true
Policy Files not Checked
If a security manager is not installed in an application, the policy files will not be checked.
The java flag:
-Djava.security.manager
insures that the default security manager will be installed.
Often you use this flag when specifying a policy file option:
java -Djava.security.manager –Djava.security.policy=~whitney/rmi/policFile myAppNote: The HelloServer class loads a security manager, so we needed a policy file to allow the HelloServer to accept socket connections. The HelloClient class does not load a security manager, so it did not need a policy file to make socket connections
Copyright © 1999 SDSU & Roger Whitney, 5500 Campanile Drive, San Diego, CA 92182-7700 USA.
All rights reserved.
Previous    visitors since 04-Feb-99    Next