《JavaTM程序设计语 言基础》第 1 部分
第 8 课:远程方法调用

[<<返回] [目录]

JavaTM远程方法调用(RMI)应用编程接 口(API)能够实现客户机和服务器的网络通信。一般情况下,由客户机程序向服 务器程序发出请求,而服务器程序则对这些请求作出响应。

其常见的示例是网络中的共享文字处理程序。文字处理器安装在服务器上,任 何想使用它的人都可以在自己的计算机上通过双击桌面上的图标或在命令行键入 其文件名来启动这个文字处理器。这两个调用操作都是向服务器程序发送一个访 问该软件的请求,而服务器程序的响应则是使该软件可为请求者使用。

用户可利用 RMI API 生成一个可被共享访问的远程服务器对象,以使客户机和服 务器通过服务器对象上的简单程序调用实现通信。客户机可轻易地直接与服务器 对象进行通信,也可以使用通用资源地址(URL)和超文本传输协议(HTTP)并通 过服务器实现相互间的间接通信。

本课将解释如何使用 RMI API 创建客户机和服务器之间的通信。


关于示例程序的说明

本课的示例程序是把 第 6 课:文件访问及权限 中的 文件输入与输出 应用程序转换为 RMI API。

程序动作

The RMIClient1 程序是一个简单的用户界面,供用户输入文本。当您点击 Click Me 按钮时,所输入的文本信息就通过远程服务器对象发送给 RMIClient2 程序。当您在 RMIClient2 程序上点击 Click Me 按钮时,RMIClient1 中的文本信息就 会出现。`

客户机 1 的第一实例

如果您启动 RMIClient1 的第二实例,并且键入了一些文本信 息;然后在您点击 Click Me 按钮后,该文本信息就发送给 RMIClient2 。若要查看 RMIClient2 接受到的文本 信息,只需点击其 Click Me 按钮即可。

客户机 1 的第二实例

文件汇总

如图所示,示例程序由 RMIClient1 程序、远程对象、接口和以及 RMIClient2 程序组成。这些可执行文件所对应的源代码文件见以下列表。

  • RMIClient1.java: 在 RemoteServer 服务器对象上调用 sendData 方法的客户机程序。
  • RMIClient2.java: 在 RemoteServer 服务器对象上调用 getData 方 法程序的客户机程序。
  • RemoteServer.java: 执行 Send.javasendDatagetData 远程方法的远程服务器对象。
  • Send.java: 声明 sendDatagetData 远程服务器方法的远 程接口程序。

此外,下述 java.policy 安全策略文件将授予运行本示例程序所需要的权限。

grant {
  permission java.net.SocketPermission 
               "*:1024-65535", 
               "connect,accept,resolve";
  permission java.net.SocketPermission 
               "*:80", "connect";
  permission java.awt.AWTPermission 
               "accessEventQueue";
  permission java.awt.AWTPermission 
               "showWindowWithoutWarningBanner";
};

编译示例程序

这些程序指令都假设开发工作是在 zelda 主目录下进行的。服 务器程序是在用户 zelda的主目录下编译的,但编译后拷贝到了用 户 zelda 运行程序的 public_html 目录下。

以下是 Unix 和 Win32 平台上命令序列,其后有相应的解释。

Unix:
cd /home/zelda/classes
javac Send.java
javac RemoteServer.java
javac RMIClient2.java
javac RMIClient1.java
rmic -d . RemoteServer
cp RemoteServer*.class /home/zelda/public_html/classes
cp Send.class /home/zelda/public_html/classes
Win32:
cd \home\zelda\classes
javac Send.java
javac RemoteServer.java
javac RMIClient2.java
javac RMIClient1.java
rmic -d . RemoteServer
copy RemoteServer*.class \home\zelda\public_html\classes
copy Send.class \home\zelda\public_html\classes

前两条 javac 命令用于编译 RemoteServerSend 类及接口。第三条 javac 命令用于编译 RMIClient2 类。最后一个 javac 命令用于编译 RMIClient1 类。

接下来的命令行则运行 RemoteServer 服务器类上的 rmic 命令。该命令生成 ClassName_Stub.classClassName_Skel.class 形式的输出类文件。这些输出类让客户机调 用 RemoteServer 服务器对象上的方法。

第一条拷贝命令用其关联的 skelstub 类文 件把 RemoteServer 类文件移动到 /home/zelda/public_html/classes 目录下的一个公用地址,此目录位于服务器计算机上,所以可被共享访问和下载。 文件放置在运行于服务器计算机中Web服务器下的 public_html 目 录下,这样客户机程序才能通过 URL 访问这些文件。

第二条拷贝命令把 Send 类文件移动到相同的地址,实现同样 的目的。RMIClient1RMIClient2 类文件不可以 共享;它们利用 URL 从各自的客户机进行通信,以访问和下载 public_html 目录下的目标文件。

  • RMIClient1 是从客户端目录下被调用的,它使用服务器端的 Web服务器和客户端的 Java VM 下载共享文件。
  • RMIClient2是从客户端的目录下调用的,它使用服务器端的 Web服务器和客户端的 Java VM 下载共享文件。

启动 RMI 注册程序

启动客户机程序前,您必须启动 RMI 注册程序,它是服务器端允许远程客户 机获取远程服务器对象引用的命名仓库。

启动 RMI 注册程序前,请确保运行 rmiregistry 命令的命令解 释程序或窗口没有指向系统任何位置的远程对象类(包括 stubskel 类)的 CLASSPATH 环境变量。如果 RMI 注册 程序在启动时发现有这些类,它就不会从服务器端的 Java VM 加载这些类,这样 客户机试图下载远程服务器类时会出现问题。

以下命令将使 CLASSPATH 清空,并启动缺省 1099 端口上的 RMI 注册程序。您可以通过按以下方式添加端口号来指定其它端口:rmiregistry 4444 &。如果指定了不同的端口号,就必须在 服务器端 代码中也指定相同的端口号。

Unix:
cd /home/zelda/public_html/classes
unsetenv CLASSPATH
rmiregistry &
Win32:
cd \home\zelda\public_html\classes
set CLASSPATH=
start rmiregistry 

注意:您可以把 CLASSPATH 设置到其在此点 的原始设置值。

运行 RemoteServer 服务器对象

运行实例程序前,应首先启动 RemoteServer。如果您首先启动 了 RMIClient1RMIClient2,它们就会因没有运 行远程服务器对象而无法创建连接。

本例中,RemoteServer 是从 /home/zelda/public_html/classes 目录下启动的。

java 程序的开始几行程序应是一在断行处都用空格隔开的整行。 用解释器命令的 -D 选项指明的属性是控制该次调用的程序动作的 程序属性。

Unix:
cd /home/zelda/public_html/classes
java 
-Djava.rmi.server.codebase=http://kq6py/~zelda/classes
-Djava.rmi.server.hostname=kq6py.eng.sun.com
-Djava.security.policy=java.policy RemoteServer
Win32:
cd \home\zelda\public_html\classes
java -Djava.rmi.server.codebase=file:
       c:\home\zelda\public_html\classes
-Djava.rmi.server.hostname=kq6py.eng.sun.com
-Djava.security.policy=java.policy RemoteServer
  • java.rmi.server.codebase 属性指明了共享类所在的位 置。
  • java.rmi.server.hostname 属性是共享类所在服务器的完 整主机名。
  • java.rmi.security.policy 属性指明了带有运行远程服务 器和访问并下载远程服务器类时所需要的权限的 策略文件。 属性指明了带有运行远程服务器和访问并下载远程服务器类 时所需要的权限的 策略文件。
  • 要执行的类(RemoteServer)。

运行 RMIClient1 程序

以下是 Unix 和 Win32 平台下的命令序列,其后有相应的解释。

本例中,RMIClient1 是从 /home/zelda/classes 目录中启动的。

java 程序的开始几行程序应是一在断行处用空格隔开的整行。 用解释器命令的 -D 选项指明的属性是控制该次调用的程序动作的 程序属性。

Unix:
cd /home/zelda/classes

java -Djava.rmi.server.codebase=
                         http://kq6py/~zelda/classes/
-Djava.security.policy=java.policy 
                  RMIClient1 kq6py.eng.sun.com
Win32:
cd \home\zelda\classes

java -Djava.rmi.server.codebase=
                         file:c:\home\zelda\classes\
-Djava.security.policy=java.policy 
                         RMIClient1 kq6py.eng.sun.com
  • java.rmi.server.codebase属性指明了供下载的共享类所在 的位置。
  • java.security.policy 属性指明了带有运行客户机程序和 访问远程服务器类时所需要的权限的 策略文件
  • 要执行的客户程序类(RMIClient1)和远程服务器类所在的 服务器的主机名(Kq6py)。

运行 RMIClient2程序

以下是 Unix 和 Win32 平台下的命令序列,其后有相应的解释。

本例中,RMIClient2 是从 /home/zelda/classes 目录中启动的。

java 程序的开始几行程序应是一在断行处用空格隔开的整行。 用解释器命令的 -D 选项指明的属性是控制该次调用的程序动作的 程序属性。

Unix:
cd /home/zelda/classes
java -Djava.rmi.server.codebase=
                         http://kq6py/~zelda/classes
-Djava.security.policy=java.policy 
                         RMIClient2 kq6py.eng.sun.com
Win32:
cd \home\zelda\classes
java -Djava.rmi.server.codebase=
                file:c:\home\zelda\public_html\classes
-Djava.security.policy=java.policy 
                         RMIClient2 kq6py.eng.sun.com
  • java.rmi.server.codebase 属性指明了共享类所在的位置。
  • java.rmi.server.hostname 属性是共享类所在服务器的完 整主机名。
  • java.rmi.security.policy 属性指明了带有运行远程服务 器对象和访问并下载远程服务器类时所需要的权限的 策略文件
  • 要执行的类(RMIClient2)。

RemoteServer 类

RemoteServer 类对 UnicastRemoteObject 进行了扩展并实现 Send 接口中声明 的 sendDatagetData 方法。这些都 是可远程访问的方法。

UnicastRemoteObject 实现远程对象的许多 java.lang.Object 方法,而且包含了使远程对象可以用于接收来自客户程序的方法调用的构造函数 和静态方法。

class RemoteServer extends UnicastRemoteObject
                 implements Send {

  String text;

  public RemoteServer() throws RemoteException {
    super();
  }

  public void sendData(String gotText){
    text = gotText;
  }

  public String getData(){
    return text;
  }

main 方法的作用是安装 RMISecurityManager 并 打开与服务器程序所运行的计算机上的某个端口的一个连接。安全管理器的作用是 确定是否有使下载程序执行需要具备权限的任务的策略文件。main 方法还为 RemoteServer 对象命名,其中包括运行 RMI 注册程序和 远程对象的服务器的名称(kq6py)以及 Send

服务器在缺省状态下以端口 1099 为其名称。若想使用其它端口名,则可以添 加该端口号并以冒号分隔,如:kq6py:4444。若在此处修改了端口, 就必须启动具有相同端口号的 RMI 注册程序

try 程序块的作用是生成 RemoteServer 类的一 个实例,并通过 Naming.rebind(name, remoteServer); 语句把 name 捆绑到 RMI 注册程序的远程对象。

  public static void main(String[] args){
    if(System.getSecurityManager() == null) {
      System.setSecurityManager(new 
               RMISecurityManager());
    }
    String name = "//kq6py.eng.sun.com/Send";
    try {
      Send remoteServer = new RemoteServer();
      Naming.rebind(name, remoteServer);
      System.out.println("RemoteServer bound");
    } catch (java.rmi.RemoteException e) {
      System.out.println("Cannot create 
                   remote server object");
    } catch (java.net.MalformedURLException e) {
      System.out.println("Cannot look up 
                   server object");
    }
  }
}

注意:remoteServer 对象是 Send 型(见类顶部的实例声明),因为客户机可用的接口是 Send 接 口及其方法,而不是 RemoteServer 类及其方法。

Send 接口

Send 接口声明了 RemoteServer 类中实现的方法。这些都是可远程访问 的方法。

public interface Send extends Remote {

  public void sendData(String text) 
                throws RemoteException;
  public String getData() throws RemoteException;
}

RMIClient1 类

RMIClient1 类为远程服务器程序创建连接,并向远程服务器对象发送数据。完成这些任务的 程序代码编写在 actionPerformedmain 方法中。

actionPerformed 方法

actionPerformed 方法调用 RemoteServer.sendData 方法来向远程服务器对象发送文本信息。

public void actionPerformed(ActionEvent event){
   Object source = event.getSource();

   if(source == button){
//Send data over socket
      String text = textField.getText();
      try{
        send.sendData(text);
      } catch (java.rmi.RemoteException e) {
        System.out.println("Cannot send data to server");
      }
      textField.setText(new String(""));
   }
}

main 方法

main 程序完成的任务是安装 RMISecurityManager 并生成一个用于查看 RemoteServer 服务器对象的 name。客户机利用 Naming.lookup 方法查看运行在 服务器上的 RMI 注册程序的 RemoteServer 对象。

安全管理器确定是否允许下载程序执行要求具备权限的任务的策略文件。

  RMIClient1 frame = new RMIClient1();

  if(System.getSecurityManager() == null) {
    System.setSecurityManager(new RMISecurityManager());
  }

  try {
//args[0] contains name of server where Send runs
    String name = "//" + args[0] + "/Send";
    send = ((Send) Naming.lookup(name));
  } catch (java.rmi.NotBoundException e) {
    System.out.println("Cannot look up 
                 remote server object");
  } catch(java.rmi.RemoteException e){
    System.out.println("Cannot look up 
                 remote server object");
  } catch(java.net.MalformedURLException e) {
    System.out.println("Cannot look up 
                 remote server object");
  }

RMIClient2 类

RMIClient2 类创建与远程服务器程序的连接、从远程服务器对象获取数据并显示所得数据。完 成这些任务的程序代码编写在 actionPerformedmain 方法中。

actionPerformed 方法

actionPerformed 方法调用 RemoteServer.getData 方法来检索客户程序发送来的数据。该数据将插入到 TextArea 对 象中,供在服务器侧显示给终端用户。

public void actionPerformed(ActionEvent event) {
   Object source = event.getSource();

   if(source == button){
      try{
        String text = send.getData();
        textArea.append(text);
      } catch (java.rmi.RemoteException e) {
        System.out.println("Cannot send data 
                     to server");
      }
      }
   }
}

main 方法

main 程序完成的任务是安装 RMISecurityManager 并生成一个用于查看 RemoteServer 服务器对象的 name。参数 args[0] 提供了服务器主机的名称。客 户机利用 Naming.lookup 方法查看运行在服务器上的 RMI 注册程 序的 RemoteServer 对象。

安全管理器确定是否允许下载程序执行要求具备权限的任务的策略文件。

  RMIClient2 frame = new RMIClient2();

  if(System.getSecurityManager() == null) {
    System.setSecurityManager(new RMISecurityManager());
  }

  try {
    String name = "//" + args[0] + "/Send";
    send = ((Send) Naming.lookup(name));
  } catch (java.rmi.NotBoundException e) {
    System.out.println("Cannot look up remote 
                 server object");
  } catch(java.rmi.RemoteException e){
    System.out.println("Cannot look up remote 
                 server object");
  } catch(java.net.MalformedURLException e) {
    System.out.println("Cannot look up remote 
                 server object");
  }

更多信息

有关 RMI API 的更多信息,可见于 Java 教程 中的 RMI 部分。

[TOP]

 

 

常见问答
下载中心
产品简介
 
 
Solaris论坛
 
   
 
null