RMI, RMI over SSL, RMI-IIOP, RMI-IIOP over SSL

Index:
RMI
 
 
    分布式系统(比如W/S应用)要求应用能够跨网络通信,应需而生了各种方法。
 
a) 最基础的当然是Socket,Server创建一个ServerSocket在某个Port上监听,Client应用通过指定Server Host和Port创建ClientSocket与Server端建立通信,达到远程通信、传送数据的目的。Socket通信没有应用层支持,要求Client和Server自己定义通信的协议(或者说规则更好理解)来编/解码通信内容,确保双方能够理解对方在说什么。
 
b) 为便利应用层程序开发对Socket通信进行封装就出现了 远程过程调用RPC(Remote Procedure Call),程序员不用再去解析从Socket来的字节流,只需要传递参数调用远程函数接口,就像调用本地函数一样,同样能得到想要的返回值。由RPC系统负责参数和返回值的序列化和网络传输,但底层通信原理应该还是Socket。RPC是基于C的,也就是结构化程序设计的,所以是调用远程服务器export出来的函数接口。
 
c) 对Java来说就是远程方法调用RMI(Remote Method Invocation),虽然叫方法调用但我们知道Java是面向对象的,所以RMI也是面向对象的。通过RMI,Client应用可以拿到远程对象的引用(Stub),通过这个引用调用远程对象的方法,并且可以通过这些方法返回其他远程对象的引用,从而Java世界的远程通信变得异常容易。当然,RMI的通信基础也是Socket,只是由RMI系统封装了作为参数和返回值的对象的序列化,也封装了连接建立的过程。
 
d) 以上abc都是对程序员说的,对用户来说网络都是被应用封装的,用户感知的应该是功能和业务。上午刚听了个云计算的Session,个人觉得这个对用户影响不大,不管服务来自一个Server还是一朵云,用户是不知道的也不需要关心。对应用层程序员来说好像影响也不大,对IT运维的兄弟们估计有不小影响吧。
 
Q:远程对象的引用,也就是所谓的Stub是可以传递的吗?
A:是的,Stub中包含有建立连接和呼叫所需的所有信息(Server的hostname/IP,port),可以把Stub传给其他Client(文件等方式),其他Client获得引用后同样可以发起Romote Method Call
 
Q:Client如何拿到远程对象的引用?
A:可以想象网络中应该有一个Client和Server都知道的注册中心(Registry),Server将远程对象(Remote Object)在Registry注册,即与某个表示“名字”的字符串绑定,而Client就按“名字”在Registry中查找并获得远程对象的引用,感觉来讲就是一个Naming Service。Java自带的注册服务器叫rmiregistry。
 
注意:RMI Registry只允许本地的Java App绑定对象,换句话说不允许远程注册到RMI Registry。但聪明的人早就找到了work-around,可以向Registry注册一个服务对象,提供一个Remote方法,比如叫proxyBind,接受远程对象的引用(Stub)做参数,功能是绑定这个Stub到本地Registry。这样远程App就可以调用这个接口来绑定想绑定的对象了。
 
 
e.g.
 
a) 定义远程接口。远程对象的类必须直接或间接实现至少一个远程接口。
//接口1
package net.gy.java.rmi;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface RMIRemoteInterface extends Remote {

  public String sayHi() throws RemoteException;  //远程方法必须申明抛出RemoteException
  public String sayHello(String str) throws RemoteException;

}
 
//接口2
package net.gy.java.rmi;

import java.rmi.AlreadyBoundException;
import java.rmi.Remote;
import java.rmi.RemoteException;

public interface ProxyBinderInterface extends Remote {

  public void proxyBind(String label, Remote stub) throws RemoteException, AlreadyBoundException;

}
 
b) 实现远程接口、构造远程对象、启动Registry、在Registry中注册远程对象
package net.gy.java.rmi; 

import java.rmi.AlreadyBoundException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
//实现远程接口 -- 远程对象类1
public class RMIServer implements RMIRemoteInterface {

   public static final String PROXY_BINDER = "PROXY_BINDER";
   public static final String HOST = "solaris330";
   public static final int RMI_PORT = 2222;

   public RMIServer() {}

  @Override
   public String sayHi() {
     return "Hello World!";
  }

  @Override
   public String sayHello(String str) throws RemoteException {
     return "Hi " + str;
  }

