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.*;
import java.io.Serializable;

public class WeatherInfo implements Serializable {
   private String cityName;
   private String temperature;
   private String description;

   public WeatherInfo( String city, String desc, String temp )
   {
      cityName = city;
      temperature = temp;
      description = desc;
   }

   public String getCityName() { return cityName; }

   public String getTemperature() { return temperature; }

   public String getDescription() { return description; }
}

Define the Client

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

Note: you will need to find out were rmiregistry is located, also the default port for the rmiregistry is 1099

To start the process running we need to start the server, then the client

Running the application

## Server
c:\> java TemperatureServerImpl

## Client
c:\> java TemperatureClient

## Client using a specific IP address for the server
c:\> java TemperatureClient 192.168.0.1

Note: make sure the stub file is in the classpath for the client