当前位置: 首页 > news >正文

Java安全 RMI远程调用

RMI概念

RMI全名叫做远程方法调用,指让某个java虚拟机中的对象调用另一个java虚拟机中的对象上的方法,调用分为server端和clinet端
image.png

Server端流程是:
定义要做什么(一个接口)->学习怎么做(一个实现类)->注册服务

public interface IRemoteHelloWorld extends Remote { public String hello() throws RemoteException; }
public class RemoteHelloWorld extends UnicastRemoteObject implements IRemoteHelloWorld { public String hello() { return "Hello world"; } }
LocateRegistry.createRegistry(1099); Naming.rebind("rmi://127.0.0.1:1099/Hello", new RemoteHelloWorld());

Client的流程是:
找到注册的服务->调用

IRemoteHelloWorld hello = (IRemoteHelloWorld) Naming.lookup("rmi://192.168.135.142:1099/Hello");``` java
String result = hello.hello(); // 输出 "Hello world"

代码示例

Server端:

package org.example;  import java.rmi.Naming;  
import java.rmi.Remote;  
import java.rmi.RemoteException;  
import java.rmi.registry.LocateRegistry;  
import java.rmi.server.UnicastRemoteObject;  // 1. 定义远程接口  
interface IRemoteHelloWorld extends Remote {  String hello() throws RemoteException; // 声明远程调用方法  
}  // 2. 实现远程接口  
class RemoteHelloWorldImpl extends UnicastRemoteObject implements IRemoteHelloWorld {  protected RemoteHelloWorldImpl() throws RemoteException {  super(); // 必须显式调用父类构造  }  @Override  public String hello() throws RemoteException {  System.out.println("收到远程调用请求");  return "Hello world"; // 返回结果给客户端  }  
}  // 3. 服务端启动类  
public class RMIServer {  private void start() throws Exception {  // 创建远程对象实例  IRemoteHelloWorld helloObj = new RemoteHelloWorldImpl();  // 创建RMI注册表(端口1099)  LocateRegistry.createRegistry(1099);  System.out.println("RMI注册表启动成功");  // 绑定对象到注册表(使用0.0.0.0或服务端实际IP)  Naming.rebind("rmi://0.0.0.0:1099/Hello", helloObj);  System.out.println("远程对象已绑定");  }  public static void main(String[] args) throws Exception {  new RMIServer().start(); // 启动服务  }  
}

Client端

package org.example;  // 客户端接口定义  
import java.rmi.Naming;  public class TrainMain {  public static void main(String[] args) throws Exception {  try {  // 查找远程对象  IRemoteHelloWorld hello = (IRemoteHelloWorld)  Naming.lookup("rmi://192.168.137.102:1099/Hello");  // 调用远程方法  String ret = hello.hello();  System.out.println("服务器返回: " + ret);  } catch (Exception e) {  System.err.println("RMI客户端异常: " + e.getMessage());  e.printStackTrace();  }  }  
}

image.png

代码详解

详解一下具体的代码:

// 1. 定义远程接口
interface IRemoteHelloWorld extends Remote {  String hello() throws RemoteException; // 声明远程调用方法  
}  
// 2. 实现远程接口  
class RemoteHelloWorldImpl extends UnicastRemoteObject implements IRemoteHelloWorld {  protected RemoteHelloWorldImpl() throws RemoteException {  super(); // 必须显式调用父类构造  }  @Override  public String hello() throws RemoteException {  System.out.println("收到远程调用请求");  return "Hello world"; // 返回结果给客户端  }  
}

接口

首先可以看到interface,是Java中定义接口的关键字,是定义一组方法规范的方式。
在接口中,只声明方法但不写方法的具体实现。
接口就像是行为规范/产品规范

// USB接口规范(就像Java接口)
interface USB {void 传输数据();void 供电();
}// 实际产品(实现接口)
class U盘 implements USB {public void 传输数据() { 把文件拷入拷出(); }public void 供电() { 用5V电压(); }
}class 手机充电器 implements USB {public void 传输数据() { 不支持数据(); }public void 供电() { 输出5V2A(); }
}

IRemoteHelloWorld 中为什么写了一个String hello(),其实也蛮好理解,不过这里涉及到了关于RMI返回类型的限制,详细说说

RMI返回类型限制

RMI的一切都需要在网络上传,而在网络上只能传输字节流。
如果我在北京,服务器在上海,我又想调用上海的资源,则全流程如下:

北京客户端代码:User user = userService.getUser(123);↑看起来是直接调用,实际上是:1. 打包参数"123" → 字节流2. 字节流通过网线送到上海3. 上海服务器解包,执行真正的getUser(123)方法4. 服务器得到User对象,打包成字节流5. 字节流通过网线送回北京6. 北京客户端解包,得到User对象

在传输过程中,很清楚的看到Java进行了序列化与反序列化的过程。但是不是所有类型都能序列化的,由此RMI返回类型限制诞生了:

能够序列化的对象?

  1. 基本类型:int、double、boolean,由于序列化的过程是将数据转换成字节流,而这些基本类型本身就能变成数字,数字就能变字节
  2. String:文字能变成UTF-8字节,所以可以被转换
  3. 实现了Serialize接口的类

不能够序列化的对象:

  1. 有状态的对象:比如一个正在运行的Thread线程,运行状态无法传送
  2. 有资源的对象:比如数据库Connection,数据库连接是点对点不可以打包传输
  3. 操作系统资源:比如FileInputStream,文件句柄不能远程调用(这句话可能会有争议。RMI传递的是对象的序列化副本或RMI传递的是对象的序列化副本存根 传递过去毫无意义)

实现

关键字implements
extends UnicastRemoteObject,跟进可以看到此处首先继承了RemoteServer
image.png

在上述代码中发现port,怀疑是绑定的端口,但是下断点调试时却并没有走到这个文件里。所以先详细分析一下这个文件:

UnicastRemoteObject分析

首先是一段很有意思的代码:
image.png

跟踪RMIClientSocketFactory
image.png

发现居然是一个接口,也就是说上一张图片是把接口作为了类型,于是就产生了问题:这个接口去做类型到底有什么用?
约束和契约
此接口类型的作用是定义一个变量能做什么,而不关心它具体是谁做的

private RMIClientSocketFactory csf;
// 意思是:csf必须是一个"能创建Socket"的东西
// 至于它具体是什么类,不重要

再往下来
image.png
这个方法看起来很诡异,实则很Server端中的代码密不可分。在Server端代码的第17行中有个super(0)的构造方法
image.png
原来的代码继承了UnicastRemoteObject,所以通过super(0)让UnicastRemoteObject先行执行:创建一个RMI对象,使用自动分配的端口(当端口等于0时分配随机端口)
那假如开发特别牛逼,就是想自定义

// 简单用法(适合80%的人)
public class MyService extends UnicastRemoteObject {public MyService() throws RemoteException {super();  // 自动端口+默认配置,简单!}
}// 高级用法(需要定制的人用)
public class MyService extends UnicastRemoteObject {public MyService() throws RemoteException {super(1099, new SSLSocketFactory(), null);  // 自定义配置}
}

再往下看就是几个重载
image.png
再往下是反序列化+导出RMI对象
image.png

端口

首先要说明,在RMI中,要让一个对象能够被远程调用得解决两个问题

怎么变成一个网络服务

首先要开一个网络端口等待别人来连接,其次得把方法调用变成网络消息传输,还要有个线程池处理并发请求,最后就是处理断开、超时这些琐事。

业务上提供的功能

像之前Server端的示例代码中,调用hello()返回Hello world是业务逻辑逻辑,与网络没有关系。所以Java为了方便,允许开发者直接继承UnicastRemoteObject,将网络服务那些琐事直接封装在里面。当构造函数执行时,UnicastRemoteObject在后台:

  1. 找个空闲端口打开监听(比如54321)

  2. 准备好线程池

  3. 生成一个Stub(代理对象)

  4. 开始等客户端连接

这也就产生了两个端口的问题,因为首先服务需要有一个自己的服务端口,其次需要绑定到注册表上的端口。
如上述Client代码,在extend UnicastRemoteObject之后,在服务端启动类new远程对象实例时,一个服务端口就已经起来了。
1099就是绑定到注册表的端口。
为了便于理解举个例子:
服务端口是酒店具体房间,每个房间提供不同的服务(服务端口)
而绑定端口就是前台,当访问某个服务时首先要访问绑定端口,然后再去到对应的服务。
这也就产生了,同一个绑定端口可以有多个服务:

// 启动三个服务
UserService userService = new UserServiceImpl();     // 自动分配端口,比如50001
OrderService orderService = new OrderServiceImpl();  // 自动分配端口,比如50002  
ProductService productService = new ProductServiceImpl(); // 自动分配端口,比如50003// 注册到Registry
Naming.rebind("rmi://localhost:1099/UserService", userService);
Naming.rebind("rmi://localhost:1099/OrderService", orderService);  
Naming.rebind("rmi://localhost:1099/ProductService", productService);

http://www.jsqmd.com/news/444199/

相关文章:

  • 2026咖啡全自动商用选购指南:性能 + 运维 + 成本一文看懂 - 品牌2026
  • 基于GIS的无人机城市调度与监测平台
  • Jenkins详解实战
  • WPF STA线程
  • 2026 商务全自动商用咖啡机选购指南推荐,从入门到高配实用选型指南 - 品牌2026
  • 厂家提醒:选购全自动总磷总氮分析仪,除了参数还应关注什么? - 品牌推荐大师1
  • Git 忽略本地修改 不再出现在本地改变
  • 【往届已完成EI检索】第二届物理学与量子计算国际学术会议(ICPQC 2026)
  • 堵车不堵心,健力宝多品类为春节返程旅客“补水续航” - 速递信息
  • 【ACM出版-EI稳定检索】第二届健康大数据国际学术会议(HBD 2026)
  • 2026年儿童羽绒服十大名牌排名,宝妈宝爸闭眼入! - 品牌测评鉴赏家
  • 2026年绝缘陶瓷管/陶瓷支架厂家权威推荐:宜兴胜达耐火陶瓷,氧化铝/滑石瓷/堇青石全系绝缘陶瓷供应商 - 品牌推荐官
  • 2026线下买童装去哪家?宝妈实测! - 品牌测评鉴赏家
  • 2026三八女神节NMN抗衰榜:NMN品牌怎么选? - 速递信息
  • 论文降AI率省钱攻略:如何用最少的钱达到最好的降AI效果 - 我要发一区
  • 2026 全自动商用咖啡机哪家技术强?商用高性价比品牌推荐 - 品牌2026
  • 2026酒精厌氧絮状菌种直销:揭秘行业优质厂家排行,酒精厌氧絮状菌种选哪家深度剖析助力明智之选 - 品牌推荐师
  • 降AI率不只靠工具!手动+工具组合降AI的进阶技巧 - 我要发一区
  • 【业务架构】用蜘蛛表格搭建销售管理系统:表结构设计+自动化规则详解 - 蜘蛛小助理
  • 2026智慧环卫管理平台推荐:郑州森鹏物联网,提供环卫一体化综合管理解决方案 - 品牌推荐官
  • 2026年重庆搬家搬厂厂家榜单 专业团队护航 适配家庭与个性化需求 解决各类搬迁核心痛点 - 深度智识库
  • 2026年2月山东干选机/智能干选设备/煤矸分选设备/大块煤矸分选设备/智能干选系统/干选设备厂家专业推荐:口碑与技术双优榜单 - 2026年企业推荐榜
  • 2026年碳酸钙系列原料推荐:石家庄驰霖矿产品,纳米/重质/活性/造纸用/超细碳酸钙全系供应 - 品牌推荐官
  • 2026年用户推荐:这些精密倒角机厂家值得信赖,全自动倒角机/双头倒角机/数控倒角机/圆棒倒角机,精密倒角机厂家怎么选择 - 品牌推荐师
  • 2026年地面/混凝土/瓷砖/抹灰/墙面空鼓治理推荐:浙江耐威德化工科技专业方案 - 品牌推荐官
  • phpStudy v8.1 离线版一键安装包(小皮面板)
  • 2026优质腾讯企业邮箱代理商推荐,正规渠道一站式服务指南 - 品牌2026
  • 宝妈宝爸必看!2026线下童装宝藏店铺大揭秘 - 品牌测评鉴赏家
  • 宝妈必藏|2026优质儿童鞋服品牌盘点,帮你轻松搞定娃的穿搭选品 - 品牌测评鉴赏家
  • 猫眼电影内容可视化与智能分析平台 | Python Flask框架 Echarts 推荐算法 爬虫 大数据 毕业设计源码