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

发布于 2024-07-04 17:01:41 字数 934 浏览 6 评论 0原文

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

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

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

这是我们已经知道的:

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

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

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

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

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

In our application, we are using RMI for client-server communication in very different ways:

  1. Pushing data from the server to the client to be displayed.
  2. Sending control information from the client to the server.
  3. Callbacks from those control messages code paths that reach back from the server to the client (sidebar note - this is a side-effect of some legacy code and is not our long-term intent).

What we would like to do is ensure that all of our RMI-related code will use only a known specified inventory of ports. This includes the registry port (commonly expected to be 1099), the server port and any ports resulting from the callbacks.

Here is what we already know:

  1. LocateRegistry.getRegistry(1099) or Locate.createRegistry(1099) will ensure that the registry is listening in on 1099.
  2. Using the UnicastRemoteObject constructor / exportObject static method with a port argument will specify the server port.

These points are also covered in this Sun forum post.

What we don't know is: how do we ensure that the client connections back to the server resulting from the callbacks will only connect on a specified port rather than defaulting to an anonymous port?

EDIT: Added a longish answer summarizing my findings and how we solved the problem. Hopefully, this will help anyone else with similar issues.

SECOND EDIT: It turns out that in my application, there seems to be a race condition in my creation and modification of socket factories. I had wanted to allow the user to override my default settings in a Beanshell script. Sadly, it appears that my script is being run significantly after the first socket is created by the factory. As a result, I'm getting a mix of ports from the set of defaults and the user settings. More work will be required that's out of the scope of this question but I thought I would point it out as a point of interest for others who might have to tread these waters at some point....

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

秋叶绚丽 2024-07-11 17:01:41

我在使用客户端回调实现 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 ");

希望这会有所帮助。
伊拉克利斯

I've been having various problems implementing an RMI Server/Client architecture, with Client Callbacks. My scenario is that both Server and Client are behind Firewall/NAT. In the end I got a fully working implementation. Here are the main things that I did:

Server Side , Local IP: 192.168.1.10. Public (Internet) IP 80.80.80.10

On the Firewall/Router/Local Server PC open port 6620.
On the Firewall/Router/Local Server PC open port 1099.
On the Router/NAT redirect incoming connections on port 6620 to 192.168.1.10:6620
On the Router/NAT redirect incoming connections on port 1099 to 192.168.1.10:1099

In the actual program:

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);

Client Side, Local IP: 10.0.1.123 Public (Internet) IP 70.70.70.20

On the Firewall/Router/Local Server PC open port 1999.
On the Router/NAT redirect incoming connections on port 1999 to 10.0.1.123:1999

In the actual program:

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

Hope this helps.
Iraklis

谢绝鈎搭 2024-07-11 17:01:41

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

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

You don't need socket factories for this, or even multiple ports. If you're starting the Registry from your server JVM you can use port 1099 for everything, and indeed that is what will happen by default. If you're not starting the registry at all, as in a client callback object, you can provide port 1099 when exporting it.

The part of your question about 'the client connections back to the server resulting from callbacks' doesn't make sense. They are no different from the original client connections to the server, and they will use the same server port(s).

傲影 2024-07-11 17:01:41

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

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

这是 Sun 的指南,告诉您如何操作。< /a>

You can do this with a custom RMI Socket Factory.

The socket factories create the sockets for RMI to use at both the client and server end so if you write your own you've got full control over the ports used. The client factories are created on the server, Serialized and then sent down to the client which is pretty neat.

Here's a guide at Sun telling you how to do it.

不弃不离 2024-07-11 17:01:41

下面的长答案总结:为了解决我遇到的问题(限制 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 即可。

Summary of the long answer below: to solve the problem that I had (restricting server and callback ports at either end of the RMI connection), I needed to create two pairs of client and server socket factories.

Longer answer ensues:

Our solution to the callback problem had essentially three parts. The first was the object wrapping which needed the ability to specify that it was being used for a client to server connection vs. being used for a server to client callback. Using an extension of UnicastRemoteObject gave us the ability to specify the client and server socket factories that we wanted to use. However, the best place to lock down the socket factories is in the constructor of the remote object.

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));
}
// ....
}

So, the first argument specifies the part on which the object is expecting requests, whereas the second and third specify the socket factories that will be used at either end of the connection driving this remote object.

Since we wanted to restrict the ports used by the connection, we needed to extend the RMI socket factories and lock down the ports. Here are some sketches of our server and client factories:

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);
    }
}
// ....
}

Note that the server socket factory above ensures that only the port that you previously specified will ever be used by this factory. The client socket factory has to be paired with the appropriate socket factory (or you'll never connect).

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);
    }
}
// ....
}

So, the only thing remaining to force your two way connection to stay on the same set of ports is some logic to recognize that you are calling back to the client-side. In that situation, just make sure that your factory method for the remote object calls the RemoteObjectWrapper constructor up top with the callback parameter set to true.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文