【初学】RMI(Remote Method Invocation)初窥门径

RMI原理与实践
本文介绍了RMI(远程方法调用)的基本原理及其在Java中的应用。重点讲解了Stub和Skeleton的概念,它们如何帮助实现客户端与远程服务器间的交互。通过一个具体示例,展示了RMI在分布式系统中的实现细节。

【引言】


作为J2EE核心技术之一的RMI,它允许客服端调用一个远程服务器的组件,并返回调用结果(返回值或异常),可以完成分布式应用。整个调用过程由RMI实现,对使用者透明。

【Stub和Skeleton】


1、要了解RMI原理,Stub和Skeleton是必须先理解的两个概念

以下引用别的一段话:

做个比方说明这两个概念。 假如你是A,你想借D的工具,但是又不认识D的管家C,所以你找来B来帮你,B认识C。B在这时就是一个代理,代理你的请求,依靠自己的话语去借。C呢他负责D家东西收回和借出 ,但是要有D的批准。在得到D的批准以后,C再把东西给B,B呢再转给A。stub和skeleton在RMI中就是角色就是B和C,他们都是代理角色,在现实开发中隐藏系统和网络的的差异, 这一部分的功能在RMI开发中对程序员是透明的。Stub为客户端编码远程命令并把他们发送到服务器。而Skeleton则是把远程命令解码,调用服务端的远程对象的方法,把结果在编 码发给stub,然后stub再解码返回调用结果给客户端。所有的操作如下图所示

Stub为远程对象在客户端的一个代理,当客户端调用远程对象的方法时,实际是委托Stub这个代理去调用远程的对象,这个调用过程如下:
(1)初始化一个与远程JVM的连接
(2)写入并传输参数给远程JVM
(3)执行远程对象的方法调用,并等待调用结果的返回(return)
(4)读取调用的返回值(也可能是一个异常)
(5)返回调用的结果给调用者
这些操作(序列号参数, 建立Socket连接等等),都由这个Stub来透明化。


在远程的JVM中,每一个对象(需要被远程调用的对象)都有一个相应的skeleton(在Java2环境中,这个skeleton不是必须的,这个先不说),skeleton的作用是分发客户端的调用到具体的实现类,skeleton接受 一个客户端过来的调用过程如下:
(1)读取客户端传递过来的参数
(2)调用实现类的方法
(3)写入并传输返回结果给调用者,同样的,这个结果也是函数调用结果或异常


2、Stub和Skeleton在什么位置产生

Stub和Skeleton都是在服务器产生的。

Stub存在于客户端,作为客户端的代理,让我们总是认为客户端产生了stub,接口没有作用。实际上stub类是通过Java动态类下载 机制下载的,它是由服 务端产生,然后根据需要动态的加载到客户端,如果下次再运行这个客户端该存根类存在于classpath中,它就不需要再下载了,而是直接加载。总的来说,stub是在服务端产生的,如果服务端的stub内容改变,那么客户端的也是需要同步更新。

【一个简单的例子】


1、服务器端
接口定义
package test.server; import java.rmi.Remote; import java.rmi.RemoteException; public interface IMyServer extends Remote { public int add(int a, int b) throws RemoteException; public int mul(int a, int b) throws RemoteException; }

接口实现类

package test.server.impl; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; import test.server.IMyServer; public class MyServer extends UnicastRemoteObject implements IMyServer { private static final long serialVersionUID = 1L; public MyServer() throws RemoteException { super(); } @Override public int add(int a, int b) throws RemoteException { return a + b; } @Override public int mul(int a, int b) throws RemoteException { return a * b; } }
用于测试的服务器端

package test.server.impl; import java.rmi.AlreadyBoundException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class ServerTest { private static Registry registry = null; public static Registry getInstance() throws RemoteException { try { return registry == null ? (registry = LocateRegistry .createRegistry(1099)) : registry; } catch (RemoteException e) { throw e; } } private ServerTest() { } public static void main(String[] args) throws InterruptedException { try { MyServer server = new MyServer(); getInstance().bind("server", server); } catch (RemoteException e) { e.printStackTrace(); } catch (AlreadyBoundException e) { e.printStackTrace(); } System.err.println("server bind end"); } }
2、客服端

