 |
CS 696 Emerging Technologies: Distributed Objects |
|
|---|
Spring Semester, 1998
RMI Intro
To Lecture Notes Index
© 1998, All Rights Reserved, SDSU & Roger Whitney
San Diego State University -- This page last updated 21-Apr-98
Contents of Doc 4, RMI Intro
- References
- RMI - Introduction by Example
- A First Program - Hello World
- The Remote Interface
- The Server Implementation
- The Client Code
- Running The Example
- Server Side
- Client Side
- Basic Issues
- Port Contention
- Program Configuration
- Java Command Line Flags
- SDSU Library Tools
- sdsu.util.ProgramProperties
- sdsu.io.LocalRepository
- A Reasonable Implementation
- Why the Null Server Constructor?
- Orphan Processes
- Why Hello Interface?
- Proxies
- Some Implications of Proxies and Registering Objects
JDK API Documentation
Java Remote Method Invocation Specification
Getting Started Using RMI
RMI Source Code
Modified from "Getting Started Using RMI"
public interface Hello extends java.rmi.Remote
{
String sayHello() throws java.rmi.RemoteException;
}
// Required for Remote Implementation
import java.rmi.*;
import java.rmi.server.UnicastRemoteObject;
// Used in method getUnixHostName
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class HelloServer
extends UnicastRemoteObject
implements Hello
{
public HelloServer() throws RemoteException
{
}
// The actual remote sayHello
public String sayHello() throws RemoteException
{
return "Hello World from " + getUnixHostName();
}
// Works only on UNIX machines
protected String getUnixHostName()
{
try
{
Process hostName;
BufferedReader answer;
hostName = Runtime.getRuntime().exec( "hostname" );
answer = new BufferedReader(
new InputStreamReader(
hostName.getInputStream()) );
hostName.waitFor();
return answer.readLine().trim();
}
catch (Exception noName)
{
return "Nameless";
}
}
// Main that registers with Server with Registry
public static void main(String args[])
{
// Create and install a security manager
System.setSecurityManager(new RMISecurityManager());
try
{
HelloServer serverObject = new HelloServer ();
Naming.rebind("//roswell.sdsu.edu/HelloServer",
serverObject );
System.out.println("HelloServer bound in registry");
}
catch (Exception error)
{
System.out.println("HelloServer err: ");
error.printStackTrace();
}
}
}
import java.rmi.*;
import java.net.MalformedURLException;
public class HelloClient
{
public static void main(String args[])
{
try {
Hello remote = (Hello) Naming.lookup(
"//roswell.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
Step 1. Compile the source code
- Server side needs interface Hello and class HelloServer
- javac Hello.java HelloServer.java
-
Step 2. Generate Stubs and Skeletons (to be explained later)
The rmi compiler generates the stubs and skeletons
- rmic HelloServer
This produces the files:
- HelloServer_Skel.class
- HelloServer_Stub.class
The Stub is used by the client and server
The Skel is used by the server
The normal command is:
- rmic fullClassname
Step 3. Insure that the RMI Registry is running
For the default port number
- rmiregistry &
For a specific port number
- rmiregistry portNumber &
On a UNIX machine the rmiregistry will run in the background and will continue
to run after you log out
This means you manually kill the rmiregistry
Step 4. Register the server object with the rmiregistry by running
HelloServer.main()
- java HelloServer &
Important the above command will create a new thread that will not stop
until you kill it!
This can be done at the command level (kill in UNIX) or by the object it
self.
The client can be run on the same machine or a different machine than the
server
Step 1. Compile the source code
- Client side needs interface Hello and class HelloClient
- javac Hello.java HelloClient.java
Step 2. Make the HelloServer_Stub.class is available
- Either copy the file from the server machine
-
- or
-
- Compile HelloServer.java on client machine and rum rmic
Step 3. Run the client code
- java HelloClient
Only one server can use a port at a time!
You will need to find a port that is unused for your server!
Try using your uid
The command "id" will give you your uid
Ports you use must be in the range: 5001-65536
Location of server object is hard coded into both server and client
Hello remote = (Hello) Naming.lookup(
"//roswell.sdsu.edu/HelloServer");
Naming.rebind("//roswell.sdsu.edu/HelloServer", serverObject);
This is typical student/teacher code, but is still bad!
Use command line flags and configuration files!
Java uses Properties for configuring a program
java.util.Properties
- A hashtable that stores key-value pairs
-
- Value is accessed via the key
Command line key-value Property pairs
Start a program with the following line:
java -Dkey1=value1 -Dkey2=value2 className
Then the key-value pairs are added to the System properties
Then anywhere in the program the pairs can be accessed via:
String value = System.getProperty( key);
String defaultValue = "Hi Mom";
value = System.getProperty( key, defaultValue);
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 = "roswell.sdsu.edu";
String host = options.getString( "host", defaultHost );
int port = options.getInt( "port", 1099 );
System.out.println( "host: " + host + " port: " + port );
}
}
Maps persistent objects to keys
Objects are stored in files
File names are:
- key.ObjectType
Currently supports:
- String, Properties,
- LabeledData, Table, LabeledTable, List, Stringizable
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" );
}
}
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 );
String host = flags.getString( "host" );
int defaultPort = Registry.REGISTRY_PORT;
int port = flags.getInt( "port", defaultPort );
return "rmi://" + host + ":" + port + "/HelloServer";
}
}
The Server
import java.rmi.*;
import java.rmi.server.UnicastRemoteObject;
import java.rmi.registry.Registry; // for port number
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import sdsu.util.ProgramProperties;
public class HelloServer
extends UnicastRemoteObject
implements Hello
{
public HelloServer() throws RemoteException
{ }
public String sayHello() throws RemoteException
{
return "Hello World from " + getUnixHostName();
}
//The Server Continued
protected String getUnixHostName()
{
try
{
Process hostName;
BufferedReader answer;
hostName = Runtime.getRuntime().exec( "hostname" );
answer = new BufferedReader(
new InputStreamReader(
hostName.getInputStream()) );
hostName.waitFor();
return answer.readLine().trim();
}
catch (Exception noName)
{
return "Nameless";
}
}
//The changed Part
private static String getHelloHostAddress( String args[] )
throws IOException
{
ProgramProperties flags = new ProgramProperties( args );
int defaultPort = Registry.REGISTRY_PORT;
int port = flags.getInt( "port", defaultPort );
// can only bind to local host, protocol defaults
// to local host, do not add a host here
return "rmi://" + ":" + port + "/HelloServer";
}
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();
}
}
}
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
rmiregistry:
- creates a process that runs in the background
- the process continues after you log out
- uses a TCP port
In the simple example each server object
- creates a process that runs in the background
- the process continues after 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
to make it easier to use I alias this command by putting the following in my
.cshrc file:
alias pps '/usr/bin/ps -o pid,stime,comm -uwhitney'
Use the kill command to kill process
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
- Client classes/objects remain unaware of the classes of objects they use,
as long as the objects adhere to the interface the client expects
- Client classes/objects remain unaware of the classes that implement these
objects. Clients only know about the abstract classes (or interfaces) that
define the interface.
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 java.rmi.server.Operation[] operations = {
new java.rmi.server.Operation("java.lang.String sayHello()")
};
private static final long interfaceHash =
6486744599627128933L;
// Constructors
public HelloServer_Stub() {
super();
}
public HelloServer_Stub(java.rmi.server.RemoteRef rep) {
super(rep);
}
HelloServer_Stub Continued
// Methods from remote interfaces
// Implementation of sayHello
public java.lang.String sayHello() throws
java.rmi.RemoteException {
int opnum = 0;
java.rmi.server.RemoteRef sub = ref;
java.rmi.server.RemoteCall call = sub.newCall(
(java.rmi.server.RemoteObject)this,
operations, opnum, interfaceHash);
try {
sub.invoke(call);
} catch (java.rmi.RemoteException ex) {
throw ex;
} catch (java.lang.Exception ex) {
throw new java.rmi.UnexpectedException(
"Unexpected exception", ex);
};
java.lang.String $result;
try {
java.io.ObjectInput in = call.getInputStream();
$result = (java.lang.String)in.readObject();
} catch (java.io.IOException ex) {
throw new java.rmi.UnmarshalException("Error unmarshaling return", ex);
} catch (java.lang.ClassNotFoundException ex) {
throw new java.rmi.UnmarshalException("Return value class not found", ex);
} catch (Exception ex) {
throw new java.rmi.UnexpectedException("Unexpected exception", ex);
} finally {
sub.done(call);
}
return $result;
}
}
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)
- How does RMIRegistry find skeletons?
-
- Running a new VM for each server object
RMIRegistry and Skeletons
Registry load the skeleton using
Class.forName( nameHere ).newInstance();
where nameHere is the full class name of the skeleton
So the skeleton code must be in the classpath of the registry!!
Several way to insure this is the case:
- 1. Start the rmiregistry in a directory that contains all the code for all
server objects
-
- 2. Use package and insure your classpath points to the package
location
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/basic
I
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.HelloServer
To
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.$1
Make
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.
Reducing the Number of VMs
Use
- sdsu.rmi.registry.UniVMRegistry
- sdsu.rmi.registry.Registrar
UniVMRegistry will load objects directly into its VM
Sample usage
1. Run rmiregistry
rmiregistry 7654 &
2.
Run UniVMRegistry's main
java sdsu.rmi.registry.UniVMRegistry -p=7654 &
3.
Use Registrar to register objects with UniVMRegistry
int port = 7654;
String serverClass = "whitney.rmi.examples.basic.SingleHelloServer";
String nameList =
Registrar.verboseRebind( port, "HelloServer", serverClass);
System.out.println( nameList );
or
Registrar aRegistrar = new Registrar( "rohan", port );
aRegistrar.rebind( "HelloServer", serverClass );
visitors since 03-Feb-98