1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/wizardforcel-thinking-in-java-zh

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
В этом репозитории не указан файл с открытой лицензией (LICENSE). При использовании обратитесь к конкретному описанию проекта и его зависимостям в коде.
Клонировать/Скачать
15.8 远程方法.md 21 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 11.03.2025 09:15 d56454c

15.8 Удалённые методы

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

15.8.1 Концепция удалённого интерфейса

RMI сильно зависит от интерфейсов. При создании удалённого объекта передаётся интерфейс, который скрывает детали реализации. Таким образом, когда клиент получает ссылку на удалённый объект, он фактически получает ссылку на интерфейс. Эта ссылка связана с некоторыми локальными корневыми компонентами, которые отвечают за сетевые взаимодействия. Однако нам не следует беспокоиться об этих деталях; достаточно использовать ссылку на свой интерфейс для отправки сообщений.

При создании удалённого интерфейса должны соблюдаться следующие правила:

(1) Удалённый интерфейс должен быть public (не может иметь доступ уровня пакета; то есть, он не может быть "дружественным"). В противном случае, если клиент попытается загрузить удалённый объект, реализующий этот интерфейс, возникнет ошибка.(2) Удалённый интерфейс должен расширять интерфейс java.rmi.Remote.

(3) Каждый метод в удалённом интерфейсе, за исключением тех, которые связаны с конкретной бизнес-логикой, должен объявлять java.rmi.RemoteException в своём блоке throws.

(4) Удалённый объект, передаваемый как аргумент или возвращаемый как результат (независимо от того, передаётся ли он непосредственно или содержится внутри локального объекта), должен быть объявлен как удалённый интерфейс, а не как реализация этого интерфейса.

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

//: PerfectTimeI.java
// Удалённый интерфейс PerfectTime
package c15.ptime;
import java.rmi.*;

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

Этот интерфейс похож на любой другой, но он расширяет Remote, и все его методы "бросают" RemoteException. Помните, что интерфейсы и все их методы являются public.

15.8.2 Реализация удалённого интерфейсаСервер должен содержать класс, расширяющий UnicastRemoteObject, и реализующий удаленный интерфейс. Этот класс также может содержать дополнительные методы, но клиент сможет использовать только те методы, которые определены в удаленном интерфейсе. Это очевидно, поскольку клиент имеет ссылку только на интерфейс, а не на класс, который его реализует. Для удаленного объекта необходимо явно определить конструктор, даже если планируется определение одного по умолчанию, который будет вызывать конструктор базового класса. Его следует явно реализовать, так как он обязан "вызвать" исключение RemoteException.Ниже приведён пример реализации удалённого интерфейса PerfectTime:

//: PerfectTime.java
// Реализация удалённого объекта PerfectTime
package c15.ptime;
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) {
    System.setSecurityManager(
      new RMISecurityManager());
    try {
      PerfectTime pt = new PerfectTime();
      Naming.bind(
        "//colossus:2005/PerfectTime", pt);
      System.out.println("Готово к работе с временем");
    } catch(Exception e) {
      e.printStackTrace();
    }
  }
} ///:~

Здесь метод main() контролирует все детали запуска сервера. При сохранении RMI-объекта необходимо выполнить следующие действия в какой-либо части программы:

(1) Создайте и установите安全管理器以支持RMI。作为Java发行版的一部分,适用于RMI的安全管理器唯一一个是RMISecurityManager

(2) 创建一个或多个远程对象的实例。这里可以看到创建了PerfectTime对象。

(3) 至少将一个远程对象注册到RMI远程对象注册表中。一个远程对象的方法可以产生对另一个远程对象的引用。因此,客户端只需要访问注册表一次,就可以获取第一个远程对象。

(1) 注册表设置

在此处,可以看到对静态方法Naming.bind()的调用。但是,此调用需要注册表作为一个独立进程在计算机上运行。注册表服务器名为rmiregistry。在32位Windows系统中,可以使用以下命令使其在后台运行:

start rmiregistry port_number

start rmiregistry


В Unix-системах можно использовать следующую команду:

rmiregistry &


Как и многие сетевые программы, `rmiregistry` располагается на определённом IP-адресе машины, где он был запущен, но также должен прослушивать определённый порт. Если `rmiregistry` вызывается без параметров, как указано выше, его порт по умолчанию будет 1099. Чтобы указать другой порт, просто добавьте соответствующий параметр командной строки с номером этого порта. В этом примере порт будет 2005, поэтому `rmiregistry` следует запустить следующим образом (для 32-битной Windows):

Для Unix используйте следующую команду:

```sh
rmiregistry 2005 &

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

(1) localhost не работает с RMI. Поэтому для тестирования RMI на отдельной машине необходимо указывать имя машины. Для получения имени своей машины в 32-битной среде Windows можно зайти в панель управления, выбрать "Сеть" и затем "Идентификатор". Здесь будет указано имя компьютера. У меня, например, имя моей машины — Colossus (потому что я использую несколько больших жёстких дисков для различных систем разработки — Colossus означает "гигант").


Примечание ⑦: Чтобы получить эту информацию, мне пришлось потратить множество клеток мозга.

Учитывая эти факторы, команда `bind()` выглядит так:

```java
Naming.bind("//colossus:2005/PerfectTime", pt);

