Remote Method Invocation (RMI)
RMI allows Java objects running on the same or separate computers to communicate with one another via remote method calls. Such method calls appear the same as those operating on objects in the same program. RMI is based on an earlier technology called remote procedure calls (RPC), the goal of this technology was to make transparent the remote calls over the network to other computers. RMI is Java representation of RPC, once a method (or service) of a Java object is registered as being remotely accessible, a client can "look up" that service and receive a reference that allows the client to use that service (call the method). RMI provides for transfer of objects of complex data types via the object serialization mechanism.
Defining and Implementing the Remote Interface
The first step in creating a client/server distributed application with RMI is to define the remote interface that describes the remote methods, which the client will use to interact with the remote server object through RMI. To create a remote interface, define an interface that extends interface Remote (package java.rmi). Interface Remote is a tagging interface, it does not dclare any methods, and so places no burden on the implementing class. An object of a class that implements interface Remote directly or indirectly is a remote object - security permitting - from any JVM that has a connection to the computer on which the remote object executes.
| RMI Server example | // TemperatureServer Class
import java.rmi.*;
public interface TemperatureServer extends Remote {
public WeatherInfo[] getWeatherInfo() throws RemoteException;
}
// TemperatureServerImpl Class
import java.rmi.*;
import java.rmi.server.*;
import java.util.*;
import java.io.*;
import java.net.*;
public class TemperatureServerImpl extends UnicastRemoteObject implements TemperatureServer {
private WeatherInfo weatherInformation[];
public TemperatureServerImpl() throws RemoteException
{
super();
updateWeatherConditions();
}
// get weather information from NWS
private void updateWeatherConditions() throws RemoteException {
try {
System.err.println(
"Updating weather information..." );
// Traveler's Forecast Web Page
URL url = new URL( "http://iwin.nws.noaa.gov/iwin/us/traveler.html" );
BufferedReader in = new BufferedReader( new InputStreamReader( url.openStream() ) );
String separator = "TAV12";
// locate first horizontal line on Web page
while ( !in.readLine().startsWith( separator ) ); // do nothing
// s1 is the day format and s2 is the night format
String s1 ="CITY WEA HI/LO WEA HI/LO";
String s2 ="CITY WEA LO/HI WEA LO/HI";
String inputLine = "";
// locate header that begins weather information
do {
inputLine = in.readLine();
} while ( !inputLine.equals( s1 ) && !inputLine.equals( s2 ) );
Vector cityVector = new Vector();
inputLine = in.readLine(); // get first city's info
while ( inputLine.length() > 28 ) {
// create WeatherInfo object for city
WeatherInfo w = new WeatherInfo(
inputLine.substring( 0, 16 ),
inputLine.substring( 16, 22 ),
inputLine.substring( 23, 29 ) );
cityVector.addElement( w ); // add to Vector
inputLine = in.readLine(); // get next city's info
}
// create array to return to client
weatherInformation = new WeatherInfo[ cityVector.size() ];
for ( int i = 0; i < weatherInformation.length; i++ )
weatherInformation[ i ] = ( WeatherInfo ) cityVector.elementAt( i );
System.err.println( "Finished Processing Data." );
in.close(); // close connection to NWS server
}
catch( java.net.ConnectException ce ) {
System.err.println( "Connection failed." );
System.exit( 1 );
}
catch( Exception e ) {
e.printStackTrace();
System.exit( 1 );
}
}
// implementation for TemperatureServer interface method
public WeatherInfo[] getWeatherInfo()
{
return weatherInformation;
}
public static void main( String args[] ) throws Exception
{
System.err.println(
"Initializing server: please wait." );
// create server object
TemperatureServerImpl temp = new TemperatureServerImpl();
// bind TemperatureServerImpl object to the rmiregistry default port 1099
String serverObjectName = "//localhost/TempServer";
Naming.rebind( serverObjectName, temp );
System.err.println( "The Temperature Server is up and running." );
}
}
|
| WeatherInfo definition | // WeatherInfo Class import java.rmi.*; public class WeatherInfo implements Serializable { public WeatherInfo( String city, String desc, String temp ) public String getCityName() { return cityName; } public String getTemperature() { return temperature; } public String getDescription() { return description; } |
The client is defined as the following
| RMI Client example | // TemperatureClient Class
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.rmi.*;
public class TemperatureClient extends JFrame
{
public TemperatureClient( String ip )
{
super( "RMI TemperatureClient..." );
getRemoteTemp( ip );
setSize( 625, 567 );
setResizable( false );
show();
}
// obtain weather information from TemperatureServerImpl remote object
private void getRemoteTemp( String ip )
{
try {
// name of remote server object bound to rmi registry
String serverObjectName = "//" + ip + "/TempServer";
// lookup TemperatureServerImpl remote object in rmiregistry
TemperatureServer mytemp = ( TemperatureServer ) Naming.lookup( serverObjectName );
// get weather information from server
WeatherInfo weatherInfo[] = mytemp.getWeatherInfo();
WeatherItem w[] = new WeatherItem[ weatherInfo.length ];
ImageIcon headerImage = new ImageIcon( "images/header.jpg" );
JPanel p = new JPanel();
// determine number of rows for the GridLayout;
// add 3 to accommodate the two header JLabels
// and balance the columns
p.setLayout( new GridLayout( ( w.length + 3 ) / 2, 2 ) );
p.add( new JLabel( headerImage ) ); // header 1
p.add( new JLabel( headerImage ) ); // header 2
for ( int i = 0; i < w.length; i++ ) {
w[ i ] = new WeatherItem( weatherInfo[ i ] );
p.add( w[ i ] );
}
getContentPane().add( new JScrollPane( p ), BorderLayout.CENTER );
}
catch ( java.rmi.ConnectException ce ) {
System.err.println( "Connection to server failed. " + "Server may be temporarily unavailable." );
}
catch ( Exception e ) {
e.printStackTrace();
System.exit( 1 );
}
}
public static void main( String args[] )
{
TemperatureClient gt = null;
// if no sever IP address or host name specified,
// use "localhost"; otherwise use specified host
if ( args.length == 0 )
gt = new TemperatureClient( "localhost" );
else
gt = new TemperatureClient( args[ 0 ] );
gt.addWindowListener(
new WindowAdapter() {
public void windowClosing( WindowEvent e )
{
System.exit( 0 );
}
}
);
}
}
|
| WeatherItem Definition | // WeatherItem Class
import java.awt.*;
import javax.swing.*;
public class WeatherItem extends JLabel {
private static ImageIcon weatherImages[], backgroundImage;
private final static String weatherConditions[] =
{ "SUNNY", "PTCLDY", "CLOUDY", "MOCLDY", "TSTRMS",
"RAIN", "SNOW", "VRYHOT", "FAIR", "RNSNOW",
"SHWRS", "WINDY", "NOINFO", "MISG" };
private final static String weatherImageNames[] =
{ "sunny", "pcloudy", "mcloudy", "mcloudy", "rain",
"rain", "snow", "vryhot", "fair", "rnsnow",
"showers", "windy", "noinfo", "noinfo" };
// static initializer block to load weather images
static {
backgroundImage = new ImageIcon( "images/back.jpg" );
weatherImages = new ImageIcon[ weatherImageNames.length ];
for ( int i = 0; i < weatherImageNames.length; ++i )
weatherImages[ i ] = new ImageIcon( "images/" + weatherImageNames[ i ] + ".jpg" );
}
// instance variables
private ImageIcon weather;
private WeatherInfo weatherInfo;
public WeatherItem( WeatherInfo w )
{
weather = null;
weatherInfo = w;
// locate image for city's weather condition
for ( int i = 0; i < weatherConditions.length; ++i )
if ( weatherConditions[ i ].equals( weatherInfo.getDescription().trim() ) ) {
weather = weatherImages[ i ];
break;
}
// pick the "no info" image if either there is no
// weather info or no image for the current
// weather condition
if ( weather == null ) {
weather = weatherImages[ weatherImages.length - 1 ];
System.err.println( "No info for: " + weatherInfo.getDescription() );
}
}
public void paintComponent( Graphics g )
{
super.paintComponent( g );
backgroundImage.paintIcon( this, g, 0, 0 );
Font f = new Font( "SansSerif", Font.BOLD, 12 );
g.setFont( f );
g.setColor( Color.white );
g.drawString( weatherInfo.getCityName(), 10, 19 );
g.drawString( weatherInfo.getTemperature(), 130, 19 );
weather.paintIcon( this, g, 253, 1 );
}
// make WeatherItem's preferred size the width and height of
// the background image
public Dimension getPreferredSize()
{
return new Dimension( backgroundImage.getIconWidth(),
backgroundImage.getIconHeight() );
}
}
|
Compile and Execute the client
Now we have all the code we need to build the application, this involves several steps, First the classes must be compiled using javac. Next, the remote server class (TemperatureServerImpl) must be compiled using the rmic compiler to produce a stub class. An object of the stub class allows the client to invoke the server object's remote methods. The stub object receives each remote method call and passes it to the Java RMI system which performs the networking that allows the client to connect to the server and interact with the remote server object.
| Creating the stub | rmic -v1.2 TemperatureServerImpl Note: you must have your classpath set, the command above will produce a file TemperatureServerImpl_Stub.class |
The file TemperatureServerImpl_Stub.class file must be available to the client either locally or via download, to enable remote communication with the server object.
Next we need to start the rmiregistry, so that the object can register itself.
| Register the object | c\:>rmiregistry 1099 c\:> netstat -an ## check for listening port 1099 |
To start the process running we need to start the server, then the client
| Running the application | ## Server ## Client using a specific IP address for the server Note: make sure the stub file is in the classpath for the client |