23.java-RMI使用实例(Remote Method Invocation,远程方法调用)

发布于:2025-05-11 ⋅ 阅读:(21) ⋅ 点赞:(0)

23.java-RMI使用实例(Remote Method Invocation,远程方法调用)

RMI(Remote Method Invocation,远程方法调用)是 Java 提供的一种技术,允许在不同的 Java 虚拟机(JVM)之间进行通信,就像在同一个 JVM 中调用方法一样。RMI 提供了分布式应用程序开发的 API,采用客户/服务器通信方式,在服务器上部署提供各种服务的远程对象,客户端请求访问服务器上远程对象的方法。

核心概念
  1. 远程对象:实现了远程接口的对象,可以在不同的 JVM 上运行。

存根:远程对象的引用
3. 存根(Stub):客户端用来调用远程对象的方法的代理。每个远程对象都包含一个代理对象 stub,当运行在本地 Java 虚拟机上的程序调用运行在远程 Java 虚拟机上的对象方法时,它首先在本地创建该对象的代理对象 stub,然后调用代理对象上匹配的方法。
4. 骨架(Skeleton):服务器端用来接收客户端请求并转发给远程对象的组件。骨架读取 stub 传递的方法参数,调用服务器方的实际对象方法,并接收方法执行后的返回值。不过,从 Java 2(即 JDK 1.2)开始,Skeleton 的生成就不再是必需的了,因为 RMI 使用动态代理来自动生成存根和骨架。
5. 注册表(Registry):用于存储远程对象的引用,客户端可以通过注册表查找远程对象。

通信流程
  1. 客户端调用远程对象的方法时,实际上是调用本地存根对象的相应方法。
  2. 存根对象采用一种与平台无关的编码方式,把方法的参数编码为字节序列(参数编组),然后把这个请求信息发送给服务器。请求信息包括被访问的远程对象的名字、被调用的方法的描述、编组后的参数的字节序列。
  3. 服务器端接收到客户端的请求信息后,由相应的骨架对象来处理这一请求信息。骨架对象反编组参数,定位要访问的远程对象,调用远程对象的相应方法,获取方法调用产生的返回值或者异常,然后对它进行编组,把编组后的返回值或者异常发送给客户端。
  4. 客户端的存根接收到服务器发送过来的编组后的返回值或者异常,再对它进行反编组,就得到调用远程方法的返回结果。

代码实例

下面通过一个简单的示例来讲解 RMI 原理以及开发流程。

注意:确保客户端使用的接口与服务器端实现的接口完全一致。
检查接口的包名、类名和方法签名是否一致。
比如下面的Rmiserver.java和RmiClient.java 均 import method.SayHello;

项目结构
rmidemo/
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   ├── method/
│   │   │   │   └── SayHello.java
│   │   │   ├── rmiclient/
│   │   │   │   └── RmiClient.java
│   │   │   └── rmiserver/
│   │   │       ├── HelloImpl.java
│   │   │       └── RmiServer.java
代码实现

idea中创建普通的java项目,标记 src/main/java 为“源代码根目录“

  1. 远程接口 SayHello.java
package method;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface SayHello extends Remote {
    public String sayhello(String name) throws RemoteException;
}
  • 定义了一个远程接口 SayHello,它继承自 Remote 接口,并且接口中的方法 sayhello 声明抛出 RemoteException 异常。
    在这里插入图片描述
  1. 远程接口实现类 HelloImpl.java
package rmiserver;

import method.SayHello;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class HelloImpl extends UnicastRemoteObject implements SayHello {
    public HelloImpl() throws RemoteException {
        super();
    }

    @Override
    public String sayhello(String name) throws RemoteException {
        return "Hello, I am " + name;
    }
}
  • 实现了远程接口 SayHello,并继承了 UnicastRemoteObject 类,这表明该类可以作为远程对象,被注册到注册中心,供客户端远程调用。
  • 重写了 sayhello 方法,实现了具体的业务逻辑。
    在这里插入图片描述
  1. 服务端 RmiServer.java
package rmiserver;

import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RmiServer {
    public static void main(String[] args) {
        try {
            HelloImpl obj = new HelloImpl();
            Registry registry = LocateRegistry.createRegistry(1099);
            registry.bind("Hello", obj);
            System.out.println("Server ready");
        } catch (Exception e) {
            System.err.println("Server exception: " + e.toString());
            e.printStackTrace();
        }
    }
}
  • 创建了一个远程对象 obj
  • 创建了一个 Registry 实例,监听端口 1099。
  • 将远程对象 obj 绑定到注册表中,绑定名称为 "Hello"
    在这里插入图片描述
  1. 客户端 RmiClient.java
    在 RmiClient 代码中,存根的使用是隐式的,由 RMI 框架自动处理。
    通过 registry.lookup(“Hello”) 获取的是远程对象的引用(存根),而不是对象实例本身。
    存根负责将客户端的方法调用序列化并转发到服务器,同时也负责将服务器的响应反序列化并返回给客户端。
    这种机制使得客户端可以像调用本地对象一样调用远程对象的方法。
package rmiclient;

import method.SayHello;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RmiClient {
    public static void main(String[] args) {
        try {
            Registry registry = LocateRegistry.getRegistry("localhost", 1099);
            SayHello stub = (SayHello) registry.lookup("Hello");
            String response = stub.sayhello("World");
            System.out.println("response: " + response);
        } catch (Exception e) {
            System.err.println("Client exception: " + e.toString());
            e.printStackTrace();
        }
    }
}
  • 获取注册表的引用,指定主机名为 "localhost",端口为 1099。
  • 通过注册表查找绑定名称为 "Hello" 的远程对象,得到其存根对象 stub
  • 调用存根对象上的 sayhello 方法,就像调用本地对象的方法一样,实际上是在调用服务器上的远程方法。
    在这里插入图片描述

运行步骤

  1. 启动服务端程序 RmiServer
  2. 启动客户端程序 RmiClient 进行调用。
    在这里插入图片描述
    在这里插入图片描述
    根据上面,就已经完成了RMI的远程方法调用。

注意事项

  • 确保客户端和服务端的类路径一致或兼容,因为 RMI 使用 Java 序列化机制。
  • RMI 依赖于底层的 TCP/IP 协议进行通信,因此需要注意网络延迟和故障可能会影响 RMI 的性能和可靠性。
  • 在生产环境中,需要注意 RMI 的安全性问题,不建议将 1099 端口暴露在公网上。

补充:绑定名称和端口测试

修改绑定名称和端口,可正常运行
在这里插入图片描述
在这里插入图片描述

补充:两台电脑实际模拟

服务端运行RmiServer类
在这里插入图片描述
将整个项目rmidemo项目复制到客户端,其中包含了所有的.java .class文件
在这里插入图片描述
客户端执行java -cp rmidemo/out/production/rmidemo rmiclient.RmiClient
命令解释 -cp rmidemo/out/production/rmidemo:
指定了类路径(classpath),即 JVM 查找类文件的路径。

rmidemo/out/production/rmidemo 是类路径的起始目录,JVM 将从这个目录开始查找类文件。
rmiclient.RmiClient:
这是要运行的 Java 类的全限定名(fully qualified name),包括包名和类名。
rmiclient 是包名,RmiClient 是类名。

成功调用服务器的方法
在这里插入图片描述
误区:直接java RmiClient.class是不行的。
在这里插入图片描述
继续测试,发现只复制out目录到客户端也是可以的
在这里插入图片描述
在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到