首先客服端需要有远程对象的接口IMyServer。

客服端测试类

package test.client; import java.rmi.NotBoundException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import test.server.IMyServer; public class ClientTest { public static void main(String[] args) { try { Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099); IMyServer server = (IMyServer) registry.lookup("server"); int a = 2, b = 10; System.err.println(server.add(a, b)); System.err.println(server.mul(a, b)); } catch (RemoteException e) { e.printStackTrace(); } catch (NotBoundException e) { e.printStackTrace(); } } }

3、运行

(1)先运行服务器端的ServerTest
(2)运行客户端的ClientTest,输出12 20

【分析】

1、首先,远程对象和调用传递的参数都必须实现接口Serializable接口,关系系列化与反系列化可以参看我的另一篇文章 《Java Serializable系列化与反系列化》
2、MyServer继承UnicastRemoteObject的作用与好处

先来看看UnicastRemoteObject这个对象的结构,如下图:

(1)先来看看java.rmi.server.RemoteObject这个类,首先它实现了Serializable, Remote这两个接口
RemoteObject的真正作用在于,他重写了Object的hashCode、equals、和toString方法,重写hashCode方法使得远程对象可以存放在Hashtable等一些哈希结果的集合中,重写equals方法使得远程对象可以进行比较,而重写后的toString方法返回远程对象的描述串。该类还实现专门的(私用)方法writeObject 和方法readObject,用于系列化与反系列化。
(2)java.rmi.server.RemoteServer类
在这个类中,则提供了一些对调试非常有用的方法,它一共有3个方法:
static String getClientHost():返回一个客户机主机的字符串表示形式,用于在当前线程中正在处理的远程方法调用。
static PrintStream getLog():返回用于 RMI 调用日志的流。
static void setLog(OutputStream out):将 RMI 调用记录到输出流 out 中。

同时要注意,该类是abstract的。
(3)java.rmi.server.UnicastRemoteObject类
再看看这个类,通常,远程对象都 继承UnicastRemoteObject类,UnicastRemoteObject 类提供远程对象所需的基本行为。在这个类中提供了支持创建和导出远程对象的一系列方法,一个对象继承UnicastRemoteObject它将获得以下特性:

A、对这种对象的引用至多仅在创建该远程对象的进程生命期内有效

B、使得远程对象既有使用TCP协议通信的能力(Socket)
C、对于客户端与服务器的调用、传参、返回值等操作使用流的方式来处理
其他的,java.rmi.registry.LocateRegistry类提供了一系列的方法用于创建、获取Registry实例的方法;在Registry接口中,定义了一系列的方法,用于操作远程对象包括:绑定对象(bind)、获取对象(lookup)、重写绑定(rebind)、解除绑定(unbind)和返回注册表绑定列表(list)方法(也可以使用java.rmi.Naming来操作)。


刚学习RMI,不对的地方欢迎指出

