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;
|
//接口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) //构造方法
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;
|
package net.gy.java.rmi.ssl;
|
package net.gy.java.rmi.ssl;
|
Wireshark检验SSL效果:
Pure RMI Senario:
RMI Over SSL Senario: