server

先编写一个接口,必须继承Remote,定义一个方法,这个方法就是远程被调用的方法接口

1
2
3
4
5
6
7
8
package org.javasec.RMI.rmi1;

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

public interface test extends Remote {
void printest(String str) throws RemoteException;
}

再编写一个接口实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package org.javasec.RMI.rmi1;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class testimpl extends UnicastRemoteObject implements test {
public testimpl() throws RemoteException {
super();
}

public void printest(String str) {
System.out.println("远程方法-调用成功");
System.out.println("你好 "+str);
}
}

必须调用父类的构造方法,实现定义的接口

然后开始编写服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package org.javasec.RMI.rmi1;

import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;

public class RmiServer {
public static void main(String[] args) throws RemoteException, MalformedURLException {
LocateRegistry.createRegistry(1099);
System.out.println("RMI注册表-启动成功");
test t1= new testimpl();
Naming.rebind("rmi://127.0.0.1:1099/test",t1);
System.out.println("Test服务注册启动-成功");
System.out.println("Test服务运行中......");
}
}

在一个端口,启动注册表,然后实例化远程对象,把服务命名绑定到对象上启动服务

Client

lookup根据命名查找远程服务方法,然后直接像本地那样调用就行

1
2
3
4
5
6
7
8
9
10
11
12
package org.javasec.RMI.rmi1;

import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;

public class RmiClient {
public static void main(String[] args) throws java.rmi.RemoteException, MalformedURLException, NotBoundException {
test t1=(test) Naming.lookup("rmi://127.0.0.1:1099/test");
t1.printest("X1ly?S");
}
}

image-20251110114017571

理解通信和结构

rmi有三个核心组件

1
2
3
4
5
┌─────────────┐   查找    ┌─────────────┐   注册    ┌─────────────┐
│ RMI Client│ ────────> │ RMI Registry│ <──────── │ RMI Server │
│ │ │ │ │ │
│ 调用远程方法 │ │ 作为命名服务 │ │ 提供具体实现 │
└─────────────┘ └─────────────┘ └─────────────┘

整个RMI通信过程会建立两次TCP连接,为什么呢?

image-20251110114940191

  • 第一步-连接Registry,根据命名查找远程服务(第一次TCP握手)
1
test t1=(test) Naming.lookup("rmi://127.0.0.1:9090/test");

这里client发起TCP握手,连接Registry 9090端口,这个不是server仅仅是注册表,提供了服务命名和具体对象的映射管理,本身不执行远程方法

  • 第二步-Registry 返回远程对象引用

在 ReturnData 消息中包含序列化数据

1
192.168.135.142:33769

这个端口是动态分配的,真正的远程方法的端口,用于实际的远程方法调用。

  • 第三步-建立实际的方法调用连接(第二次 TCP 握手)
1
t1.printest("X1ly?S");

这里才真正连接到 33769 端口进行方法调用

Registry 只做一件事:维护名称到对象的映射关系,它不执行实际的远程方法。