10

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

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

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

这是我们已经知道的:

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

这篇Sun 论坛帖子也介绍了这些要点。

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

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

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

4

4 回答 4

4

您可以使用自定义 RMI 套接字工厂来完成此操作。

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

这是 Sun 的指南,告诉您如何操作。

于 2008-09-11T14:36:40.987 回答
2

为此,您不需要套接字工厂,甚至不需要多个端口。如果您从服务器 JVM 启动 Registry,您可以使用端口 1099 进行所有操作,实际上这就是默认情况下会发生的情况。如果您根本没有启动注册表,就像在客户端回调对象中一样,您可以在导出它时提供端口 1099。

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

于 2016-05-20T22:25:28.773 回答
1

下面的长答案总结:为了解决我遇到的问题(限制 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 构造函数,并将回调参数设置为 true。

于 2008-11-19T19:39:28.583 回答
0

我在使用客户端回调实现 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 公共(互联网)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 ");

希望这可以帮助。伊拉克利斯

于 2010-03-16T14:25:23.513 回答