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 |