先来一个最简单的例子:
1.业务接口类
package Simple;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface Business extends Remote{
String doBusiness(String businessCode) throws RemoteException;
}
2.业务实现类
package Simple;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class BusinessImpl extends UnicastRemoteObject implements Business{
protected BusinessImpl() throws RemoteException {
super();
// TODO Auto-generated constructor stub
}
/**
*
*/
private static final long serialVersionUID = 1L;
@Override
public String doBusiness(String businessCode) throws RemoteException {
// TODO Auto-generated method stub
return businessCode + " has been done!";
}
}
3.Server类
package Simple;
import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;
public class SimpleServer {
public static void main(String[] args)throws Exception{
Business busi = new BusinessImpl();
LocateRegistry.createRegistry(1097);
Naming.rebind("rmi://localhost:1097/busi",busi);
}
}
4.Client类
package Simple;
import java.rmi.Naming;
public class Client {
public static void main(String[] args)throws Exception{
Business busi = (Business)Naming.lookup("rmi://localhost:1097/busi");
System.out.println(busi.doBusiness("001"));
}
}
备注说明:
1.从JDK5.0开始,rmi编程不再需要编程人员借助rmic生成stub等辅助类,也不需要手动启动Register。也就是说这些工作JDK已经帮你做好啦
2.EJB的SessionBean其实就是rmi技术和JNDI技术的结合,上述的例子是把业务对象(或stub)存储在Register中,而EJB则把业务对象(或stub)存储在JDNI空间里.
3.LocateRegistry.createRegistry(1097);这里的1097(默认为1099)我们称之为通讯端口或者查找端口,服务端在createRegistry时实际上会new ServerSocket(1097),客户端的socket)通过与端口号为1097的服务端端口互联lookup到busi对象(实际上是stub,为方便描述,下文不再特别指出)。客户端获取到的busi(stub)对象在和服务端的sketon对象进行通讯时实际上也会建立socket连接,数据传输时的ServerSocket也需要一个端口(不同于通讯端口),我们称为之数据端口。这个ServerSocket是在BusinessImpl的父类UnicastRemoteObject的构造方法中初始化的,默认调用new ServerSocket(0),即随便选择一个端口通讯。但在生产环境中,防火墙往往限制通讯端口,只允许部分端口开放。所以上述程序可能会无法穿透防火墙,这时候我们可以强行绑定数据端口.
package Simple;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;
import java.rmi.server.RMISocketFactory;
public class Server{
public static void main(String... args) throws Exception{
RMISocketFactory.setSocketFactory(new RMISocketFactory(){
@Override
public ServerSocket createServerSocket(int port) throws IOException {
if(port == 0)
port = 14800;
return new ServerSocket(port);
}
@Override
public Socket createSocket(String host, int port)
throws IOException {
return new Socket(host,port);
}
});
LocateRegistry.createRegistry(1097);
Business busi = new BusinessImpl();
Naming.rebind("rmi://localhost:1097/busi",busi);
}
}
我们通过RMISocketFactory.setSocketFactory来控制绑定生成ServerSocket的端口号.在这里LocateRegistry.createRegistry时会调用匿名内部类的createServerSocket生成new ServerSocket(1097),在new BusinessImpl()时会调用匿名内部类的createServerSocket生成new ServerSocket(14800)(原来port=0进行了重绑定),同时也会调用匿名内部类的createSocket生成new Socket("localhost",1097)。在这里一定要注意new BusinessImpl()在setSocketFactory之后才会绑定成功。
我们也可以把LocateRegistry.createRegistry是生成ServerSocket的策略和new BusinessImpl()生成ServerSocket和Socket的策略区别开来,如下:
package Simple;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;
import java.rmi.server.RMIClientSocketFactory;
import java.rmi.server.RMIServerSocketFactory;
import java.rmi.server.RMISocketFactory;
public class Server{
public static void main(String... args) throws Exception{
RMISocketFactory.setSocketFactory(new RMISocketFactory(){
@Override
public ServerSocket createServerSocket(int port) throws IOException {
if(port == 0)--没有作用了(肯定==0)
port = 14800;
return new ServerSocket(port);
}
@Override
public Socket createSocket(String host, int port)
throws IOException {
return new Socket(host,port);
}
});
LocateRegistry.createRegistry(1097,new RMIClientSocketFactory(){
@Override
public Socket createSocket(String host, int port)
throws IOException {
return new Socket(host,port);
}},new RMIServerSocketFactory(){
@Override
public ServerSocket createServerSocket(int port) throws IOException {
return new ServerSocket(port);
}});
Business busi = new BusinessImpl();
Naming.rebind("rmi://localhost:1097/busi",busi);
}
}
显然LocateRegistry.createRegistry会使用参数RMIClientSocketFactory的createServerSocket去new ServerSocket(1097).
而new BusinessImpl()还会使用RMISocketFactory.setSocketFactory参数中相关方式,new ServerSocket(34800)和new Socket("localhost",1907).这时候方法中if(port==0)的判断也没有意义了(肯定==0了,LocateRegistry.createRegistry也不会使用这里的工厂生成serverSocket(1907)了)。
再来一种更简单的实现(需要对BusinessImpl进行一点改进)
package Simple;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class BusinessImpl extends UnicastRemoteObject implements Business{
protected BusinessImpl() throws RemoteException {
super();
}
protected BusinessImpl(int port) throws RemoteException{
super(port);
}
/**
*
*/
private static final long serialVersionUID = 1L;
@Override
public String doBusiness(String businessCode) throws RemoteException {
// TODO Auto-generated method stub
return businessCode + " has been done!";
}
}
package Simple;
import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;
public class Server{
public static void main(String... args) throws Exception{
LocateRegistry.createRegistry(1097);
Business busi = new BusinessImpl(34880);
Naming.rebind("rmi://localhost:1097/busi",busi);
}
}
够简单的吧,哈哈。
最后再来谈谈默认的套接字工厂吧。
默认的套接字工厂实现通过三层方法来创建客户机套接字。首先,尝试进行到远程 VM 的直接套接字连接。如果该操作失败(因防火墙的问题),则运行时使用具有服务器显式端口号的 HTTP。如果防火墙不允许此类型的通信,则服务器上的 cgi-bin 脚本的 HTTP 用于 POST(发送)该 RMI 调用。
sun.rmi.transport.proxy.RMIMasterSocketFactory失败后,会使用另外两个类sun.rmi.transport.proxy.RMIHttpToPortSocketFactory和sun.rmi.transport.proxy.RMIHttpToCGISocketFactory来实现通过HTTP协议,不过这个需要服务器端配合。在jdk文档下有一个例子docs\technotes\guides\rmi\archives里面有Servlet实现。
参与文献:
(1)http://www.huomo.cn/developer/article-1b147.html 让JAVA的RMI彻底穿透防火墙
(2)http://hi.baidu.com/netjava/item/c2efd413ce50d2fa9c778a1c RMI穿透防火墙
(3)http://skanion.iteye.com/blog/1168747 RMI心得(注册端口)
(4)http://blog.csdn.net/ktyl2000/article/details/4485896 RMI远程调用时的内外网端口映射问题(RMI远程调用如何穿透防火墙)
(5)http://www.blogjava.net/china-qd/archive/2006/04/25/42927.html 用RMI进行远程方法调用
(6)http://blog.csdn.net/sun_boyhappy/article/details/5655014 RMI IP绑定和端口固定
(7)http://bbs.csdn.net/topics/220029980 RMISocketFactory的含义和RMI的理解
(8)java_RMI技术讲解.doc
(9)RMI开发权威指南.pdf