Если использовать стандартный порт 1099, то указание порта не требуется, поэтому можно использовать:

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

В будущих версиях JDK (после 1.1), после исправления проблемы с localhost, можно будет выполнять локальные тесты, не указывая IP адрес, а используя только идентификатор:

Naming.bind("PerfectTime", pt);
```Название сервиса произвольно; здесь оно равно `PerfectTime`, но совпадает с названием класса. Вы можете изменять его по необходимости. Самое важное  обеспечить уникальность этого имени в регистре, чтобы клиент мог корректно получать удалённый объект. Если такое имя уже существует в регистре, будет выброшено исключение `AlreadyBoundException`. Чтобы избежать этой ошибки, можно использовать метод `rebind()`, вместо `bind()`. Это потому что `rebind()` либо создаст новый элемент, либо заменит существующий элемент с таким же именем. Хотя `main()` завершает работу, наши объекты уже созданы и зарегистрированы, поэтому они будут поддерживаться активными реестром регистрации до тех пор, пока клиенты не обратятся к ним с запросами. Пока `rmiregistry` работает и мы не вызвали метод `Naming.unbind()` для имени, объект будет находиться в этом месте. По этой причине при создании нашего кода нам следует закрывать `rmiregistry`, а затем снова запускать его при компиляции новой версии удалённого объекта. Не обязательно запускать `rmiregistry` как внешний процесс. Если известно, что это будет единственным приложением, использующим регистратор, можно запустить его внутри программы с помощью следующего кода:```java
LocateRegistry.createRegistry(2005);

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

15.8.3 Создание корня и кости

Если скомпилировать и запустить PerfectTime.java, даже если rmiregistry работает правильно, программа может не работать должным образом. Это связано с тем, что фреймворк RMI ещё не настроен. В первую очередь необходимо создать корень и кость, чтобы обеспечить сетевые операции и сделать возможным использование удалённого объекта как локального объекта на своей машине.Все эти действия происходят за кадром и довольно сложны. Любые объекты, передаваемые между удаленными объектами, должны реализовывать Serializable. Если требуется передача удаленной ссылки вместо всего объекта, то параметры объекта могут реализовывать Remote. Поэтому можно представить себе автоматическую сериализацию и сборку данных при "сборке" всех параметров через сеть. К счастью, нам не нужно знать детали этих процессов, но создание корневого объекта и его костей является обязательным условием. Простейший способ создания этих компонентов заключается в вызове rmic над скомпилированным кодом, который создаст необходимые файлы. Таким образом, единственное, что нужно сделать, это добавить новый шаг в процесс компиляции.Однако инструмент rmic имеет прямую связь с конкретными пакетами и путями классов. Файл PerfectTime.java находится в пакете c15.Ptime, поэтому даже если мы вызываем rmic из того же каталога, где расположен PerfectTime.class, rmic не сможет найти файлы. Это происходит потому, что он ищет пути классов. Поэтому нам нужно указать пути классов вместе с вызовом rmic, как показано ниже:

rmic c15.Ptime.PerfectTime

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

PerfectTime_Stub.class
PerfectTime_Skel.class

Эти классы соответственно представляют корень (stub) и скелет (skeleton). Теперь сервер и клиент готовы общаться друг с другом.

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

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

//: DisplayPerfectTime.java
// Использует удаленный объект PerfectTime
package c15.ptime;
import java.rmi.*;
import java.rmi.registry.*;
``````java
public class DisplayPerfectTime {
  public static void main(String[] args) {
    System.setSecurityManager(new RMISecurityManager());
    try {
      PerfectTimeI t = (PerfectTimeI) Naming.lookup("//colossus:2005/PerfectTime");
      for (int i = 0; i < 10; i++) {
        System.out.println("Перфектное время = " + t.getPerfectTime());
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}
///:~

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

Результат вызова Naming.lookup() должен быть преобразован в удалённый интерфейс, а не в конкретный класс. В противном случае будет выброшено исключение.

В следующем методе:

t.getPerfectTime()

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

15.8.5 Альтернативы RMIRMI представляет собой один из способов создания специальных объектов, которые могут быть опубликованы через сеть. Главное преимущество RMI — это предоставление "чисто Java" решения, но если уже существует множество кода, написанного на других языках, то RMI может оказаться недостаточно гибким. На данный момент две наиболее конкурентоспособные альтернативы — это Microsoft DCOM (в соответствии с планами Microsoft, он будет доступен на платформах кроме Windows) и CORBA. Поддержка CORBA была введена в Java начиная с версии 1.1 и представляет собой новый подход к созданию многоплатформенных приложений. Полное описание распределённых объектов в Java содержится в книге Orfali и Harkey "Клиент/Сервер программирование с Java и CORBA", опубликованной John Wiley & Sons в 1997 году (книга кажется несколько предвзятой по отношению к CORBA). Более объективное представление CORBA представлено в книге Andreas Vogel и Keith Duddy "Java программирование с CORBA", опубликованной John Wiley & Sons в том же году.

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/wizardforcel-thinking-in-java-zh.git
git@api.gitlife.ru:oschina-mirror/wizardforcel-thinking-in-java-zh.git
oschina-mirror
wizardforcel-thinking-in-java-zh
wizardforcel-thinking-in-java-zh
master