【发布时间】:2022-01-24 15:55:20
【问题描述】:
我需要创建一个可以向客户端通知事件的 RMI 服务。 每个客户端在服务器上注册自己,客户端可以发出一个事件,服务器会将它广播给所有其他客户端。
程序可以运行,但是服务器上的客户端引用永远不会被垃圾回收,服务器用来检查客户端引用是否永远不会终止的线程。
所以每次客户端连接到服务器时,都会创建一个新线程并且永远不会终止。
Notifier 类可以注册和注销监听器。
广播方法调用每个注册的监听器并将消息发回。
public class Notifier extends UnicastRemoteObject implements INotifier{
private List<IListener> listeners = Collections.synchronizedList(new ArrayList());
public Notifier() throws RemoteException {
super();
}
@Override
public void register(IListener listener) throws RemoteException{
listeners.add(listener);
}
@Override
public void unregister(IListener listener) throws RemoteException{
boolean remove = listeners.remove(listener);
if(remove) {
System.out.println(listener+" removed");
} else {
System.out.println(listener+" NOT removed");
}
}
@Override
public void broadcast(String msg) throws RemoteException {
for (IListener listener : listeners) {
try {
listener.onMessage(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
}
监听器只是打印每条收到的消息。
public class ListenerImpl extends UnicastRemoteObject implements IListener {
public ListenerImpl() throws RemoteException {
super();
}
@Override
public void onMessage(String msg) throws RemoteException{
System.out.println("Received: "+msg);
}
}
RunListener 客户端订阅一个侦听器,等待几秒钟以接收消息,然后终止。
public class RunListener {
public static void main(String[] args) throws Exception {
Registry registry = LocateRegistry.getRegistry();
INotifier notifier = (INotifier) registry.lookup("Notifier");
ListenerImpl listener = new ListenerImpl();
notifier.register(listener);
Thread.sleep(6000);
notifier.unregister(listener);
UnicastRemoteObject.unexportObject(listener, true);
}
}
RunNotifier 只是发布服务并定期发送消息。
public class RunNotifier {
static AtomicInteger counter = new AtomicInteger();
public static void main(String[] args) throws RemoteException, AlreadyBoundException, NotBoundException {
Registry registry = LocateRegistry.createRegistry(1099);
INotifier notifier = new Notifier();
registry.bind("Notifier", notifier);
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
executor.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
int n = counter.incrementAndGet();
System.out.println("Broadcasting "+n);
notifier.broadcast("Hello ("+n+ ")");
} catch (RemoteException e) {
e.printStackTrace();
}
}
},5 , 5, TimeUnit.SECONDS);
try {
System.in.read();
} catch (IOException e) {
}
executor.shutdown();
registry.unbind("Notifier");
UnicastRemoteObject.unexportObject(notifier, true);
}
}
我看过很多关于 RMI 堆栈溢出的问答,但没有一个能解决这类问题。
我想我犯了一些非常大的错误,但我无法发现它。
如图所示,每个传入的连接都会创建一个新的 RMI RenewClean 线程,并且该线程永远不会终止。
一旦客户端断开连接并终止,RenewClean 线程将默默吞下所有抛出的 ConnectionException 并继续轮询一个永远不会回复的客户端。
附带说明一下,我什至尝试在 Notifier 类中只保留 IListener 的弱引用,但结果仍然相同。
【问题讨论】:
-
有没有办法重新架构它,这样你就不会保留对客户端的对象引用?例如,为什么不使用队列来发送/接收消息?不要将对象排队,而是将消息排队。
-
System.out.println(listener+" removed");这一行是执行还是另一行?注意:您需要同步您对列表的遍历。 -
“已删除”。为简洁起见,我没有包括同步。
-
即使不取消导出监听器也应该可以工作,事实上它甚至可以更好地工作,但它需要比 5 秒长得多。查看 RMI 系统属性页面中的默认 DGC 计时器和租用间隔。
-
@user207421 :
UnicastRemoteObject.unexportObject(notifier, true);仅用于客户端的“干净”退出。我运行它还在两个 JVM 上设置了-Djava.rmi.dgc.leaseValue=1000,但无济于事。