   public static void main(String args[]) {

     try {
       int port = (args.length < 1) ? RMI_PORT : Integer.valueOf(args[ 0]);

      RMIServer obj = new RMIServer();
      RMIRemoteInterface stub = (RMIRemoteInterface) UnicastRemoteObject
          .exportObject(obj, 0);

       // Bind the remote object's stub in the registry

       // RMI registry not allow remote hosts to bind
      // Registry registry = LocateRegistry.getRegistry(HOST, port);
       /*
       *启动Registry,也可以命令行启动Java自带的rmiregistry,默认端口是1099
       *可以在启动时指定端口,e.g. rmiregistry 2222&
       */
      Registry registry = LocateRegistry.createRegistry(port);
      registry.bind( "newHello", stub);
 
      // 继承UnicastRemoteObject的远程对象可以直接绑定,不用显式生成stub;
      registry.bind(PROXY_BINDER, obj. new ProxyBinder());

      System.err.println( "Server ready");
    } catch (Exception e) {
      System.err.println( "Server exception: " + e.toString());
      e.printStackTrace();
    }
  }
//实现远程接口 -- 远程对象类2
   class ProxyBinder extends UnicastRemoteObject implements ProxyBinderInterface {

     private static final long serialVersionUID = - 2373753944757892323L;

     protected ProxyBinder() throws RemoteException {
       super();
    }
    /**
    *注册远程对象到本地Registry
    */
    @Override
     public void proxyBind(String label, Remote stub) throws RemoteException, AlreadyBoundException{
      Registry registry = LocateRegistry.getRegistry(HOST, RMI_PORT);
      registry.bind(label, stub);
    }
  }
}
 
//另一个Server实现,RMI调用远程对象ProxyBinder的方法注册远程对象到Registry
package net.gy.java.rmi;

import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;

public class RemoteRMIServer implements RMIRemoteInterface {

  public static final String HOST = "solaris330";
  public static final int RMI_PORT = 2222;

  public RemoteRMIServer() {}

  @Override
  public String sayHi() {
    return "Hello World!";
  }
  
  @Override
  public String sayHello(String str) throws RemoteException {
    return "Hi " + str;
  }

 
  public static void main(String args[]) {

    try {
      int port = (args.length < 1) ? RMI_PORT : Integer.valueOf(args[ 0]);
      RemoteRMIServer obj = new RemoteRMIServer();
      RMIRemoteInterface stub = (RMIRemoteInterface) UnicastRemoteObject
          .exportObject(obj, 0);
      
      //找到Registry,并从中查找得到ProxyBinder的引用
      Registry registry = LocateRegistry.getRegistry(HOST, port);
      ProxyBinderInterface proxyBinder = (ProxyBinderInterface)registry.lookup(RMIServer.PROXY_BINDER);
      //通过ProxyBinder注册stub到远程Registry
      proxyBinder.proxyBind( "remoteServer", stub);
      System.err.println( "Remote Server ready");

    } catch (Exception e) {
      System.err.println( "Server exception: " + e.toString());
      e.printStackTrace();
    }
  }
}
 
c)Client端应用

                 
                 
package net.gy.java.rmi;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RMIClient {
  
   public static void main(String[] args) {
    
     if (args.length < 1) {
      System.err.println( "Usage: RMIClient  [port]");
      System.exit( 1);
    }
    String host = args[ 0];
     int port = RMIServer.RMI_PORT;
     if (args.length == 2) {
      port = Integer.valueOf(args[ 1]); 
    }

     try {
        Registry registry = LocateRegistry.getRegistry(host, port);
        RMIRemoteInterface stub = (RMIRemoteInterface) registry.lookup( "remoteServer");
        
        FileOutputStream fos = new FileOutputStream( "c:\\t.tmp");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(stub);
        oos.close();

        String response = stub.sayHi();
        System.out.println( "sayHi: " + response);
        System.out.println( "sayHello: " + stub.sayHello( "Java world"));
    } catch (Exception e) {
        System.err.println( "Client exception: " + e.toString());
        e.printStackTrace();
    }
}
  
   public static void main2(String[] args) {
    
     try {
      FileInputStream fis = new FileInputStream( "c:\\t.tmp");
      ObjectInputStream ois = new ObjectInputStream(fis);
      RMIRemoteInterface stub = (RMIRemoteInterface) ois.readObject();
      
      String response = stub.sayHi();
      System.out.println( "response: " + response);
      
    } catch(Exception e) {
      System.err.println( "Client exception: " + e.toString());
      e.printStackTrace();
    }
  
  }
}
 
 
 
有两种情形需要考虑,一是从Registry查询获得Stub的过程需要Secured by SSL,另外就是拿到Stub后通过Stub进行远程方法调用时需要SSL。本质上这两种情形的需求是一样的,就是把原来的Socket编程Secure Socket。
接口:
LocateRegistry:
     createRegistry (int port, RMIClientSocketFactory  csf, RMIServerSocketFactory  ssf)
     getRegistry ( String  host, int port, RMIClientSocketFactory  csf)
 
