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

Index:
 
 
    分布式系统(比如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:
    getRegistry(String host, int port, RMIClientSocketFactory csf)
 
UnicastRemoteObject:
    UnicastRemoteObject(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 Over SSL Senario:
  
 
 
 
 
 
 
 
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




posted @ 2011-12-21 00:11  郭太东  阅读(860)  评论(0编辑  收藏  举报