我自己最早听到RMI还是在EJB里面,至于之前为什么会去看EJB还是有历史原因的,后来Spring里面经常使用,在Spring里面调用还是很方便的,配置一下就可以了,没有Spring的框架下就是纯java的RMI了。
近来几天困扰的一个RMI问题,终于解决了,解决之前是纠结的,解决之后是收获的,正是因为如此,才有深入了解JAVA的RMI原理。出现这个问题根本原因就是防火墙。
RMI的原理,用图来说明
这个过程并不复杂,但是上图只是个大概,最为重要的还是RMI与客户端通信,必须有两个端口,其一为固定的端口默认为1099,其二为随机生成的数据端口。
在没有防火墙限制的情况下,可能都不会有什么问题,按正常流程调用即可。往往我们的生产环境,是开通防火墙的,只开通个别已知的端口,对未知的端口是不允许访问的,那么就会导致RMI通信失败。错误信息如下:
java.rmi.ConnectIOException: error during JRMP connection establishment; nested
exception is:
java.net.SocketTimeoutException: Read timed out
at sun.rmi.transport.tcp.TCPChannel.createConnection(TCPChannel.java:286
)
这个错误信息就足以确定是某个端口没有开通的问题,通常对问题的的定位花时间更多,尤其是下环境比较复杂的情况下,反而解决方法是相对简单的,只须把随机生成的数据端口改为固定端口,不随机,并且在防火墙中开放此端口。
解决方法:
1.写一个SMRMISocket.java类
/**
* <p>
* 设置RMI固定端口,避免在启用防火墙时,客户端无法调用
* </p>
* @author huangshuidan
* @date 2011-12-09
*
*/
public class SMRMISocket extends RMISocketFactory{
private static int SOCKET_PORT=5902;//固定RMI的数据库端口
/* (non-Javadoc)
* @see java.rmi.server.RMISocketFactory#createServerSocket(int)
*/
public ServerSocket createServerSocket(int port) throws IOException {
if (port == 0){
port = SOCKET_PORT;//不指定就随机
System.out.println("固定RMI的数据库端口:"+port);
}
return new ServerSocket(port);
}
/* (non-Javadoc)
* @see java.rmi.server.RMISocketFactory#createSocket(java.lang.String, int)
*/
public Socket createSocket(String host, int port) throws IOException {
return new Socket(host,port);
}
}
可以把SOCKET_PORT这个固定端口写到配置文件去。
2.//固定RMI数据端口--begin
RMISocketFactory.setSocketFactory(new SMRMISocket());
//固定RMI数据端口--end
LocateRegistry.createRegistry(1099);
IHello hello = new HelloImpl();
注意这三行的顺序设置固定端口,一定要放在实例化UnicastRemoteObject对象之前,不然还是随机生成数据端口。
=======================================================================================
到这里为止就足以解决问题了,可是我们项目的环境相当复杂,更新后不生效,后面一一排查,发现有个配置跟RMI服务冲突了,
修改了下就全部搞定。
一分辛苦一分收获,假设没有这个问题的出现,我也不会深入去研究RMI,最多就停留在会用,所以会用和深入学习还是有相当大的差距。