解决RMI服务关闭时客户端抛出UnmarshalException的问题
咱们先理清楚你遇到的问题根源:当你在RMI服务的closeBookManager()方法里直接调用System.exit(0),服务器进程会立刻终止——这相当于直接把RMI通信的连接掐断了,而客户端此时还在等着服务器返回方法调用的响应结果,自然就会因为输入流突然中断抛出EOFException,进而引发UnmarshalException。简单说,就是服务器没走完RMI调用的正常响应流程就“跑路”了,客户端直接懵了。
至于你尝试的解绑(unbind)和取消导出(unexport)没效果,大概率是操作顺序不对,或者参数设置有问题:比如unexportObject默认是不强制取消的,如果此时还有活跃的RMI调用,取消操作会失败;另外如果只解绑了命名服务里的对象,RMI对象本身还是处于导出状态,依然能被客户端调用。
接下来给你两个可行的解决方案:
方案一:用标记变量让主线程处理关闭逻辑
这种方式最稳妥,让RMI方法只传递关闭信号,真正的清理和退出由服务器的主线程(也就是你那个永真循环的线程)来完成,保证RMI调用能正常返回给客户端。
- 在RMI服务类里添加一个volatile的标记变量(保证多线程可见性):
public class BookManagerServiceImpl extends UnicastRemoteObject implements BookManagerService { private volatile boolean isShutdown = false; // 构造方法等其他代码... public void closeBookManager() throws RemoteException { isShutdown = true; System.out.println("收到关闭信号,准备退出服务器..."); } public boolean isShutdown() { return isShutdown; } }
- 修改服务器主线程的循环逻辑,检查标记变量:
public class Server { public static void main(String[] args) { // 初始化RMI服务、绑定到注册表等代码... BookManagerServiceImpl service = new BookManagerServiceImpl(); Scanner in = new Scanner(System.in); while (true) { String input = in.nextLine(); if(input.equals("closeBookManager()")) { service.closeBookManager(); break; } // 或者在每次循环迭代时检查关闭标记 if (service.isShutdown()) { break; } } // 循环退出后,执行清理操作 try { // 解绑命名服务中的对象 Naming.unbind("rmi://localhost/BookManagerService"); // 强制取消导出RMI对象(第二个参数true表示强制关闭,即使有活跃调用) UnicastRemoteObject.unexportObject(service, true); // 如果是服务器自己启动的注册表,也可以关闭它 Registry registry = LocateRegistry.getRegistry(); registry.unbind("BookManagerService"); } catch (Exception e) { e.printStackTrace(); } // 最后安全退出 System.exit(0); } }
方案二:异步执行关闭逻辑
如果一定要在RMI方法里触发关闭,可以启动一个新线程来执行清理和退出操作,这样RMI方法可以正常返回响应给客户端,之后服务器再关闭。
修改closeBookManager()方法:
public void closeBookManager() throws RemoteException { new Thread(() -> { try { // 给RMI响应留一点时间,确保客户端能收到返回 Thread.sleep(100); // 执行解绑操作 Naming.unbind("rmi://localhost/BookManagerService"); // 强制取消导出对象 UnicastRemoteObject.unexportObject(this, true); // 退出进程 System.exit(0); } catch (Exception e) { e.printStackTrace(); } }).start(); }
关键注意点
- 调用
UnicastRemoteObject.unexportObject()时,一定要传入第二个参数true,这样可以强制取消导出,避免因为有活跃调用导致取消失败。 - 如果你的RMI注册表是通过
rmiregistry命令单独启动的,你不需要(也不能)关闭它,只需要解绑自己的服务对象即可。
内容的提问来源于stack exchange,提问作者juicyFruit
相关产品推荐
相关产品推荐
