RMI穿越防火墙

RMI我接触算是比较早的了,研究生论文里阐述的项目,底层通信机制采用的就是RMI。当时还特意把RMI的规范翻译过来了,其实我上两届的师兄毕业论文几乎就是RMI的规范,当时RMI还是刚出现没有多久,作为Java的分布式对象通信技术,Corba的轻量级实现。
    毕业后,就很少用RMI了。前两年,做一个局域网内的C/S的Java应用,用到了RMI。很奇怪吧,用Java来做桌面应用很少见,呵呵。主要是客户端的机器有不同的操作系统,Java的优点真正得到了体现。

    本人只在局域网内做过RMI应用,以下内容没有经过实践,记录在这里,这不过是为了以后方便。

    使用RMI,开发真的是很简单。RMI之所以使用的范围受限制主要是穿越防火墙不方便。RMI通信过程:首先由RMI服务器注册,创建ServerSocket;其次由RMI客户机lookup,返回对象stub;最后由RMI客户机调用对象方法。由于只是得到服务的stub,实际运行是由服务器完成的,但看似在客户端完成。其中1099是客户端查找对象stub需要的查找端口,绑定到RMI注册程序,而对象stub与客户端传递数据,则需要RMISocketFactory另外分配服务端口,而缺省实现这个端口是随机产生的。缺省的RMISocketFactory是sun.rmi.transport.proxy.RMIMasterSocketFactory,大家在www.google.com/codesearch中可以搜索到这个类的实现。
一、固定端口
     RMI穿越防火墙不方便主要是因为除了RMI服务注册的端口(默认1099)外,与RMI的通讯还需要另外的端口来传送数据,而另外的端口是随机分配的。所以,防火墙只设置允许1099是不可以的,而另一个数据端口是随机分配的,则无法设置,所以要想RMI的客户能通过防火墙来与RMI服务通讯,则需要能让随机分配的端口固定下来。我们需要实现一个自己的RMISocketFactory子类MyRMISocket。

import java.rmi.server.*;
import java.io.*;
import java.net.*;
public class MyRMISocket extends RMISocketFactory
{
public Socket createSocket(String host, int port) throws IOException
{
return new Socket(host,port);
}
public ServerSocket createServerSocket(int port) throws IOException
{
if (port == 0)
    port = 5099; //不指定就随机分配了
return new ServerSocket(port);
}
}

在实例化UnicastRemoteObject的子类前设置这个类
try {
RMISocketFactory.setSocketFactory (new MyRMISocket()); 

System.setProperty("java.rmi.server.hostname","XX.XX.XX.139");
IP对应的是公网IP

LocateRegistry.createRegistry(1099);

String rmiAddress = "//"+ip+":"+port+"/Engine";

Naming.rebind(rmiAddress, new ExampleRMIImpl(null));
} catch (Exception ex) { }
这样的话RMI分配的端口就被固定了,防火墙只需要打开1099和5099端口即可。
二、通过HTTP
    现在的RMISocketFactory 实例由 RMI 运行时使用,以便获得 RMI 调用所需的客户机和服务器套接字。应用程序可以使用 setSocketFactory 方法来请求 RMI 运行时使用其套接字工厂实例而不是使用默认的实现。
    所用的默认套接字工厂实现通过三层方法来创建客户机套接字。首先,尝试进行到远程 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实现。

    如果大家感兴趣可以看看文档rmi下相关技术文章(docs/technotes/guides/rmi/socketfactory/index.html),里面有Socket通信定制实现。其实RMISocketFactory实现了客户端和服务器端的Socket管理,自己可以重新实现,实现自己的通信协议.

   我的文档是Java6,其它版本位置可能不同.

//这是网上找到的信息,记录供以后查找

我最近的项目采用rmi技术,是c/s结构,服务器位于防火墙之后,个个客户段均位于防火墙之外,防火墙已经打开了1099和7192(服务器端口),防火墙将内部机器(服务器所在的工作站的IP对外虚拟了外网网段的IP地址)客户端链接是找不到服务器地址,纠其原因,客户端通过rmi连接之后,返回了位于防火墙之后服务器所在机器的实际IP地址而不是虚拟IP地址,但对于客户端而言需要的是虚拟的IP地址,如何能设置使rmi获得的是虚拟的IP地址。

System.setProperty("java.rmi.server.hostname","XX.XX.XX.139");
IP对应的是公网IP

首先服务器端需要java.rmi.server.hostname=“服务器名”,
然后客户端的机器需要配置etc\hosts,在其中添加
 “虚拟IP”  “服务器名”,

即可通过防火墙。



    今天帮助一位网友忙了半天,我告诉他修改固定端口,使RMI通过防火墙的方法,结果在本地可以,放到服务器上就不可以。

    本地是Windows系统,通过netstat -an能看到设置的固定端口1099,而服务器是Linux,使用lsof -i 1099,发现1099没有启动。远程无法访问,而放到Linux服务器本地的RMI客户端就可以访问。怀疑是没有使用固定端口,通过netstat -atn |grep java,发现了启动了一系列端口,其中有一个很大的端口,应该是随机产生的。

    询问后,才知道服务器上启动了两个RMI服务,其中一个采用了固定通信端口的RMISocketFactory,而另一个没有修改。原因找到了......

    帮助别人,提高自己。

   

    今天查找的时候发现了一篇文章说明:

//在sun.rmi.server.UnicastRef调用时的服务IP为:x.x.x.135,而不是x.x.x.90(客户的本地不存在RMI服务嘛)

//System.setProperty("java.rmi.server.hostname","x.x.x.135"); 

      //下面这行代码不能少,否则当路由器x.x.x.135映射到的内网IP:x.x.x.90时,
       //访问RMI服务时将导向本地的x.x.x.90,那么客户端就是访问本地x.x.x.90,
       //这绝对错误.服务是在公网路由器(含公共IP)的后面,不在客户的本地
       System.setProperty("java.rmi.server.hostname","x.x.x.135");

引自:http://blog.csdn.net/ktyl2000/archive/2009/08/26/4485896.aspx  




  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值