Java Seminaries - RMI Socket Factories

This document assumes that you are using Java 2 SDK, v1.2 or later. ... RMISocketFactory which is abstract and implements both interfaces. Just be aware that ...
79KB taille 5 téléchargements 320 vues
Java Seminaries - RMI Socket Factories Michael Rauch Thomas Bähler Aim of this document is to explain under what circumstances a Custom RMI Socket Factory may be useful to you and what steps you have to perform to actually customize the socket used by the RMI system. To achieve this we will have a look at the interfaces RMIClientSocketFactory and RMIServerSocketFactory. Implementation details will be shown with examples. We will further show you how to create a custom pair of sockets and use them in your application. Accompanying this document are two code examples, one will show you how to use RMI over SSL and the other one will show you how to write custom Socket implementations to count the number of bytes that are sent and received.

1. Motivation By default Java RMI uses the TCP transport layer provided by java.net.Socket for the communication between the remote object and its client. Using your own RMI socket factory allows the RMI transport layer to use a non-TCP or custom transport protocol over IP. Different types of sockets can be used on a per-object basis. Two common reasons for using custom socket factories: •

sensible data is transfered between client and server, data encryption is desired



big chunks of data are transfered between client and server, data compression is desired

This document assumes that you are using Java 2 SDK, v1.2 or later. The example involving SSL works with the Java Secure Socket Extension (JSSE) that is part of the SDK since v1.4.

1

Java Seminaries - RMI Socket Factories

2. Create a RMI Socket Factory Perform the following three steps: 1. Decide upon the type of socket to be used 2. Write a client-side socket factory that implements java.rmi.server.RMIClientSocketFactory 3. Write a server-side socket factory that implements java.rmi.server.RMIServerSocketFactory Instead of writing two separate socket factories (step 2. & 3.) you can also extend java.rmi.server.RMISocketFactory which is abstract and implements both interfaces. Just be aware that in case you use dynamic class loading to provide the client with the factory, it may be better to create two separate factories to save some network bandwith.

2.1. Decide upon the type of socket to be used Depending on what you want to accomplish (e.g.: data compression, encryption, etc.) you may use an already existing socket or create your own custom socket. Refer to Section 4 to see how to create your own socket.

2.2. Write a client-side socket factory Implement java.rmi.server.RMIClientSocketFactory to return the desired type of socket: public interface RMIClientSocketFactory { public Socket createSocket(String host, int port) throws IOException; }

Note: Don’t forget to implement Serializable in your factory! The stub that is transfered to the client contains a serialized version of the factory.

2.3. Write a server-side socket factory Implement java.rmi.server.RMIServerSocketFactory to return the desired type of server socket: public interface RMIServerSocketFactory { public ServerSocket createServerSocket(int port) throws IOException; }

The following example shows how to combine the client and server socket factory in one class by extending from RMISocketFactory.

2

Java Seminaries - RMI Socket Factories Example 1. Implementation of SecureRMISocketFactory import import import import import

java.rmi.*; java.rmi.server.*; java.net.*; java.io.*; javax.net.ssl.*;

public class SecureRMISocketFactory extends RMISocketFactory implements Serializable { public Socket createSocket(String host, int port) throws IOException { return SSLSocketFactory.getDefault().createSocket(host, port); } public ServerSocket createServerSocket (int port) throws IOException { return SSLServerSocketFactory.getDefault().createServerSocket(port); } }

Check out [SUN3] on how to setup your SSL environment properly to run the SSL sample code. Especially make sure you have a keystore on the server side and a truststore on the client side and call the client and server with appropriate arguments. See commands.txt that comes with our examples for hints.

3. Make your application use your custom socket factory Perform the following two steps: 1. In the remote object implementation, write a constructor that calls the UnicastRemoteObject (or Activatable) constructor that takes RMIClientSocketFactory and RMIServerSocketFactory as parameters 2. Adjust your java.security.policy file to allow your program to create sockets.

The following code shows the interesting parts in UnicastRemoteObject: public class UnicastRemoteObject extends RemoteServer { ... protected UnicastRemoteObject(int port, RMIClientSocketFactory csf, RMIServerSocketFactory ssf) throws RemoteException { ... } ... public static Remote exportObject(Remote obj, int port, RMIClientSocketFactory csf, RMIServerSocketFactory ssf)

3

Java Seminaries - RMI Socket Factories throws RemoteException { ... } ... }

