使用Java RMI如何从服务器向客户端发送消息?

6
该应用程序是一个局域网项目管理应用程序,其中包括项目、任务等对象。因此,RMI似乎是可行的选择。但是,它还有实时通知功能,当其他客户端触发事件时,会向某些客户端发送通知。我了解到,在RMI中,服务器无法跟踪已连接到它的客户端。因此,作为一种选择,我认为服务器可以像客户端事先连接到服务器一样连接到客户端。这是如何完成的吗?如果不是,那么在这种情况下,我应该使用套接字编程吗?如果我的问题很愚蠢,请提前道歉。

你能像 http://stackoverflow.com/questions/227276/java-rmi-not-closing-socket-after-lease-expiration 那样保持连接不中断吗? - bhantol
@bhantol,那个问题不是关于如何保持连接的活性,而保持其活性也不是解决这个问题的方法。 - user207421
@EJP,我的问题是关于连接是否被断开并重新创建或保持活动状态?我想知道这一点,因为我提出的解决方案是基于WebSockets或者完全使用WebSockets而不是RMI。 - bhantol
@bhantol 什么连接?RMI不会暴露连接,也不依赖于以任何方式保持连接活动。 - user207421
2个回答

12

你的假设是正确的。

对于从服务器主动推送通知到客户端,客户端也必须成为“服务器”。

因此,在客户端应用程序中,您还需要有一个远程接口。当客户端第一次连接到服务器时,您需要将远程接口实例的引用传递给它。

该对象需要导出为远程RMI对象,但不需要在注册表中注册,因为您将直接将其引用传递给需要调用其方法的服务器。

服务器会保留所有客户端的注册信息,以便在需要时进行回调。通常使用一个Map来存储客户端的有意义标识符作为键,存储客户端的远程引用作为值。

当客户端应用程序关闭时,客户端需要注销。

而且服务器可能希望定期检查所有客户端,以便不会保持对已经离线的客户端的引用。

您的服务器接口应该类似于:

public interface Server extends Remote {

    void register(Client client) throws RemoteException;

    void unregister(Client client) throws RemoteException;

    void doSomethingUseful(...) throws RemoteException;

    ...

}

以及您的客户界面:

public interface ClientCallbackInterface extends Remote {

    void ping() throws RemoteException;

    void notifyChanges(...) throws RemoteException;

}

在您的客户端应用程序启动代码中的某个位置:

ClientCallbackInterface client = new ClientImpl();

UnicastRemoteObject.exportObject(client);

Registry registry = LocateRegistry.getRegistry(serverIp, serverRegistryPort); 

Server server = (Server) registry.lookup(serviceName);

server.register(client);

完全可以实现,但不是琐碎的工作。你必须注意以下几点:

  • 如果涉及到防火墙,你必须小心处理可能引发的问题。
  • 本地操作系统防火墙也可能会出现问题,你的客户端应用程序必须打开本地传入端口。
  • 如果在同一台机器上尝试启动多个客户端,则会出现端口冲突,也必须小心处理。
  • 在局域网之外完全不起作用。

我确实实现过这样的系统,它运行良好。但所有上述内容都说明,如果我再做一次,我肯定会使用其他东西,可能是REST服务和WebSockets进行回调。这将减少对网络方面的限制,只需要HTTP。


知道这个很好。不过,我的问题是回调函数是如何执行的?Client 接口是否扩展了 ClientCallbackInterface 接口? - Aniket Thakur
是的,我的代码中有一个错别字。我已经编辑过了,谢谢您的注意。 - Pierre Henry
@PierreHenry 除了端口问题,为什么会说“完全不能在局域网之外工作”?局域网是否包括VPLAN? - Paul Uszak
@Paul:除了端口问题,没什么了,但这是一个很大的问题。如果您可以控制防火墙和可打开的端口,它将在VPN上工作。默认情况下,RMI使用随机端口,但您可以更改。 - Pierre Henry

0
正如Pierre Henry的回答所建议的那样,您可以在RMI中实现使用观察者设计模式的回调模式。

在粒度层面上,您可以实现套接字编程并使用观察者设计模式。每个客户端将首先注册到服务器。服务器将把它们存储在数据结构中,当需要发送某些事件通知时,服务器将遍历这些已注册的客户端并调用方法或向它们推送通知。

考虑使用一些JMS(Java消息服务)的实现,如Active MQ。您的发送方将异步侦听队列。发送方将发送消息,接收方将接收消息。如果您希望所有客户端都接收消息,请改用主题而不是队列,并且将有发布者和订阅者。


正如我在我的答案中所写的那样,你实际上可以使用RMI来实现观察者模式。然而,我同意JMS(或其他东西)可能是更好的选择。 - Pierre Henry
远程对象是一个服务器,调用其远程方法的进程是客户端。RMI客户端也可以是服务器:这是一种回调模式。不必使用套接字和可观察对象。 - user207421
已修改答案,删除了RMI部分的“无法完成”。请添加一个答案或提供您所指的回调模式的链接。 - Aniket Thakur
@PierreHenry已经提供了您所需的所有信息。实现RMI回调模式非常简单,而注册表和/或LDAP与此无关。您整个答案仍然是不正确的。 - user207421

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接