阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

RMI远程调用

22次阅读
没有评论

共计 2831 个字符,预计需要花费 8 分钟才能阅读完成。

Java 的 RMI 远程调用是指,一个 JVM 中的代码可以通过网络实现远程调用另一个 JVM 的某个方法。RMI 是 Remote Method Invocation 的缩写。

提供服务的一方我们称之为服务器,而实现远程调用的一方我们称之为客户端。

我们先来实现一个最简单的 RMI:服务器会提供一个 WorldClock 服务,允许客户端获取指定时区的时间,即允许客户端调用下面的方法:

LocalDateTime getLocalDateTime(String zoneId);

要实现 RMI,服务器和客户端必须共享同一个接口。我们定义一个 WorldClock 接口,代码如下:

public interface WorldClock extends Remote {LocalDateTime getLocalDateTime(String zoneId) throws RemoteException;
}

Java 的 RMI 规定此接口必须派生自java.rmi.Remote,并在每个方法声明抛出RemoteException

下一步是编写服务器的实现类,因为客户端请求的调用方法 getLocalDateTime() 最终会通过这个实现类返回结果。实现类 WorldClockService 代码如下:

public class WorldClockService implements WorldClock {@Override
    public LocalDateTime getLocalDateTime(String zoneId) throws RemoteException {return LocalDateTime.now(ZoneId.of(zoneId)).withNano(0);
    }
}

现在,服务器端的服务相关代码就编写完毕。我们需要通过 Java RMI 提供的一系列底层支持接口,把上面编写的服务以 RMI 的形式暴露在网络上,客户端才能调用:

public class Server {public static void main(String[] args) throws RemoteException {System.out.println("create World clock remote service...");
        // 实例化一个 WorldClock:
        WorldClock worldClock = new WorldClockService();
        // 将此服务转换为远程服务接口:
        WorldClock skeleton = (WorldClock) UnicastRemoteObject.exportObject(worldClock, 0);
        // 将 RMI 服务注册到 1099 端口:
        Registry registry = LocateRegistry.createRegistry(1099);
        // 注册此服务,服务名为 "WorldClock":
        registry.rebind("WorldClock", skeleton);
    }
}

上述代码主要目的是通过 RMI 提供的相关类,将我们自己的 WorldClock 实例注册到 RMI 服务上。RMI 的默认端口是 1099,最后一步注册服务时通过rebind() 指定服务名称为"WorldClock"

下一步我们就可以编写客户端代码。RMI 要求服务器和客户端共享同一个接口,因此我们要把 WorldClock.java 这个接口文件复制到客户端,然后在客户端实现 RMI 调用:

public class Client {public static void main(String[] args) throws RemoteException, NotBoundException {// 连接到服务器 localhost,端口 1099:
        Registry registry = LocateRegistry.getRegistry("localhost", 1099);
        // 查找名称为 "WorldClock" 的服务并强制转型为 WorldClock 接口:
        WorldClock worldClock = (WorldClock) registry.lookup("WorldClock");
        // 正常调用接口方法:
        LocalDateTime now = worldClock.getLocalDateTime("Asia/Shanghai");
        // 打印调用结果:
        System.out.println(now);
    }
}

先运行服务器,再运行客户端。从运行结果可知,因为客户端只有接口,并没有实现类,因此,客户端获得的接口方法返回值实际上是通过网络从服务器端获取的。整个过程实际上非常简单,对客户端来说,客户端持有的 WorldClock 接口实际上对应了一个“实现类”,它是由 Registry 内部动态生成的,并负责把方法调用通过网络传递到服务器端。而服务器端接收网络调用的服务并不是我们自己编写的 WorldClockService,而是Registry 自动生成的代码。我们把客户端的“实现类”称为stub,而服务器端的网络服务类称为skeleton,它会真正调用服务器端的WorldClockService,获取结果,然后把结果通过网络传递给客户端。整个过程由 RMI 底层负责实现序列化和反序列化:

┌ ─ ─ ─ ─ ─ ─ ─ ─ ┐         ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐
  ┌─────────────┐                                  ┌────────────┐
│ │   Service   │ │         │                      │  Service   │ │
  └─────────────┘                                  └────────────┘
│        ▲        │         │                            ▲        │
         │                                               │
│        │        │         │                            │        │
  ┌─────────────┐   Network    ┌───────────────┐   ┌────────────┐
│ │ Client Stub ├─┼─────────┼─▶│Server Skeleton│──▶│Service Impl│ │
  └─────────────┘              └───────────────┘   └────────────┘
└ ─ ─ ─ ─ ─ ─ ─ ─ ┘         └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘

Java 的 RMI 严重依赖序列化和反序列化,而这种情况下可能会造成严重的安全漏洞,因为 Java 的序列化和反序列化不但涉及到数据,还涉及到二进制的字节码,即使使用白名单机制也很难保证 100% 排除恶意构造的字节码。因此,使用 RMI 时,双方必须是内网互相信任的机器,不要把 1099 端口暴露在公网上作为对外服务。

此外,Java 的 RMI 调用机制决定了双方必须是 Java 程序,其他语言很难调用 Java 的 RMI。如果要使用不同语言进行 RPC 调用,可以选择更通用的协议,例如 gRPC。

练习

使用 RMI 实现远程调用。

下载练习

小结

Java 提供了 RMI 实现远程方法调用:

RMI 通过自动生成 stub 和 skeleton 实现网络调用,客户端只需要查找服务并获得接口实例,服务器端只需要编写实现类并注册为服务;

RMI 的序列化和反序列化可能会造成安全漏洞,因此调用双方必须是内网互相信任的机器,不要把 1099 端口暴露在公网上作为对外服务。

正文完
星哥说事-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2024-08-05发表,共计2831字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中