Спец-ориентированные Java-приложения
2.1 RMI-приложения
Вызов удаленных методов (RMI - Remote Method Invocation) обеспечивает средства коммуникации между Java программами, даже если они выполняются на разных компьютерах, находящихся в противоположных точках земного шара.
Важная особенность RMI заключается в том, что он представляет программируемый интерфейс для работы с сетями в отличие от сокетов TCP. Главное преимущество его в том, что он предлагает вам интерфейс более высокого уровеня, основанный на вызовах методов, так, как если бы удаленный объект обрабатывался локально. RMI более удобен и более естественен, чем интерфейс, основанный на сокетах, но он требует выполнения Java-программ на обоих концах соединения. Сетевое соединение, тем не менеее, достигается использованием все того же TCP/IP протокола.
Рассмотрим основные шаги для построения работающего RMI-приложения:
Следующий ниже пример разработан для клиента и сервера, выполняющихся на одной и той же машине, однако, заложенные здесь принципы можно с успехом применить для программ, выполняющихся на разных машинах и даже с различной архитектурой. Программа клиента ищет имена и номера телефонов в центральной базе, постоянно резидентной на сервере.
Как работает RMI
Вы определяете Java-интерфейс, чтобы описать каждый объект, который будет дистанционно разделяем, и перечисляeте общие методы, которые могут быть вызваны для объекта. Сервер будет использовать RMI-интерфейс и создаст объекты для вызова, специальным образом зарегистрированные и доступные для вызова по URL-основанной схеме, например: rmi://localhost/LookupServer
Клиент, используя эту запись, будет пытаться отыскать объект с данным именем, и получить удаленную ссылку к нему. Затем вызванный метод будет обработан с помощью RMI компилятора и преобразован из пользовательского кода в последовательную форму объекта, который передается пользователю с помощью TCP/IP.
Разработка удаленного объектного кода
RMI поддерживает объекты Java, осуществляющие связь через их методы, независимо от того, где эти объекты размещены. Первый шаг для создания класса, к которому можно обратиться дистанционно, - это определение интерфейса, описывающего методы, которые являются дистанционно-разделяемыми. // Lookup.java import java.rmi.*; public interface Lookup extends Remote { public String findInfo(String info) throws RemoteException; }
Java.rmi.Remote - пустой интерфейс, который указывает, что это удаленный объект, - так объекты класса, выполняющие Поиск(_Lookup_) отмечены удаленными ссылками. Все методы в удаленном интерфейсе должны быть объявлены через исключение типа Java.rmi.RemoteException, которое активизируется всякий раз, когда метод удаленного вызова дает сбои.
Разработка серверного кода
После того как вы определили интерфейс к удаленному объекту, нужно выполнить следующий шаг - разработать код сервера. Сервер осуществляет объектный интерфейс и создает образцы объекта, который будет дистанционно-разделяем.
Для нашего примера это будет выглядеть следующим образом:
// LookupServer.java import java.io.*; import java.util.*; import java.rmi.*; import java.rmi.server.*; public class LookupServer extends UnicastRemoteObject implements Lookup { private Vector save = new Vector(); public LookupServer(String db) throws RemoteException { try { FileReader fr = new FileReader(db); BufferedReader br = new BufferedReader(fr); String s = null; while ((s = br.readLine()) != null) save.addElement(s); fr.close(); } catch (Throwable e) { System.err.println(_exception_); System.exit(1); } } public String findInfo(String info) { if (info == null) return null; info = info.toLowerCase(); int n = save.size(); for (int i = 0; i < n; i++) { String dbs = (String)save.elementAt(i); if (dbs.toLowerCase().indexOf(info) != -1) return dbs; } return null; } public static void main(String args[]) { try { RMISecurityManager security = new RMISecurityManager(); System.setSecurityManager(security); String db = args[0]; LookupServer server = new LookupServer(db); Naming.rebind(_LookupServer_, server); System.err.println(_LookupServer ready..._); } catch (Throwable e) { System.err.println(_exception: _ + e); System.exit(1); } } }
Сервер читает в текстовой базе номера телефонов и имена и сохраняет их внутренне. Метод findInfo ищет затем нужное имя и телефон.
Пример базы данных:
Ivanov, Ivan 295-0083
Petrov, Peter 775-9958
Romanov, Alexander 555-7779
Заметим, что LookupServer является расширением стандартного класса java.rmi.server.UnicastRemoteObject и выполняет Lookup. Один из этих классов обеспечивает некоторые базисные реквизиты, необходимые для удаленных объектов, а другой определяет методы, которые будут вызваны дистанционно.
Установка службы безопасности
Наиболее сложная часть этого кода - то, что происходит в процедуре main(). Первым делом нужно установить защиту. RMI принужден загружать удаленные .class файлы и в этом смысле напоминает какой-нибудь web-браузер с его операциями по загрузке апплетов, что само по себе всегда небезопасно. Если вы не установили защиту, то по умолчанию должны загружаться только локальные файлы, и RMI по определению не может работать с такими ограничениями. Так что вы должны установить security manager, чтобы сделать возможной загрузку удаленных .class файлов.
Образец LookupServer затем регистрируется с помощью службы Naming.rebind и становится доступным клиенту по имени.
Вы могли бы задаться вопросом, как удаленный метод фактически становится вызываемым, если сервер не содержит никакого сетевого кода и никаких TCP/IP примитивов? Это происходит за сценой, поскольку сервер и клиент используют так называемые скелетоны и стабы для коммуникации между собой. Соответствующие .class файлы генерируются из серверного .class файла через RMI транслятор, описанный ниже.
Концептуально, класс stub(заглушка) выглядит так:
public class LookupServer_Stub extends java.rmi.server.RemoteStub implements Lookup, java.rmi.Remote { ... }
и скелетон - так: public class LookupServer_Skel implements java.rmi.server.Skeleton { ... }
Использование команды: Javap -c LookupServer_Stub
будет показывать байт-код и иллюстрировать то, что происходит за сценой.
Стаб(stub) -это суррогат для удаленного объекта, и скелетон - некая сущность на сервере, которая обрабатывает удаленные вызовы.
Стаб обеспечивает функции приема передачи на стороне клиента, а скелетон - на стороне сервера. При этом производится преобразование объектов в последовательную форму, а проще говоря, в поток байтов, передаваемых с помощью протокола TCP/IP
Разработка клиентского кода // LookupClient.java import java.rmi.*; import java.rmi.server.*; public class LookupClient { public static void main(String args[]) { try { RMISecurityManager security = new RMISecurityManager(); System.setSecurityManager(security); String host = _localhost_; String server = _LookupServer_; String name = _rmi://_ + host + _/_ + server; Lookup look_obj = (Lookup)Naming.lookup(name); String results = look_obj.findInfo(args[0]); if (results == null) System.err.println(_** not found **_); else System.out.println(results); } catch (Throwable e) { System.err.println(_exception: _ + e); System.exit(1); } } }
Если вы активизируете сервер, выполняя прямой клиентский запрос, то защита для пользователя определяется такая же как и на сервере. URL при этом определяется как: Rmi://localhost/LookupServer
где localhost - имя локального компьютера (IP, адрес = 127.0.0.1), используемого как сервер. Клиент располагается на той же самой машине. Вы можете также использовать и удаленную главную ЭВМ. Когда вызов к нужному методу сделан, результаты незамедлительно передаются клиенту.
Компиляция кода
Три файла - Lookup.java, LookupServer.java, и LookupClient.java компилируются как и обычно в Java: javac Lookup.java javac LookupServer.java javac LookupClient.java
Выполнение RMI компилятора
После того, как вы откомпилируете эти файлы, выполните RMI Compiler (rmic): rmic LookupServer
чтобы получить LookupServer_Skel.class и LookupServer_Stub.class файлы. Вы должны переместить клиентские файлы (Lookup.class, LookupClient.class, и LookupServer_Stub.class) в директорию, откуда вы их желаете выполнять как клиент.
Вы должны переместить серверные файлы (Lookup.class, LookupServer.class, LookupServer_Skel.class, и LookupServer_Stub.class) в директорию, где они станут доступными для публичного доступа.
Регистрация
Объект, к которому обращаются дистанционно, должен быть введен в регистр объектов, т.е. зарегистрирован. В JDK 1.1 имеется специальная программа
Rmiregistry
Rmiregistry может выполняться или в отдельном окне или как фоновый процесс на сервере.
Старт сервера
Вы должны стартовать сервер по команде: java LookupServer database_name
К примеру, если ваша база данных с набором имен и телефонов находится в файле C:\PHONE.TXT, то вы должны дать команду: java LookupServer C:\PHONE.TXT
Старт клиента
Клиентская программа стартуется по команде: java LookupClient Ivanov
_Ivanov_ - это искомое имя в базе.
Таким образом, RMI дает возможность создавать распределенные Java-to-Java прикладные программы, в которых методы удаленных объектов Java вызываются из других Java-программ на различных главных ЭВМ так как, если бы эти методы вызывались локально. Естественно, что подобные возможности можно эффективно использовать при работе с SQL-серверами, которые предоставляют Java-API интерфейс для доступа к данным. Например, в группе продуктов Informix в Informix Client SDK дано описание Informix Object Interface for Java, где приводятся многочисленные примеры, как организовать взаимодействующие RMI-приложения с доступом к базам данных Informix. Более того, имеется и соответствующий RMI-сервер, который содержит массу удобных и полезных методов, которые можно вызывать дистанционно. В сущности, приведенный выше пример можно приспособить для работы с любыми базами данных и SQL-серверами, если вы знаете каким образом устроен Java-API интерфейс для доступа к базам данных. В крайнем случае не возбраняется и использование JDBC в RMI-приложениях, хотя вряд ли это будет в достаточной степени эффективно. В Informix, например, для непосредственного взаимодействия с базами данных существуют два RMI-пакета: informix.api.remote.rmi - для удаленных клиентов и informix.api.remote.rmi.server - для rmi-сервера. При этом в клиентском приложении используется интерфейс к DBMSManager, который накапливает информацию обо всех серверах Informix и базах данных, и вы можете установить либо локальное, либо удаленное соединение с базой через RMI сервер.Для локального соединения создается DirectDBMSManager объект. Для удаленного соединения создается RMIDBMS-Manager объект и передается к соответствующему RMI серверу. Спецификация RMI сервера осуществляется в форме: rmi://hostname[:port]/ //Создание DBMSManager объекта DBMSManager getDBMSManager() throws Exception { // based on RMI Checkbox, get appropriate DBMSManager DBMSManager dbmsManager; if (RMIcheckbox.getState()) dbmsManager = new RMIDBMSManager(rmiServerTextField.getText()); else dbmsManager = new DirectDBMSManager(); return dbmsManager;
Само взаимодействие с базой осуществляется с помощью специальных транзакционных методов и методов управления курсором, которые могут вызываться как локально, так и дистанционно. В различных базах и SQL-серверах это может осуществляться по разному, но если сервер кроссплатформенный - единый и универсальный Java-API интерфейс играет немаловажную роль.