Традиционный подход к выполнению кода на других машинах, разнесенных по сети может смутить из-за своей нудной и склонной к ошибкам реализации. Лучший способ рассмотреть эту проблему состоит в предположении, что некоторые объекты располагаются на другой машине, и что вы можете посылать сообщения этим удаленным объектам и получать результат, как будто они располагаются на вашей локальной машине. Это упрощение в точности является тем, что позволяет делать Удаленный Вызов Методов (RMI) в Java. Этот раздел сделает обзор шагов, необходимых для создания вашего собственного RMI объекта.

Удаленный интерфейс

RMI делает тяжелым использование интерфейсов. Когда вы хотите создать удаленный объект, вы маскируете лежащую в основе реализацию, общаясь по интерфейсу. То есть, когда клиент получает ссылку на удаленный объект, реально он получает ссылку на интерфейс, который соединен с некоторым локальным кодом заглушки, которая общается по сети. Но вы не думаете об этом, вы просто посылаете сообщения через вашу ссылку на интерфейс.

Когда вы создаете удаленный интерфейс, вы должны следовать следующему руководству:

  • Удаленный интерфейс должен быть публичным (он не может иметь "пакетный уровень доступа", таким образом он не может быть "дружественным"). В противном случае, клиент получит ошибку при попытке загрузить удаленный объект, который реализует удаленный интерфейс.
  • Удаленный интерфейс должен наследоваться от интерфейса java.rmi.Remote.
  • Каждый метод удаленного интерфейса должен декларировать java.rmi.RemoteException в своем разделе throws в дополнение ко всем остальным специфичным для приложения исключениям.
  • Удаленный объект, передающийся как аргумент или возвращаемое значение (либо напрямую, либо как встроенная часть локального объекта), должен быть декларирован как удаленный интерфейс, а не класс реализации.

Вот простейший удаленный интерфейс, который представляет службу точного времени:

//: c15:rmi:PerfectTimeI.java
// The PerfectTime remote interface.
package c15.rmi;

import java.rmi.*;

public interface PerfectTimeI extends Remote {
  
long getPerfectTime() throws RemoteException;
} // /:~

Он выглядит как и любой другой интерфейс, за исключением того, что он является наследником Remote и все его методы выбрасывают RemoteException. Помните, что все методы интерфейса автоматически являются публичными.

Реализация удаленного интерфейса

Сервер должен содержать класс, который наследуется от UnicastRemoteObject и реализует удаленный интерфейс. Этот класс может также иметь дополнительные методы, но для клиента, конечно, будут доступны только методы удаленного интерфейса, так как клиент будет получать только ссылку на интерфейс, а не на класс, реализующий его.

Вы должны явно определить конструктор для удаленного объекта, даже если вы определяете только конструктор по умолчанию, который вызывает конструктор базового класса. Вы должны написать его, так как он должен выбрасывать RemoteException.

Вот реализация удаленного интерфейса PerfectTimeI:

//: c15:rmi:PerfectTime.java
// Реализация удаленного
// объекта PerfectTime.
// {Broken}
package c15.rmi;

import java.rmi.*;

import java.rmi.server.*;

import java.rmi.registry.*;

import java.net.*;

public class PerfectTime extends UnicastRemoteObject implements PerfectTimeI {
  
// Реализация интерфейса:
  
public long getPerfectTime() throws RemoteException {
     
return System.currentTimeMillis();
  
}
  
  
// Необходимо реализовывать конструктор,
   // выбрасывающий RemoteException:
  
public PerfectTime() throws RemoteException {
     
// super(); // Вызывается автоматически
  
}
  
  
// Регистрация для обслуживания RMI. Выбрасывает
   // исключения на консоль.
  
public static void main(String[] args) throws Exception {
     
System.setSecurityManager(new RMISecurityManager());
      PerfectTime pt =
new PerfectTime();
      Naming.bind
("//peppy:2005/PerfectTime", pt);
      System.out.println
("Ready to do time");
  
}
}
// /:~

