如何确保RMI仅使用一组特定的端口?
在我们的应用程序中,我们以非常不同的方式将RMI用于客户端-服务器通信:
- 将数据从服务器推送到客户端以进行显示。
- 从客户端向服务器发送控制信息。
- 这些控制消息的回调从服务器到达客户端的代码路径(补充说明-这是某些旧有代码的副作用,而不是我们的长期意图)。
我们要做的是确保我们所有与RMI相关的代码仅使用已知的指定端口清单。这包括注册表端口(通常应为1099),服务器端口以及由回调产生的所有端口。
这是我们已经知道的:
- LocateRegistry.getRegistry(1099)或者Locate.createRegistry(1099)将确保注册表正在侦听1099.
- 将UnicastRemoteObject构造函数/ exportObject静态方法与port参数一起使用将指定服务器端口。
这些问题也将在此Sun论坛帖子中介绍。
我们不知道的是:我们如何确保由回调产生的返回服务器的客户端连接将仅在指定的端口上进行连接,而不是默认为匿名端口?
编辑:添加了一个冗长的答案,总结了我的发现以及我们如何解决问题。希望这将对其他有类似问题的人有所帮助。
第二次编辑:事实证明,在我的应用程序中,我在创建和修改套接字工厂时似乎存在竞争条件。我本来希望允许用户在Beanshell脚本中覆盖我的默认设置。不幸的是,工厂创建第一个套接字后,我的脚本似乎正在大量运行。结果,我从默认设置和用户设置中混合了端口。这将超出此问题的范围,需要做更多的工作,但我想指出的是,这对于某些可能不得不在某些时候涉水的人来说是一个兴趣点。
解决方案
回答
我们可以使用自定义RMI套接字工厂来执行此操作。
套接字工厂创建供RMI在客户端和服务器端使用的套接字,因此,如果我们编写自己的套接字,则可以完全控制使用的端口。客户端工厂在服务器上创建,序列化,然后发送到客户端,这非常简洁。
这是Sun的指南,告诉我们如何执行此操作。
回答
以下是长答案的摘要:要解决我遇到的问题(在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); } } // .... }
因此,唯一迫使双向连接保持在同一组端口上的是一些逻辑,以识别我们正在回叫客户端。在这种情况下,只需确保远程对象的工厂方法在回调参数设置为true的情况下,首先调用RemoteObjectWrapper构造函数。
回答
在实现带有客户端回调的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 ");
希望这可以帮助。
伊拉克利斯