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:
UnicastRemoteObject:
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