As you can guess from the code excerpt above you can also use the method exportObject(...) giving the factories as parameters if you don’t want to extend from UnicastRemoteObject. Example 2. Make your remote object use custom socket factories import java.rmi.*; import java.util.*; import java.rmi.server.*; public class AccountImpl extends UnicastRemoteObject implements Account { public AccountImpl() throws RemoteException{ super(); } public AccountImpl(RMISocketFactory factory) throws RemoteException { super(0,factory,factory); } public void setCreditCardNr(String str) { /* do something useful with the credit card information */ } public List getLastTransactions() { ArrayList array = new ArrayList(); array.add("01.01.2002 + Sfr. 500.00"); array.add("02.01.2002 + Sfr. 50.00"); array.add("03.01.2002 - Sfr. 700.00"); array.add("04.01.2002 + Sfr. 200.00"); array.add("05.01.2002 - Sfr. 400.00"); array.add("06.01.2002 + Sfr. 0.10"); array.add("06.01.2002 - Sfr. 20.00"); array.add("07.01.2002 + Sfr. 10.00"); return array; } }

4

Java Seminaries - RMI Socket Factories

4. Create a custom pair of sockets 4.1. Overview Figure 1. Overview

4.2. Step-by-step instructions The transmitted data is only accessible in the read and write methods of the streams. That means, if you want to manipulate the data, you have to write your own stream classes. Perform the following five steps: 1. Extend an InputStream class. Overwrite: read() and read(byte buf [], int off, int len )

2. Extend an OutputStream class. Overwrite: write(int b) and write(byte buf [], int off, int len)

3. Extend a Socket class. Overwrite: getInputStream() and getOutputSteram()

4. Extend a ServerSocket class. Overwrite: accept()

5. Write your own RMISocketFactory which returns your Sockets.

5

Java Seminaries - RMI Socket Factories Example 3. Custom InputStream : MacigInputStream public class MagicInputStream extends FilterInputStream { ....... public MagicInputStream(InputStream out){ super (out); } public int read() throws IOException{ int char = super.read(); modify(char); return char; } public int read(byte buf [], int off, int len) throws IOException{ int number_of_characters = super.read(buf,off,len); modify(buf); return number_of_characters; } }

Example 4. Custom OutputStream : MagicOutputStream public class MagicOutputStream extends FilterOutputStream{ ..... public MagicOutputStream(OutputStream out){ super (out); } public void write (int b) throws IOException { modify(b); super.write(b); }

public void write(byte buf [], int off, int len) throws IOException{ //copy buffer

6

Java Seminaries - RMI Socket Factories modify(buf_copy); super.write(buf,off,len); } }

Example 5. Custom Socket : MagicSocket public class MagicSocket extends Socket{ ..... public MagicSocket()throws IOException { super(); } public MagicSocket(String host, int port) throws IOException{ super(host,port); } public OutputStream getOutputStream()throws IOException{ if (out == null) out = new MagicOutputStream(super.getOutputStream()); return out; } public InputStream getInputStream()throws IOException{ if (in == null) in = new MagicInputStream(super.getInputStream()); return in; } public synchronized void close() throws IOException { getOutputStream().flush(); super.close(); } }

Example 6. Custom ServerSocket : MagicServerSocket public class MagicServerSocket extends ServerSocket{ ...... public MagicServerSocket(int port) throws IOException{ super (port); } public Socket accept() throws IOException {

7

Java Seminaries - RMI Socket Factories Socket s = new MagicSocket(); implAccept(s); return s; } }

5. Make the rmiregistry use custom sockets Figure 2. Registry lookup

Problem : We already know, that the client first does the lookup (1), then loads stubs and other classes (2) and finally he uses the rmi object(3). The client-side socket factory will be serialized and instantiated as soon as the client deserializes the stub. But you have to remember that our custom sockets will not be used in step 1 and 2 of course. Solution : If you want the rmiregistry to use your sockets, use these methods. But remember, your client must be able to access the RMIClientSocketFactory class, you cannot use dynamic class loading. Example 7. Make the rmiregistry use custom sockets LocateRegistry.createRegistry( int port, RMIClientSocketFactory csf, RMIServerSocketFactory ssf) LocateRegistry.getRegistry(

String host, int port, RMIClientSocketFactory csf)

8

Java Seminaries - RMI Socket Factories

Bibliography [SUN1] Sun Microsystems, Creating a Custom RMI Socket Factory (http://java.sun.com/j2se/1.3/docs/guide/rmi/rmisocketfactory.doc.html). [SUN2] Sun Microsystems, Creating a Custom Socket Type (http://java.sun.com/j2se/1.3/docs/guide/rmi/sockettype.doc.html). [SUN3] Sun Microsystems, Java Secure Socket Extension (JSSE) Reference Guide (http://java.sun.com/j2se/1.4/docs/guide/security/jsse/JSSERefGuide.html#CreateKeystore).

9