Здесь main( ) обрабатывает все детали установки сервера. Когда вы обслуживаете RMI объекты, в некотором месте вашей программы вы должны:

  • Создать и установить менеджер безопасности, который поддерживает RMI. Для RMI досупен только один такой менеджер, как часть пакета Java, он называется RMISecurityManager.
  • Создать один или несколько экземпляров удаленного объекта. Здесы вы можете видеть создание объекта PerfectTime.
  • Зарегистрировать как минимум один удаленный объект в RMI репозитории удаленных объектов в целях загрузки. Один удаленный объект может иметь методы, которые производят ссылки на другие удаленные объекты. Это позволит вам сделать так, чтобы клиент обращался к репозиторию только один раз, чтобы получить первый удаленный объект.

Настройка репозитория

В этом примере вы видите вызов статического метода Naming.bind( ). Однако, этот вызов требует, чтобы репозиторий был запущен как отдельный процесс на компьютере. Имя сервера репозитория rmiregistry, в 32-х системе Windows вы используете команду:

start rmiregistry

для запуска его в фоновом режиме. Для системы Unix используется команда:

rmiregistry &

Как и многие сетевые программы, rmiregestry получает IP адрес той машины, на которой она запущена, но она также должна слушать порт. Если вы вызовите rmiregestry как это показано выше, без аргументов, для репозитория будет использован порт по умолчанию 1099. Если вы хотите, чтобы репозиторий был на другом порту, вы добавляете аргумент в командную строку, который указывает порт. Например, интересующий порт 2005, так что rmiregestry должна быть запущена следующей командой под 32-х битной Windows:

start rmiregistry 2005

или для Unix:

rmiregistry 2005 &

Информация о номере порта также должна быть передана в команду bind( ), точно так же, как и IP адрес машины, где расположен репозиторий. Но таким образом мы получаем препятствие, если вы хотите запустить RMI программу локально, способ тестирования сетевых программ был указан в этой главе. В JDK 1.1.1 существует пара проблем:[2]

  1. localhost не работает с RMI. То есть, экспериментируя с RMI на одной единственной машине, вы должны предоставить имя машины. Чтобы найти имя вашей машины в 32-х битной среде Windows, запустите контрольную панель и выберите "Network". Выберите закладку "Identification", и вы увидите имя вашего компьютера. Во многих случаях я называю мой компьютер "Peppy". Заметьте, что регистр букв игнорируется.
  2. RMI не будет работать до тех пор, пока ваш компьютер не будет иметь активного TCP/IP соединения, даже если все компоненты просто общаются друг с другом на вашей локальной машине. Это означает, что вы должны соединить ваш компьютер с вашим провайдером Internet прежде, чем пробовать запускать программу или вы получите непонятное сообщение об исключении.

Учитывая все вышесказанное, команда bind( ) принимает вид:

Naming.bind("//peppy:2005/PerfectTime", pt);

Если вы используете порт по умолчанию 1099, вам не нужно указывать порт, так что вы можете написать:

Naming.bind("//peppy/PerfectTime", pt);

Вы должны быть способны выполнить локальное тестирование оставив в покое IP адрес и используя только идентификатор:

Naming.bind("PerfectTime", pt);

Имя службы произвольно, в данном случае это PerfectTime, что совпадает с именем класса, но вы можете использовать любое название, какое хотите. Важно то, что оно должно быть уникальным в известном клиенту репозитории, к которому он обращается для производства удаленного объекта. Если имя уже есть в репозитории, вы получите AlreadyBoundException. Чтобы предотвратить это, вы можете всегда использовать rebind( ) вместе bind( ), так как rebind( ) либо добавляет новую запись, либо заменяет уже существующую.

Даже после того, как main( ) завершит работу, ваш объект будет создан и зарегистрирован, так что он останентся жить в репозитории, ожидая прихода клиентов и запросов от них. До тех пор, пока работает rmiregistry и вы не вызвали метод Naming.unbind( ) с вашим именем, объект будет существовать. По этой причине, когда вы разрабатываете ваш код, вам необходимо выгружать rmiregistry и перезапускать его, когда вы скомпилировали новую версию вашего удаленного объекта.

