You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
解决RMI服务关闭时客户端抛出UnmarshalException的问题

咱们先理清楚你遇到的问题根源:当你在RMI服务的closeBookManager()方法里直接调用System.exit(0),服务器进程会立刻终止——这相当于直接把RMI通信的连接掐断了,而客户端此时还在等着服务器返回方法调用的响应结果,自然就会因为输入流突然中断抛出EOFException,进而引发UnmarshalException。简单说,就是服务器没走完RMI调用的正常响应流程就“跑路”了,客户端直接懵了。

至于你尝试的解绑(unbind)和取消导出(unexport)没效果,大概率是操作顺序不对,或者参数设置有问题:比如unexportObject默认是不强制取消的,如果此时还有活跃的RMI调用,取消操作会失败;另外如果只解绑了命名服务里的对象,RMI对象本身还是处于导出状态,依然能被客户端调用。

接下来给你两个可行的解决方案:

方案一:用标记变量让主线程处理关闭逻辑

这种方式最稳妥,让RMI方法只传递关闭信号,真正的清理和退出由服务器的主线程(也就是你那个永真循环的线程)来完成,保证RMI调用能正常返回给客户端。

  1. 在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;
    }
}
  1. 修改服务器主线程的循环逻辑,检查标记变量:
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 07:12:49