UnicastRemoteObject:
     UnicastRemoteObject (int port, RMIClientSocketFactory  csf, RMIServerSocketFactory  ssf) //构造方法
     exportObject ( Remote  obj, int port, RMIClientSocketFactory  csf, RMIServerSocketFactory  ssf)
 
RMISocketFactory: setSocketFactory ( RMISocketFactory  fac)
 
java.rmi.server.RMIServerSocketFactory
java.rmi.server.RMIClientSocketFactory  
注意:client socket factory的实现必须同时实现java.io.Serializable接口,这样这个socket factory才能被序列化并包含到Stub中
 
e.g.
package net.gy.java.rmi.ssl; 

import java.io.IOException;
import java.net.ServerSocket;
import java.rmi.server.RMIServerSocketFactory;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocketFactory;

public class MySSLRMIServerSocketFactory implements RMIServerSocketFactory {

   private SSLServerSocketFactory sf = null;

   public MySSLRMIServerSocketFactory() {
     try {
      sf = getSSLServerSocketFactory();
    } catch (UnrecoverableKeyException e) {
       // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (KeyManagementException e) {
       // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (KeyStoreException e) {
       // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
       // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (CertificateException e) {
       // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (IOException e) {
       // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
  @Override
   public ServerSocket createServerSocket( int port) throws IOException {
     // TODO Auto-generated method stub
    ServerSocket ss = sf.createServerSocket(port);
     return ss;
  }

   private SSLServerSocketFactory getSSLServerSocketFactory()
       throws UnrecoverableKeyException, KeyStoreException,
      NoSuchAlgorithmException, CertificateException, IOException,
      KeyManagementException {
    SSLServerSocketFactory ssf = null;

    KeyStore ks = KeyStore.getInstance( "JKS");
    String keystoreFileName = "keystore4test";
    String passwd = "xxxxxx";
    ks.load( this.getClass().getResourceAsStream(keystoreFileName),
        passwd.toCharArray());
    KeyManagerFactory kmf = KeyManagerFactory.getInstance( "SunX509");
    kmf.init(ks, passwd.toCharArray());

    SSLContext sslContext = SSLContext.getInstance( "TLS");
    sslContext.init(kmf.getKeyManagers(), null, null);
    ssf = sslContext.getServerSocketFactory();
     return ssf;
  }
}
 

 
package net.gy.java.rmi.ssl; 

import java.io.IOException;
import java.io.Serializable;
import java.net.Socket;
import java.rmi.server.RMIClientSocketFactory;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;

import javax.net.SocketFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;

public class MySSLRMIClientSocketFactory implements RMIClientSocketFactory,
    Serializable {

   /**
   * 
   */

   private static final long serialVersionUID = - 4926828809111173712L;
   private SocketFactory sf = null;

   public MySSLRMIClientSocketFactory() {
// if using default socket factory here, you need specify the trust store as argument in java command like following:
// java -cp . -Djavax.net.ssl.trustStore="c:\\keystore4test" net.gy.java.rmi.ssl.SSLRMIClient

// sf = SSLSocketFactory.getDefault();

     try {
      sf = buildSSLSocketFactory();
    } catch (KeyManagementException e) {
       // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
       // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (KeyStoreException e) {
       // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (CertificateException e) {
       // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (IOException e) {
       // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }

  @Override
   public Socket createSocket(String host, int port) throws IOException {
     // TODO Auto-generated method stub
    Socket sc = sf.createSocket(host, port);
     return sc;
  }

   private SSLSocketFactory buildSSLSocketFactory()
       throws NoSuchAlgorithmException, KeyStoreException, CertificateException,
      IOException, KeyManagementException {
    String trustStoreFileName = "keystore4test";
    String passwd = "xxxxxx";
    TrustManagerFactory tmf = TrustManagerFactory.getInstance( "SunX509");
    KeyStore tks = KeyStore.getInstance( "JKS");
    tks.load( this.getClass().getResourceAsStream(trustStoreFileName),
        passwd.toCharArray());
    tmf.init(tks);

    SSLContext sslContext = SSLContext.getInstance( "TLS");
    sslContext.init(null, tmf.getTrustManagers(), null);
     return sslContext.getSocketFactory();
  }

}
 

 
package net.gy.java.rmi.ssl; 

import java.rmi.AlreadyBoundException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;

import javax.rmi.ssl.SslRMIClientSocketFactory;
import javax.rmi.ssl.SslRMIServerSocketFactory;

import net.gy.java.rmi.ProxyBinderInterface;
import net.gy.java.rmi.RMIRemoteInterface;

public class SSLRMIServer implements RMIRemoteInterface {

   public static final String PROXY_BINDER = "PROXY_BINDER";
   public static final String HOST = "Solaris330";
   public static final int RMI_PORT = 2222;

   public SSLRMIServer() {

  }

  @Override
   public String sayHi() {
     return "Hello World!";
  }

  @Override
   public String sayHello(String str) throws RemoteException {
     // TODO Auto-generated method stub
     return "Hi " + str;
  }

   public static void main(String args[]) {

     try {
       int port = (args.length < 1) ? RMI_PORT : Integer.valueOf(args[ 0]);

      SSLRMIServer obj = new SSLRMIServer();
      RMIRemoteInterface stub = (RMIRemoteInterface) UnicastRemoteObject
          .exportObject(obj, 0);

// ProxyBinderInterface proxyBinder = (ProxyBinderInterface)UnicastRemoteObject.e

       // Bind the remote object's stub in the registry

       // RMI registry not allow remote hosts to bind
// Registry registry = LocateRegistry.getRegistry(HOST, port);
// Registry registry = LocateRegistry.createRegistry(port);
      Registry registry = 
        LocateRegistry.createRegistry(port, new MySSLRMIClientSocketFactory() , new MySSLRMIServerSocketFactory());
      registry.bind( "newHello", stub);
      registry.bind(PROXY_BINDER, obj. new ProxyBinder());

      System.err.println( "Server ready");
    } catch (Exception e) {
      System.err.println( "Server exception: " + e.toString());
      e.printStackTrace();
    }
  }

   class ProxyBinder extends UnicastRemoteObject implements ProxyBinderInterface {

     /**
     * 
     */

     private static final long serialVersionUID = - 2373753944757892323L;

     protected ProxyBinder() throws RemoteException {
       super();
       // TODO Auto-generated constructor stub
    }

    @Override
     public void proxyBind(String label, Remote stub) throws RemoteException, AlreadyBoundException{
      Registry registry = LocateRegistry.getRegistry(HOST, RMI_PORT);
      registry.bind(label, stub);
    }

  }

}
 

 
package net.gy.java.rmi.ssl; 

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

import javax.rmi.ssl.SslRMIClientSocketFactory;

import net.gy.java.rmi.RMIRemoteInterface;

public class SSLRMIClient {

   public static void main(String[] args) {

     if (args.length < 1) {
      System.err.println( "Usage: RMIClient <host> [port]");
// System.exit(1);
    }
    String host = "Solaris330"; //args[0];
// String host = "localhost";
     int port = SSLRMIServer.RMI_PORT;
     if (args.length == 2) {
      port = Integer.valueOf(args[ 1]); 
    }

     try {
// Registry registry = LocateRegistry.getRegistry(host, port);
      Registry registry = LocateRegistry.getRegistry(host, port, new MySSLRMIClientSocketFactory());
        RMIRemoteInterface stub = (RMIRemoteInterface) registry.lookup( "newHello");

// FileOutputStream fos = new FileOutputStream("c:\\t.tmp");
// ObjectOutputStream oos = new ObjectOutputStream(fos);
// oos.writeObject(stub);
// oos.close();

        String response = stub.sayHi();
        System.out.println( "sayHi: " + response);
        System.out.println( "sayHello: " + stub.sayHello( "Java World"));
    } catch (Exception e) {
        System.err.println( "Client exception: " + e.toString());
        e.printStackTrace();
    }
}

   public static void main2(String[] args) {

     try {
      FileInputStream fis = new FileInputStream( "c:\\t.tmp");
      ObjectInputStream ois = new ObjectInputStream(fis);
      RMIRemoteInterface stub = (RMIRemoteInterface) ois.readObject();

      String response = stub.sayHi();
      System.out.println( "response: " + response);

    } catch(Exception e) {
      System.err.println( "Client exception: " + e.toString());
      e.printStackTrace();
    }

  }
}
 
Wireshark检验SSL效果:
Pure RMI Senario:
RMI, RMI over SSL, RMI-IIOP, RMI-IIOP over SSL_第1张图片 
 
RMI Over SSL Senario:
RMI, RMI over SSL, RMI-IIOP, RMI-IIOP over SSL_第2张图片  
 
 
 
 
 
 
 
MISC:
debug ssl java programs by specifying argument: javax.net.debug=ssl
e.g. java -cp RMITest.jar -Djavax.net.debug=ssl net.gy.java.rmi.RMIServer




你可能感兴趣的:(over)