Вам не нужно принудительно запускать rmiregistry, как внешний процесс. Если вы знаете, что ваше приложение является единственным, использующим репозиторий, вы можете запускать его внутри программы с помощью строки:

LocateRegistry.createRegistry(2005);

Как и прежде, 2005 - это номер порта, который мы будем использовать в этом примере. Это эквивалентно запуску rmiregistry 2005 из командной строки, но такой запуск часто будет более последовательным, когда вы разрабатываете RMI код, так как при этом пропускаются дополнительные шаги запуска и остановки репозитория. Как только вы выполните этот код, вы можете вызвать bind( ), используя Naming, как и прежде.

Создание заглушек и скелетов

Если вы скомпилируете и запустите PerfectTime.java, программа не будет работать, даже если у вас будет правильно запущенный репозиторий. Это происходит потому, что рабочая среда для RMI еще не до конца создана. Вы должны сначала создать заглушки и скелеты, которые обеспечат операцию сетевого соединения и позволят вам считать, что удаленный объект - это просто другой локальный объект на вашей машине.

То, что происходит за сценой достаточно сложно. Любые объекты, котоые вы передаете при вызове или получаете от удаленного объекта должны реализовывать Serializable (если вы хотите передавать удаленную ссылку вместо самого объекта, объектные аргументы могут реализовывать Remote), так что вы можете представить, что заглушки и скелеты автоматически выполняют сериализацию и десериализацию, как будтно они "руководят" всеми аргументами через сеть и возвращают результат. К счастью, вам не нужно знать ничего этого, но вы должны создать заглушки и скелеты. Это простой процесс: вы вызываете инструмент rmic для вашего скомпилированного кода и создаете необходимые файлы. Таким образом требуется просто добавить еще один шаг в процесс компиляции.

Однако, инструмент rmic привередлив к пакетам и classpaths. PerfectTime.java расположен в пакете c15.rmi, и если вы вызовите rmic в том же директории, в котором расположен PerfectTime.class, rmic не найдет файл, так как он ищет в переменной classpath. Таким образом вы должны указать расположение в переменной classpath, например так:

rmic c15.rmi.PerfectTime

Вам не нужно находиться в директории, содержащем PerfectTime.class, когда вы запускаете эту команду, а результат будет помещено в текущий каталог.

Когда запуск rmic завершится успешно, вы получите два новых класс в директории:

PerfectTime_Stub.class
PerfectTime_Skel.class

соответственно это заглушка и скелет. Теперь вы готовы получить сервер и клиент для общения между собой.

Использование удаленного объекта

Главная цель RMI состоит в упрощении использования удаленных объектов. Есть только одна дополнительная вещь, которую вы должны выполнить в клиентской программе, это найти и получить удаленный интерфейс с сервера. Все остальное - это обычное Java программирование: посылка сообщений объекту. Вот программа, которая использует PerfectTime:

//: c15:rmi:DisplayPerfectTime.java
// Использование удаленного объекта PerfectTime.
// {Broken}
package c15.rmi;

import java.rmi.*;

import java.rmi.registry.*;

public class DisplayPerfectTime {
  
public static void main(String[] args) throws Exception {
     
System.setSecurityManager(new RMISecurityManager());
      PerfectTimeI t =
(PerfectTimeI) Naming
            .lookup
("//peppy:2005/PerfectTime");
     
for (int i = 0; i < 10; i++)
        
System.out.println("Perfect time = " + t.getPerfectTime());
  
}
}
// /:~

Строка идентификатора точно такая же, как и используемая для регистрации объекта с помощью Naming, а первая часть представляет URL и номер порта. Так как вы используете URL, вы можете также указать машину в Internet'е.

То, что приходит от Naming.lookup( ) должно быть приведено к удаленному интерфейсу, а не к классу. Если вместо этого вы будете использовать класс, вы получите исключение.

Вы можете видеть вызов метода

t.getPerfectTime()

так как то, что вы имеете - это ссылка на удаленный объект, программирование с которой не отличается от программирования с локальным объектом (с одним отличием: удаленные методы выбрасывают RemoteException).