Java RMIRemote Method Invocation)是一种允许在不同 Java 虚拟机(JVM)之间进行远程方法调用的机制,其底层实现依赖于动态代理技术。通过动态代理,RMI 能够让客户端像调用本地方法一样调用远程服务端的方法,而无需显式处理网络通信细节。 ### 动态代理在 RMI 中的作用 在 Java RMI 中,客户端通过代理对象与远程服务进行交互。这个代理对象由 `java.lang.reflect.Proxy` 类生成,并且实现了与远程接口相同的接口。当客户端调用代理对象的方法时,该调用会被转发到一个 `InvocationHandler` 实现类中,从而将方法调用封装为远程请求发送至服务端。 这种机制的核心在于: - **代理类的动态生成**:RMI 在运行时生成代理类,确保客户端可以透明地调用远程方法。 - **方法调用拦截**:所有对代理对象的方法调用都会被 `InvocationHandler` 拦截,进而转换为网络请求。 - **远程通信抽象化**:通过代理和处理器的配合,隐藏了底层网络传输、序列化、反序列化等复杂性 [^1]。 ### RMI 的核心组件及工作流程 Java RMI 主要包括以下几个关键组件: - **远程接口(Remote Interface)**:定义远程服务的方法,必须继承 `java.rmi.Remote` 接口,且每个方法需声明抛出 `RemoteException`。 - **远程实现类(Remote Implementation)**:具体实现远程接口的服务端类。 - **RMI 注册中心(Registry)**:用于注册和查找远程对象引用的服务。 - **动态代理对象**:客户端通过代理对象发起远程方法调用 [^3]。 #### 工作流程简述 1. 服务端启动 RMI 注册中心并绑定远程对象。 2. 客户端通过 `Naming.lookup()` 或 `Registry.getRegistry()` 获取远程接口的代理对象。 3. 客户端调用代理对象的方法,触发 `InvocationHandler` 的 `invoke()` 方法。 4. `invoke()` 方法将方法调用信息序列化后发送至服务端。 5. 服务端接收请求,执行真实对象的方法并将结果返回给客户端 [^4]。 ### 示例代码 以下是一个基于 JDK 动态代理实现的简化版 RMI 远程调用示例: ```java import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.net.*; import java.io.*; // 定义远程接口 interface RemoteService { String sayHello(String name) throws Exception; } // 实现类 class RemoteServiceImpl implements RemoteService { public String sayHello(String name) { return "Hello, " + name; } } // 自定义 InvocationHandler 实现远程调用逻辑 class RemoteInvocationHandler implements InvocationHandler { private String host; private int port; public RemoteInvocationHandler(String host, int port) { this.host = host; this.port = port; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try (Socket socket = new Socket(host, port); ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream()); ObjectInputStream in = new ObjectInputStream(socket.getInputStream())) { // 发送方法名、参数类型和参数值 out.writeUTF(method.getName()); out.writeObject(method.getParameterTypes()); out.writeObject(args); // 接收返回结果 return in.readObject(); } } } // 简单服务器端监听请求 class SimpleServer { public static void start(RemoteService service, int port) throws IOException { ServerSocket serverSocket = new ServerSocket(port); System.out.println("Server started on port " + port); while (true) { Socket socket = serverSocket.accept(); new Thread(() -> handleClient(socket, service)).start(); } } private static void handleClient(Socket socket, RemoteService service) { try (ObjectInputStream in = new ObjectInputStream(socket.getInputStream()); ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream())) { String methodName = in.readUTF(); Class<?>[] paramTypes = (Class<?>[]) in.readObject(); Object[] args = (Object[]) in.readObject(); Method method = service.getClass().getMethod(methodName, paramTypes); Object result = method.invoke(service, args); out.writeObject(result); } catch (Exception e) { e.printStackTrace(); } } } // 测试类 public class RMIDemo { public static void main(String[] args) throws Exception { // 启动服务端 new Thread(() -> { try { SimpleServer.start(new RemoteServiceImpl(), 8080); } catch (IOException e) { e.printStackTrace(); } }).start(); Thread.sleep(1000); // 等待服务启动 // 创建代理对象 RemoteService proxy = (RemoteService) Proxy.newProxyInstance( RMIDemo.class.getClassLoader(), new Class[]{RemoteService.class}, new RemoteInvocationHandler("localhost", 8080) ); // 调用远程方法 String response = proxy.sayHello("World"); System.out.println(response); } } ``` 输出结果: ``` Server started on port 8080 Hello, World ``` 上述代码演示了如何利用 JDK 动态代理模拟 RMI 的远程调用过程,其中 `RemoteInvocationHandler` 负责将方法调用序列化并通过网络发送至服务端。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值