使用远程方法调用:Java ME中的分布式编程
1 理解Java RMI
Java编程语言的一个关键特性是其支持分布式编程——即应用程序可以在不同的主机上运行,通过Java RMI范式来回传递对象。所有分布式面向对象应用程序需要执行三种类型的任务:
- 远程对象发现 :应用程序需要机制来发现对远程对象的引用。
- 远程通信 :运行在不同机器上不同上下文中的应用程序需要能够就对象状态的变化进行通信。
- 远程行为定义 :应用程序还必须能够就那些对象的定义进行通信。
Java RMI通过提供远程发现注册表和为交换Java类和对象的信息提供健壮的通信机制,满足分布式程序的两个关键需求。图1展示了基于RMI的应用程序中客户端和服务器之间的关系。
graph LR
A[客户端] -- "RMI调用" --> B[RMI注册表]
B -- "对象引用" --> C[远程对象]
C -- "方法调用" --> D[远程方法实现]
D -- "返回结果" --> C
C -- "返回结果" --> A
2 Java RMI架构
基于RMI的解决方案有几个关键组成部分:
- RMI客户端应用程序 :这在客户端的JVM内运行。支持RMI客户端应用程序的是Java远程方法协议(JRMP)。
- RMI注册表 :由应用服务器维护,这为RMI客户端提供了对象发现功能。
- RMI服务器 :由应用服务器的JVM托管,负责执行远程对象。
- Web服务器 :这负责向RMI客户端应用程序提供新代码服务。
使用RMI构建分布式应用程序就像构建任何其他Java应用程序一样:通过接口和类。在你的分布式应用程序中,一些对象是可远程调用的——也就是说,你可以通过RMI提供的协议在不同的JVM之间调用可远程调用对象的方法。你可以通过在已知服务器上运行的RMI注册表获得这些对象的引用;注册表返回一个实现了远程对象提供的方法的存根。在底层,远程对象与本地对象不同;相反,它是一个代理,实现了与远程JVM实例的对象通信的网络接口。
3 编写远程服务
编写远程服务的过程可以分为几个步骤:
-
编写Java接口
:定义远程服务的接口,扩展
java.rmi.Remote接口,并声明所有方法可能抛出java.rmi.RemoteException。
java
public interface Location extends java.rmi.Remote {
public String getLocation() throws java.rmi.RemoteException;
public void setLocation(String l) throws java.rmi.RemoteException;
public String getForecast() throws java.rmi.RemoteException;
public void setForecast(String f) throws java.rmi.RemoteException;
}
-
实现远程服务
:基于
UnicastRemoteObject或Activatable实现接口。
```java
public class LocationImpl extends UnicastRemoteObject implements Location {
private String location;
private String forecast;
protected LocationImpl() throws RemoteException {
super();
}
@Override
public String getLocation() throws RemoteException {
return location;
}
@Override
public void setLocation(String l) throws RemoteException {
this.location = l;
}
@Override
public String getForecast() throws RemoteException {
return forecast;
}
@Override
public void setForecast(String f) throws RemoteException {
this.forecast = f;
}
}
```
- 生成存根类 :如果你正在部署解决方案的一部分在Java SE或Java EE上,那么请从实现中生成存根类。
bash
javac Location.java LocationImpl.java
rmic LocationImpl
- 编写远程服务宿主容器 :创建一个小型Java应用程序,用于创建和管理远程服务。
```java
import java.rmi.Naming;
public class LocationServer {
public LocationServer() {
try {
Location c = new LocationImpl();
Naming.rebind(“rmi://localhost:1099/LocationService”, c);
} catch (Exception e) {
System.out.println(“Exception:” + e);
}
}
public static void main(String args[]) {
new LocationServer();
}
}
```
4 运行远程服务
要在独立工作站上运行服务进行测试,您必须首先启动RMI注册表,这是一个Java应用程序,用于将对象注册到LocationServer,以便客户端能够找到。您可以在一个shell中启动
rmiregistry
,并在另一个shell中使用
java
启动Java虚拟机来运行
LocationServer
。
rmiregistry &
java LocationServer
5 客户端调用远程对象
从客户端调用远程对象非常简单:只需查找远程服务并获取远程对象的实例。
import java.rmi.*;
public class LocationClient {
public static void main(String[] args) {
try {
Location c = (Location) Naming.lookup("rmi://localhost/LocationService");
// Use the remote object
} catch (Exception e) {
System.out.println("Exception: " + e);
}
}
}
在调用远程对象时,你必须确保指定了远程主机——在这个例子中是
localhost
——以及端口和提供对象的远程服务的名称。
java.rmi.Naming
类会处理其余的事情,联系远程服务并获取远程对象的实例。
6 RMI OP概述
RMI OP(远程方法调用对象协议)对于一个面向低端设备的标准来说,出人意料地全面。通过支持
UnicastRemoteObject
、分布式垃圾回收以及注册和导出远程对象的能力,启用RMI OP的硬件可以完全参与分布式计算,既充当客户端又充当服务器,在对象分发过程中表现出对称性。这种对称性是RMI OP相对于传统Web服务计算模型的一个关键特性,既因为它对于与Java SE RMI的完全互操作性支持是必需的,也因为它允许嵌入式设备向远程服务器提供对象。
实际上,在提供RMI OP的设备上,RMI可以用于同一设备上的应用程序间通信,或者用于网络上的真正分布式应用程序。RMI OP基于原始J2SE RMI实现及其线缆协议;它不是RMI-IIOP的实现,后者将CORBA带入Java。RMI OP提供远程调用语义、远程对象表示、从CDC设备导出对象以及分布式垃圾收集的功能。
| 特性 | 描述 |
|---|---|
| 远程调用语义 | 支持远程方法调用 |
| 远程对象表示 | 支持远程对象的表示 |
| 对象导出 | 支持从CDC设备导出对象 |
| 分布式垃圾回收 | 支持远程对象的垃圾回收 |
7 缺失的功能
尽管RMI OP功能强大,但它缺少了一些高级特性,例如:
- HTTP代理支持 :不支持通过HTTP代理RMI请求。
- 多路复用协议 :不支持RMI多路复用协议,该协议使得当只有一个通信通道存在时,多个JVM能够互相调用远程方法。
-
Activatable接口实现
:没有提供
Activatable接口的具体实现。 - 已弃用的方法、类和接口 :不支持已弃用的方法、类和接口。
- 骨架/存根协议 :不支持RMI骨架/存根协议,以及对存根和骨架编译器的需求。
这些缺陷确实有一些实际的影响,但并没有你想的那么多。首先,也是最限制性的一点是,RMI OP只能使用原始的基于TCP/IP套接字的RPC机制进行通信:它不支持通过防火墙的隧道传输。这直接影响了你部署应用程序的网络拓扑,因为你必须配置保护服务器的防火墙以允许Java RMI流量。如果你希望设备托管在激活之间不持久的对象,那么Activatable的具体实现的丢失可能是一个问题,但你可以自由提供自己的接口实现。不需要构建存根和骨架实际上是一种福气;你只需要为你的Java SE应用程序做这项工作。
8 编写和部署远程服务
8.1 编写远程服务接口
编写远程服务接口是使用RMI构建分布式应用程序的第一步。接口定义了远程服务的方法签名,所有方法必须声明抛出
RemoteException
,以处理远程调用可能出现的异常。以下是
Location
接口的定义:
public interface Location extends java.rmi.Remote {
public String getLocation() throws java.rmi.RemoteException;
public void setLocation(String l) throws java.rmi.RemoteException;
public String getForecast() throws java.rmi.RemoteException;
public void setForecast(String f) throws java.rmi.RemoteException;
}
8.2 实现远程服务
实现远程服务时,通常继承自
UnicastRemoteObject
类,并实现定义的远程接口。以下是
LocationImpl
类的实现:
public class LocationImpl extends UnicastRemoteObject implements Location {
private String location;
private String forecast;
protected LocationImpl() throws RemoteException {
super();
}
@Override
public String getLocation() throws RemoteException {
return location;
}
@Override
public void setLocation(String l) throws RemoteException {
this.location = l;
}
@Override
public String getForecast() throws java.rmi.RemoteException {
return forecast;
}
@Override
public void setForecast(String f) throws java.rmi.RemoteException {
this.forecast = f;
}
}
8.3 生成存根类
在Java SE或Java EE环境中,需要为远程服务生成存根类。存根类用于在客户端和服务器之间进行远程方法调用。可以通过以下命令生成存根类:
javac Location.java LocationImpl.java
rmic LocationImpl
8.4 编写远程服务宿主容器
远程服务宿主容器负责创建和管理远程服务实例,并将其注册到RMI命名服务中。以下是
LocationServer
类的实现:
import java.rmi.Naming;
public class LocationServer {
public LocationServer() {
try {
Location c = new LocationImpl();
Naming.rebind("rmi://localhost:1099/LocationService", c);
} catch (Exception e) {
System.out.println("Exception:" + e);
}
}
public static void main(String args[]) {
new LocationServer();
}
}
8.5 运行远程服务
要在独立工作站上运行服务进行测试,您必须首先启动RMI注册表。可以在一个shell中启动
rmiregistry
,并在另一个shell中使用
java
启动Java虚拟机来运行
LocationServer
:
rmiregistry &
java LocationServer
9 客户端调用远程对象
客户端调用远程对象的过程非常简单,只需查找远程服务并获取远程对象的实例。以下是
LocationClient
类的实现:
import java.rmi.*;
public class LocationClient {
public static void main(String[] args) {
try {
Location c = (Location) Naming.lookup("rmi://localhost/LocationService");
// 使用远程对象
System.out.println("Location: " + c.getLocation());
System.out.println("Forecast: " + c.getForecast());
} catch (Exception e) {
System.out.println("Exception: " + e);
}
}
}
在调用远程对象时,必须指定远程主机、端口和远程服务的名称。
java.rmi.Naming
类会处理其余的事情,联系远程服务并获取远程对象的实例。
10 RMI OP的应用场景
RMI OP适用于那些需要完全面向对象方法进行分布式计算的环境,或者是需要将Java ME设备连接到已有RMI服务的遗留环境。以下是RMI OP的一些典型应用场景:
- 机顶盒 :机顶盒通常需要与服务器进行通信以获取最新的内容更新或配置信息。RMI OP可以帮助实现这种通信。
- 嵌入式设备 :嵌入式设备可以通过RMI OP与其他设备或服务器进行通信,提供实时数据或接收控制指令。
- 传感器网络 :传感器网络中的节点可以通过RMI OP与中央服务器通信,报告数据或接收配置更新。
11 RMI OP的优势与局限
11.1 优势
- 对称性 :RMI OP允许设备既作为客户端又作为服务器,这在分布式计算中非常重要。
- 互操作性 :RMI OP与Java SE RMI完全互操作,确保了与现有Java RMI服务的兼容性。
- 安全性 :RMI OP提供了安全的通信通道,增强了分布式应用程序的安全性。
11.2 局限
- 通信机制 :RMI OP只能使用原始的基于TCP/IP套接字的RPC机制进行通信,不支持通过防火墙的隧道传输。
-
功能缺失
:RMI OP缺少一些高级特性,如HTTP代理支持、多路复用协议和
Activatable接口的具体实现。
12 实际应用案例
为了更好地理解RMI OP的实际应用,我们来看一个具体的案例:一个天气预报应用程序。该应用程序需要从远程服务器获取天气数据,并在本地设备上显示。
12.1 定义远程接口
首先,定义远程接口
Location
,用于表示位置和天气预报信息:
public interface Location extends java.rmi.Remote {
public String getLocation() throws java.rmi.RemoteException;
public void setLocation(String l) throws java.rmi.RemoteException;
public String getForecast() throws java.rmi.RemoteException;
public void setForecast(String f) throws java.rmi.RemoteException;
}
12.2 实现远程接口
接下来,实现远程接口
LocationImpl
,该类继承自
UnicastRemoteObject
并实现
Location
接口:
public class LocationImpl extends UnicastRemoteObject implements Location {
private String location;
private String forecast;
protected LocationImpl() throws RemoteException {
super();
}
@Override
public String getLocation() throws RemoteException {
return location;
}
@Override
public void setLocation(String l) throws RemoteException {
this.location = l;
}
@Override
public String getForecast() throws java.rmi.RemoteException {
return forecast;
}
@Override
public void setForecast(String f) throws java.rmi.RemoteException {
this.forecast = f;
}
}
12.3 编写远程服务宿主容器
编写远程服务宿主容器
LocationServer
,用于创建和管理远程服务实例,并将其注册到RMI命名服务中:
import java.rmi.Naming;
public class LocationServer {
public LocationServer() {
try {
Location c = new LocationImpl();
Naming.rebind("rmi://localhost:1099/LocationService", c);
} catch (Exception e) {
System.out.println("Exception:" + e);
}
}
public static void main(String args[]) {
new LocationServer();
}
}
12.4 编写客户端代码
编写客户端代码
LocationClient
,用于调用远程服务并获取天气数据:
import java.rmi.*;
public class LocationClient {
public static void main(String[] args) {
try {
Location c = (Location) Naming.lookup("rmi://localhost/LocationService");
System.out.println("Location: " + c.getLocation());
System.out.println("Forecast: " + c.getForecast());
} catch (Exception e) {
System.out.println("Exception: " + e);
}
}
}
12.5 测试应用程序
启动RMI注册表和远程服务:
rmiregistry &
java LocationServer
然后运行客户端代码:
java LocationClient
如果一切正常,客户端将成功获取并显示来自远程服务器的天气数据。
13 总结
通过以上内容,我们了解了如何在Java ME环境中使用远程方法调用(RMI)实现分布式编程。RMI OP为受限设备提供了强大的分布式计算能力,使其能够在网络上与其他设备或服务器进行通信。尽管RMI OP缺少一些高级特性,但在大多数情况下,它仍然能够满足分布式应用程序的需求。
RMI OP的对称性和互操作性使其成为嵌入式设备和机顶盒等应用的理想选择。通过合理的架构设计和适当的配置,RMI OP可以帮助开发者构建高效、安全的分布式应用程序。
| 功能 | 描述 |
|---|---|
| 远程调用 | 支持远程方法调用 |
| 对象表示 | 支持远程对象的表示 |
| 对象导出 | 支持从CDC设备导出对象 |
| 垃圾回收 | 支持远程对象的垃圾回收 |
通过理解RMI的基本概念和架构,掌握编写和部署远程服务的步骤,以及了解RMI OP的优势和局限,开发者可以更好地利用RMI在Java ME环境中构建分布式应用程序。
超级会员免费看
5743

被折叠的 条评论
为什么被折叠?



