如何确保RMI仅使用一组特定的端口?

在我们的应用程序中,我们使用RMI以非常不同的方式进行客户端 – 服务器通信:

  1. 将数据从服务器推送到客户端以进行显示。
  2. 从客户端向服务器发送控制信息。
  3. 来自那些控制消息的回调代码从服务器返回到客户端的路径(侧边栏注释 – 这是一些遗留代码的副作用,而不是我们的长期意图)。

我们想要做的是确保我们所有与RMI相关的代码仅使用已知的指定端口库存。 这包括注册表端口(通常预期为1099),服务器端口以及回调产生的任何端口。

这是我们已经知道的:

  1. LocateRegistry.getRegistry(1099)或Locate.createRegistry(1099)将确保注册表正在侦听1099。
  2. 将UnicastRemoteObject构造函数/ exportObject静态方法与port参数一起使用将指定服务器端口。

这些Sun论坛post也涵盖了这些要点。

我们不知道的是:我们如何确保回调产生的客户端连接回服务器只能连接到指定的端口,而不是默认为匿名端口?

编辑:添加了一个很长的答案,总结了我的发现以及我们如何解决问题。 希望这可以帮助其他有类似问题的人。

第二次编辑:事实certificate,在我的应用程序中,我在创建和修改套接字工厂时似乎存在竞争条件。 我本来想让用户在Beanshell脚本中覆盖我的默认设置。 遗憾的是,在工厂创建第一个套接字后,我的脚本似乎正在运行。 结果,我从默认设置和用户设置中获得了混合端口。 将需要做更多的工作,这超出了这个问题的范围,但我想我会指出它是其他可能不得不在某些时候踏上这些水域的人的兴趣点….

您可以使用自定义RMI套接字工厂执行此操作。

套接字工厂为RMI创建套接字,以便在客户端和服务器端使用,因此如果您自己编写,则可以完全控制所使用的端口。 客户端工厂在服务器上创建,序列化,然后发送到客户端,非常整洁。

这是Sun的一个指南,告诉你如何做到这一点。

您不需要套接字工厂,甚至不需要多个端口。 如果您从服务器JVM启动注册表,则可以将端口1099用于所有内容,实际上这是默认情况下会发生的情况。 如果您没有像在客户端回调对象中那样启动注册表,则可以在导出时提供端口1099。

关于“回调产生的客户端连接回服务器”的问题部分没有意义。 它们与服务器的原始客户端连接没有区别,它们将使用相同的服务器端口。

下面的长答案总结:为了解决我遇到的问题(限制RMI连接两端的服务器和回调端口),我需要创建两对客户端和服务器套接字工厂。

更长的答案随之而来:

我们对回调问题的解决方案基本上有三个部分。 第一个是对象包装,它需要能够指定它用于客户端到服务器连接而不是用于服务器到客户端回调。 使用UnicastRemoteObject的扩展使我们能够指定我们想要使用的客户端和服务器套接字工厂。 但是,锁定套接字工厂的最佳位置是远程对象的构造函数。

 public class RemoteObjectWrapped extends UnicastRemoteObject { // .... private RemoteObjectWrapped(final boolean callback) throws RemoteException { super((callback ? RemoteConnectionParameters.getCallbackPort() : RemoteConnectionParameters.getServerSidePort()), (callback ? CALLBACK_CLIENT_SOCKET_FACTORY : CLIENT_SOCKET_FACTORY), (callback ? CALLBACK_SERVER_SOCKET_FACTORY : SERVER_SOCKET_FACTORY)); } // .... } 

因此,第一个参数指定对象期望请求的部分,而第二个和第三个参数指定将在驱动此远程对象的连接的任一端使用的套接字工厂。

由于我们想限制连接使用的端口,我们需要扩展RMI套接字工厂并锁定端口。 以下是我们的服务器和客户端工厂的一些草图:

 public class SpecifiedServerSocketFactory implements RMIServerSocketFactory { /** Always use this port when specified. */ private int serverPort; /** * @param ignoredPort This port is ignored. * @return a {@link ServerSocket} if we managed to create one on the correct port. * @throws java.io.IOException */ @Override public ServerSocket createServerSocket(final int ignoredPort) throws IOException { try { final ServerSocket serverSocket = new ServerSocket(this.serverPort); return serverSocket; } catch (IOException ioe) { throw new IOException("Failed to open server socket on port " + serverPort, ioe); } } // .... } 

请注意,上面的服务器套接字工厂确保此工厂仅使用您先前指定的端口。 客户端套接字工厂必须与相应的套接字工厂配对(或者您永远不会连接)。

 public class SpecifiedClientSocketFactory implements RMIClientSocketFactory, Serializable { /** Serialization hint */ public static final long serialVersionUID = 1L; /** This is the remote port to which we will always connect. */ private int remotePort; /** Storing the host just for reference. */ private String remoteHost = "HOST NOT YET SET"; // .... /** * @param host The host to which we are trying to connect * @param ignoredPort This port is ignored. * @return A new Socket if we managed to create one to the host. * @throws java.io.IOException */ @Override public Socket createSocket(final String host, final int ignoredPort) throws IOException { try { final Socket socket = new Socket(host, remotePort); this.remoteHost = host; return socket; } catch (IOException ioe) { throw new IOException("Failed to open a socket back to host " + host + " on port " + remotePort, ioe); } } // .... } 

因此,唯一可以强制您的双向连接保持在同一组端口的东西是一些逻辑,以识别您正在回调客户端。 在这种情况下,只需确保远程对象的工厂方法调用RemoteObjectWrapper构造函数,并将callback参数设置为true。

我在使用客户端回调实现RMI服务器/客户端架构时遇到了各种问题。 我的方案是服务器和客户端都在防火墙/ NAT之后。 最后,我得到了一个完全有效的实施。 以下是我做的主要事情:

服务器端,本地IP:192.168.1.10。 公共(互联网)IP 80.80.80.10

在防火墙/路由器/本地服务器PC上打开端口6620.在防火墙/路由器/本地服务器PC上打开端口1099.在路由器/ NAT上将端口6620上的传入连接重定向到192.168.1.10:6620关于路由器/ NAT重定向传入端口1099到192.168.1.10:1099上的连接

在实际程序中:

 System.getProperties().put("java.rmi.server.hostname", IP 80.80.80.10); MyService rmiserver = new MyService(); MyService stub = (MyService) UnicastRemoteObject.exportObject(rmiserver, 6620); LocateRegistry.createRegistry(1099); Registry registry = LocateRegistry.getRegistry(); registry.rebind("FAManagerService", stub); 

客户端,本地IP:10.0.1.123公共(Internet)IP 70.70.70.20

在防火墙/路由器/本地服务器PC上打开端口1999.在路由器/ NAT上将端口1999上的传入连接重定向到10.0.1.123:1999

在实际程序中:

 System.getProperties().put("java.rmi.server.hostname", 70.70.70.20); UnicastRemoteObject.exportObject(this, 1999); MyService server = (MyService) Naming.lookup("rmi://" + serverIP + "/MyService "); 

希望这可以帮助。 伊拿克里斯