Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

RMI

.pdf
Скачиваний:
32
Добавлен:
30.05.2015
Размер:
1.14 Mб
Скачать

4.Хост-сервер

Удаленные службы RMI должны быть помещены в процесс сервера. Класс CalculatorServer является очень простым сервером, предоставляющим простые элементы для размещения.

import java.rmi.Naming;

public class CalculatorServer {

public CalculatorServer() { try {

Calculator c = new CalculatorImpl(); Naming.rebind(" rmi://localhost:1099/

CalculatorService", c);

}catch (Exception e) { System.out.println("Trouble: " + e);

}

}

public static void main(String args[]) { new CalculatorServer();

}

}

5.Клиент

Исходный код клиента следующий:

import java.rmi.Naming;

import java.rmi.RemoteException; import java.net.MalformedURLException; import java.rmi.NotBoundException;

public class CalculatorClient {

public static void main(String[] args) { try {

Calculator c = (Calculator) Naming.lookup(

"rmi://remotehost

/CalculatorService"); System.out.println( c.sub(4, 3) ); System.out.println( c.add(4, 5) ); System.out.println( c.mul(3, 6) ); System.out.println( c.div(9, 3) );

}

catch (MalformedURLException murle) { System.out.println(); System.out.println(

"MalformedURLException");

System.out.println(murle);

}

catch (RemoteException re) { System.out.println(); System.out.println(

"RemoteException");

System.out.println(re);

}

catch (NotBoundException nbe) { System.out.println(); System.out.println(

"NotBoundException");

System.out.println(nbe);

}

catch ( java.lang.ArithmeticException

ae) {

System.out.println();

System.out.println(

"java.lang.ArithmeticException");

System.out.println(ae);

}

}

}

6.Запуск RMI-системы

Теперь вы готовы к запуску системы! Вы должны запустить три консоли, одну для сервера, одну для клиента и одну для реестра RMI.

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

rmiregistry

Если все пройдет хорошо, реестр начнет работать, и вы можете перейти к следующей консоли.

Во второй консоли запустите сервер, содержащий CalculatorService, и наберите следующее:

>java CalculatorServer

Программа запустится, загрузит реализацию в память и будет ждать соединения клиента.

В последней консоли запустите клиентскую программу.

>java CalculatorClient

Если все пройдет хорошо, вы увидите следующую информацию:

1

9

18

3

Вот и все: вы создали работающую систему RMI. Даже если вы запустили три консоли на одном и том же компьютере, RMI использует стек протоколов TCP/IP вашей сети для взаимодействия между тремя отдельными JVM. Это вполне законченная RMI-система.

Упражнения

1.UML-определение примера системы RMI

2.Простая банковская система

Параметры в RMI

RMI поддерживает вызовы методов удаленных объектов. Если эти вызовы содержат параметры или принимают возвращаемое значение, как RMI передает их между JVM? Какая используется семантика? Поддерживает ли RMI передачу по значению или передачу по ссылке? Ответ зависит от того, являются ли параметры переменными простого типа, объектами или удаленными объектами.

Параметры в одной JVM

Сначала рассмотрим, как передаются параметры в одной JVM. Нормальной семантикой для Java является передача по значению. Когда параметр передается в метод, JVM создает копию значения, помещает копию в стек и затем выполняет метод. Когда этот параметр используется внутри метода, происходит обращение к стеку и используется копия параметра. Значения, возвращаемые из методов, тоже передаются по значению.

Когда в метод передаются данные простого типа (boolean, byte, short, int, long, char, float, или double), используется передача по значению. Механизм передачи в качестве параметров объектов является более сложным. Вспомните, что объекты расположены в памяти, и к ним можно получить доступ при помощи одной или нескольких переменных-ссылок. И хотя в следующем фрагменте программы кажется, что в метод println()передается объект

String s = "Test";

System.out.println(s);

на самом деле в метод передается переменная-ссылка. В примере делается копия переменной-ссылки s (увеличивая на единицу счетчик ссылок объекта String) и помещается в стек. Внутри метода код использует копию ссылки для доступа к объекту.

Теперь давайте посмотрим, как RMI передает параметры и возвращаемые значения между удаленными

JVM.

Простые параметры

Когда в качестве параметра в удаленный метод передается простой тип данных, RMI-система передает их по значению. RMI делает копию простого типа данных и передает ее в удаленный метод. Если метод возвращает простой тип данных, также используется передача по значению.

Значения передаются между JVM в стандартном, машинно-независимом формате. Это позволяет JVM, работающим на разных платформах, надежно взаимодействовать друг с другом.

Объектные параметры

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

В отличие от простых типов данных, передача объекта в удаленную JVM – не простая задача. Объект Java может быть простым и самодостаточным, но может содержать сложную, похожую на граф, структуру ссылок на другие объекты Java. Поскольку различные JVM не разделяют динамическую память, RMI должен послать указанный объект и все объекты, на которые он ссылается. (Передача большой структуры объектов может потребовать много времени процессора и пропускной способности сети.)

RMI использует технологию, называемую сериализацией объектов, для преобразования объекта в линейный формат, который затем может быть передан по сети. Сериализация объекта, по существу, выравнивает объект и все объекты, на которые он ссылается. Сериализованные объекты могут быть десериализованы в памяти удаленной JVM и подготовлены к использованию программой Java.

Удаленные объектные параметры

RMI представляет третий тип параметров: удаленные объекты. Как вы уже видели, удаленная программа может получить ссылку на удаленный объект через реестр RMI. Существует другой способ передачи клиенту ссылки на удаленный интерфейс – она может быть возвращена клиенту из вызова метода. В следующем коде для получения ссылки на удаленную службу Account используется метод getAccount()службы BankManager.

BankManager bm; Account a; try {

bm = (BankManager) Naming.lookup( "rmi://BankServer

/BankManagerService"

);

a = bm.getAccount( "jGuru" ); // Код, использующий счет

}

catch (RemoteException re) {

}

В реализации метода getAccount() метод возвращает локальную ссылку на удаленную службу.

public Account

getAccount(String accountName) {

// Код для поиска соответствующего счета

AccountImpl ai =

// возврат найденной ссылки return AccountImpl;

}

Когда метод возвращает локальную ссылку на экспортированный удаленный объект, RMI не возвращает этот объект. Вместо него он подставляет другой объект (удаленный прокси-объект для данной службы) в поток возвращаемых данных.

На следующей диаграмме показано, как могут использоваться вызовы метода RMI:

Возвратить удаленную ссылку с сервера клиенту А

Передать удаленную ссылку от клиента А клиенту В

Передать удаленную ссылку от клиента В назад на сервер

Обратите внимание, что когда клиенту А возвращается объект AccountImpl, прокси-объект Account замещается. Последовательные вызовы метода продолжают передавать ссылку сначала клиенту В, а затем назад серверу. На протяжении этого процесса ссылки продолжают обращаться к одному экземпляру удаленной службы.

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

Упражнение

3.Параметры RMI

Клиентские обратные вызовы RMI

Во многих архитектурах серверу может понадобиться сделать обратный вызов клиенту. Например, поддержка отображения текущего хода процесса (progress bar), извещение о тике таймера, предупреждения о проблемах и т.д.

Для достижения этого клиент тоже должен работать как сервер RMI. Здесь нет ничего особенного, поскольку RMI работает одинаково хорошо между всеми компьютерами. Однако, расширение клиентом java.rmi.server.UnicastRemoteObject может быть не практичным. В этих случаях удаленный объект может подготовить себя для удаленного использования, вызвав статический метод

UnicastRemoteObject.exportObject (<remote_object>).

Упражнение

4.Обратные вызовы RMI-клиента

Распространение и установка программного обеспечения RMI

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

Для целей этого раздела предполагается, что общий процесс разработки DC-системы привел вас к тому моменту, когда вы должны подумать о размещении обработки на узлах сети, и определить, как установить систему на каждом сетевом узле.

Распространение классов RMI

Для запуска RMI-приложения файлы поддерживающих классов должны быть расположены в таких местах, где бы они могли быть найдены сервером и клиентами.

Для сервера должны быть доступны (для загрузчика классов) классы:

Определения интерфейса удаленной службы

Реализации удаленных служб

Скелеты для классов реализации (только для серверов, основанных на JDK 1.1)

Заглушки для классов реализации

Все остальные классы сервера

Для клиента должны быть доступны (для загрузчика классов) классы:

Определения интерфейса удаленной службы

Заглушки для классов, реализующих удаленную службу

Классы сервера для объектов, используемых клиентом (таких, как возвращаемое значение)

Все остальные классы клиента

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

Автоматическое распространение классов

Разработчики RMI расширили концепцию загрузки классов, включив загрузку классов из серверов HTTP и FTP. Это является мощным расширением, поскольку означает, что классы могут быть размещены в одном или в небольшом количестве мест, но в то же время все узлы в RMI-системе смогут получить нужный для функционирования класс.

RMI поддерживает удаленную загрузку классов при помощи RMIClassLoader. Если на клиенте или сервере выполняется RMI-система и для дальнейшей работы необходимо загрузить класс из удаленного места, вызывается метод RMIClassLoader.

Способ загрузки классов управляется множеством свойств. Эти свойства могут быть установлены при запуске JVM:

java [ -D<PropertyName>=<PropertyValue> ]+<ClassFile>

Свойство java.rmi.server.codebase используется для задания URL. Этот URL указывает на месторасположение file:, ftp: или http:, обеспечивающих классы для объектов, переданных из этой JVM. Если программа, выполняющаяся в JVM, посылает объект другой JVM (в качестве возвращаемого из метода значения), эта другая JVM должна загрузить файл классов для этого объекта. Когда RMI передает объект при помощи сериализации, указанный в этом параметре URL встраивается в поток вместе с самим объектом.

Примечание: RMI не передает файлы классов вместе с сериализованными объектами.

Если удаленная JVM нуждается в файле классов для объекта, она ищет встроенный URL и связывается с сервером по этому URL для загрузки файла.

Если свойство java.rmi.server.useCodebaseOnly установлено в значение true, JVM будет загружать классы либо из места, указанного в переменной окружения CLASSPATH, либо из URL, указанного в этом свойстве.

Используя различные комбинации доступных системных свойств, может быть создано большое количество различных системных конфигураций RMI.

Closed. Все классы, используемые клиентами и сервером, должны быть расположены в JVM и ссылка на это место должна быть указана в переменной окружения CLASSPATH. Динамическая загрузка классов не поддерживается.

Server based. Клиентский апплет загружается из CODEBASE сервера вместе со всеми поддерживающими классами. Это похоже на способ загрузки апплетов с того же самого HTTP-сервера, который поддерживает веб-страницу этих апплетов.

Client Dynamic. Основные классы загружаются из места, которое указано в переменной окружения CLASSPATH JVM клиента. Классы поддержки загружаются при помощи java.rmi.server.RMIClassLoader из HTTP или FTP-сервера по сети из места, указанного сервером.

Server Dynamic. Основные классы загружаются из места, которое указано в переменной окружения CLASSPATH JVM клиента. Классы поддержки загружаются при помощи java.rmi.server.RMIClassLoader из HTTP или FTP-сервера по сети из места, указанного клиентом.

Bootstrap client. В этой конфигурации весь код клиента загружается из HTTP или FTP-сервера по сети. Единственный код, расположенный на клиентской машине, - это небольшой начальный загрузчик.

Bootstrap server. В этой конфигурации весь код сервера загружается из HTTP или FTP-сервера по сети. Единственный код, расположенный на серверной машине, - это небольшой начальный загрузчик.

Упражнение этого раздела включает создание конфигурации bootstrap client. Внимательно следуйте указаниям, так как необходимо разместить и откомпилировать разные файлы в разных каталогах.

Упражнение

5.Пример начальной загрузки

Брандмауэр

Любое сетевое корпоративное приложение, которое должно работать вне границ интранет, неизбежно столкнется с брандмауэрами. В общем случае, брандмауэры блокируют весь сетевой трафик, за исключением трафика, проходящего через определенные «хорошо известные» порты.

Поскольку транспортный уровень RMI открывает динамические соединения сокетов между клиентом и сервером, трафик JRMP обычно блокируется большинством брандмауэров. К счастью, разработчики RMI предвидели эту проблему. Ее решение обеспечивается самим транспортным уровнем RMI. Для прохождения через брандмауэры RMI использует HTTP-туннелирование, помещая вызовы RMI в запрос HTTP POST.

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

В этом сценарии транспортный уровень при попытке установить соединение с сервером блокируется брандмауэром. Если это происходит, транспортный уровень RMI автоматически повторяет попытку, помещая данные вызова JRMP в запрос HTTP POST. Заголовок HTTP POST для вызова выглядит так:

http://hostname:port

Если клиент находится за брандмауэром, важно также установить в соответствующее значение системное свойство http.proxyHost. Поскольку большинство брандмауэров распознают HTTP-протокол, указанный прокси-сервер должен быть способен направлять запрос прямо в порт, который прослушивает удаленный сервер. Как только заключенные в HTTP данные JRMP принимаются сервером, они автоматически декодируются и отправляются по назначению транспортным уровнем RMI. Ответ также передается клиенту в виде заключенных в HTTP данных.

На следующей диаграмме показан сценарий, когда и клиент и сервер находятся за брандмауэром, или когда прокси-сервер клиента может направлять данные только в HTTP-порт, имеющий номер 80.

В этом случае транспортный уровень RMI использует еще один дополнительный обходной уровень. Причиной этого является то, что клиент не может больше передавать заключенные в HTTP JRMP-вызовы в

произвольные порты, поскольку сервер находится за брандмауэром. Вместо этого, транспортный уровень RMI размещает JRMP-вызов внутри HTTP-пакетов и передает эти пакеты в порт 80 сервера. Заголовок HTTP POST имеет теперь следующую форму:

http://hostname:80/cgi-bin/java-rmi?forward=<port>

Это вызывает выполнение сценария CGI, java-rmi.cgi, который в свою очередь вызывает локальную JVM, распаковывает HTTP-пакет и направляет вызов серверному процессу в назначенный порт. RMI JRMPответы от сервера передаются назад как пакеты HTTP REPLY в нужный порт клиента, где RMI опять распаковывает информацию и передает ее в соответствующую заглушку RMI.

Естественно, для того, чтобы все это работало, сценарий java-rmi.cgi, который включен в стандартный JDK 1.1 или в платформу Java 2, должен: быть предварительно сконфигурирован, указывать путь к интерпретатору Java и располагаться в каталоге веб-сервера cgi-bin. Также очень важно при запуске RMIсервера указывать полное доменное имя хоста в системном свойстве для устранения любых проблем разрешения DNS-имен, например:

java.rmi.server.hostname=host.domain.com

Примечание: Вместо использования CGI-сценария для направления вызова более эффективной является реализация той же задачи с использованием сервлета. Вы можете получить исходный код такого сервлета из

RMI FAQ от Sun (http://java.sun.com/products/jdk/1.2/docs/guide/rmi/faq.html).

Необходимо отметить, что, несмотря на встроенный механизм прохождения через брандмауэр, RMI испытывает значительное снижение производительности из-за HTTP-туннелирования. Существуют также и другие трудности при использовании HTTP-туннелирования. Например, ваше RMI-приложение не сможет больше мультиплексировать JRMP-вызовы в одном соединении, поскольку в этом случае применяется дискретный протокол типа запрос-ответ. Кроме того, использование сценария java-rmi.cgi открывает довольно большую дыру в безопасности вашего сервера, так как теперь сценарий может перенаправить любой входящий запрос на любой порт, полностью обходя механизм защиты брандмауэра. Разработчики должны также отметить, что использование HTTP-туннелирования запрещает RMI-приложениям использовать обратные вызовы, что может быть основным ограничением при разработке. Поэтому, если клиент обнаружит брандмауэр, он всегда может запретить имеющуюся по умолчанию возможность HTTPтуннелирования, установив свойство:

java.rmi.server.disableHttp=true

Распределенная сборка мусора

Одним из преимуществ программирования для платформы Java является отсутствие беспокойства о распределении памяти. JVM имеет автоматический сборщик мусора, который освобождает память, занимаемую любым объектом, который больше не используется исполняющейся программой.

Одним из требований к разработке RMI была ее бесшовная интеграция в язык программирования Java, включая и сборку мусора. Разработка эффективного сборщика мусора для одной машины является тяжелой задачей; разработка распределенного сборщика мусора является очень тяжелой задачей.

RMI-система обеспечивает подсчитывающий ссылки алгоритм распределенной сборки мусора, основанный на сетевых объектах, используемых в Modula-3. Эта система при работе следит за тем, какие клиенты запросили доступ к удаленным объектам, выполняющимся на сервере. Когда появляется ссылка, сервер помечает объект как «грязный2, а когда клиент удаляет ссылку, объект помечается как «чистый».

Интерфейс к DGC (распределенный сборщик мусора) скрыт на уровне заглушек и скелетов. Однако удаленный объект может реализовать интерфейс java.rmi.server.Unreferenced и получить уведомление через метод unreferenced, когда нет больше ни одного клиента, содержащего живую ссылку.

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

управляется системным свойством java.rmi.dgc.leaseValue. Его значение указывается в миллисекундах и по умолчанию равно 10 минутам.

Из-за такой семантики сборки мусора, клиент должен быть подготовлен для работы с объектами, которые могут «исчезать».

В следующем примере вы будете иметь возможность поэкспериментировать с распределенным сборщиком мусора.

Упражнение

6.Распределенная сборка мусора

Сериализация удаленных объектов

При разработке системы, использующей RMI, бывают ситуации, когда вы хотели бы гибко управлять тем, где работают удаленные объекты. В настоящее время если удаленный объект начинает работать на определенной JVM, он будет оставаться на этой JVM. Вы не можете «передать» удаленный объект на другую машину для работы на новом месте. RMI усложняет возможность локального или удаленного запуска службы.

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

Существуют два различных пути для решения этой проблемы. Первый заключается в ручной сериализации удаленного объекта и передачи его в другую JVM. Для этого есть две стратегии. Первая стратегия – создать ObjectInputStream и ObjectOutputStream соединения между двумя JVM. При этом вы можете явно поместить удаленный объект в поток. Второй путь – сериализовать объект в массив byte и передать массив byte как возвращаемое значение в вызове RMI-метода. Оба этих способа требуют кодирования на уровне ниже RMI, и это может привести к дополнительному кодированию и сложностям в эксплуатации.

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

-Не реализует java.rmi.Remote

-Реализует java.io.Serializable

Затем вы разрабатываете удаленный интерфейс, объявляющий удаленный доступ к своей функциональности. При создании реализации удаленного интерфейса, вместо повторной реализации функциональности, вы разрешаете обращение к экземпляру локальной версии.

Теперь рассмотрим основные блоки этой модели. Обратите внимание, что это очень простой пример. Реальный пример будет иметь значительное количество локальных полей и методов.

// Разместите функциональность в локальном объекте. public class LocalModel

implements java.io.Serializable

{

public String getVersionNumber()

{

return "Version 1.0";

}

}

Затем вы объявляете интерфейс java.rmi.Remote, определяющий такую же функциональность:

interface RemoteModelRef

extends java.rmi.Remote

{

String getVersionNumber()

throws java.rmi.RemoteException;

}

Реализация удаленной службы принимает ссылку на LocalModel и делегирует реальную работу этому объекту:

public class RemoteModelImpl extends

java.rmi.server.UnicastRemoteObject implements RemoteModelRef

{

LocalModel lm;

public RemoteModelImpl (LocalModel lm) throws java.rmi.RemoteException

{

super(); this.lm = lm;

}

//Делегируйте в реализацию //локальной модели

public String getVersionNumber() throws java.rmi.RemoteException

{

return lm.getVersionNumber();

}

}

И, наконец, вы определяете удаленную службу, обеспечивающую доступ к клиентам. Это делается при помощи интерфейса java.rmi.Remote и его реализации:

interface RemoteModelMgr extends java.rmi.Remote

{

RemoteModelRef getRemoteModelRef() throws java.rmi.RemoteException;

LocalModel getLocalModel() throws java.rmi.RemoteException;

}

public class RemoteModelMgrImpl extends

java.rmi.server.UnicastRemoteObject implements RemoteModelMgr

{

LocalModel lm; RemoteModelImpl rmImpl;

public RemoteModelMgrImpl()

throws java.rmi.RemoteException

{

super();

}

public RemoteModelRef getRemoteModelRef() throws java.rmi.RemoteException

{

// Инсталляция delgatee if (null == lm)

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]