Цель работы: начальное ознакомление с архитектурой брокера объектных запросов CORBA и разработка простого распределенного приложения на основе CORBA средствами языка программирования Java.
CORBA (Common Object Request Broker Architecture) - многофункциональная, гибкая и достаточно сложная система, позволяющая создавать и выполнять масштабируемые распределенные приложения с использованием различных языков программирования. Ниже дается самое начальное описание базовых средств CORBA.
Взаимодействие распределенных объектов имеет две стороны - клиента и сервера. Сервер обеспечивает удаленный интерфейс, а клиент вызывает этот интерфейс. При этом любой объект может быть клиентом, использующим удаленный объект как сервер, и одновременно выступать в роли сервера для третьего объекта.
Процесс разработки распределенного приложения на языке Java с использованием технологии CORBA включает в себя следующие шаги.
IDL - декларативный язык для формального описания интерфейсов взаимодейстия клиентов и серверов в распределенных приложениях. IDL не привязан к какому-либо языку программирования, но для большинства современных языков программирования существуют спецификации, определяющие правила трансляции конструкций IDL в конструкции этих языков.
Ниже приведен пример описания интерфейса для приложения Hello (в файле Hello.idl каталога Hello).
module HelloApp { interface Hello { string sayHello(); oneway void shutdown(); }; };
Конструкция module определяет пространство имен в котором будут существовать включенные в нее интерфейсы. Эта конструкция по своему смыслу близка понятию "пакет" (package) языка Java.
Конструкция interface определяет программный интерфейс, с помощью которого объекты общаются друг с другом. В этом интерфейсы языков IDL и Java аналогичны.
В теле конструкции interface объявляются операции, которые должен быть способен выполнять сервер по запросу клиента. Операции языка IDL соответствуют методам языка Java.
Для компиляции файла с IDL-описанием используется следующая команда:
idlj -fall Hello.idl
В результате создается подкаталог с именем HelloApp (имя из конструкции module) в каталоге Hello, содержащий 6 файлов: Hello.java, HelloPOA.java, _HelloStub.java, HelloOperations.java, HelloHelper.java, HelloHolder.java.
Серверная часть приложения состоит из двух классов: собственно сервера и серванта (servant). Сервант (назовем его HelloImpl) реализует IDL-интерфейс Hello, при этом каждая сущность IDL-интерфейса Hello реализуется сущностью класса HelloImpl. Сервант является подклассом класса HelloPOA, описание которого сгенерировано компилятором idlj.
Сервант содержит по одному методу для каждой IDL-операции (в нашем примере это методы sayHello() и shutdown()). Методы серванта являются "обычными" методами Java, все вспомогательные операции (общение с ORB, приведение аргументов и результатов и т.п.) выполняются каркасом.
Класс сервера содержит метод main, который выполняет следующие задачи:
Ниже представлен пример кода (в файле HelloServer.java каталога Hello) серверной части приложения.
// ----- Импорт всех необходимых пакетов ----- // Пакет, сгенерированный компилятором idlj import HelloApp.*; // Пакет, необходимый для использования службы имен import org.omg.CosNaming.*; // Пакет, содержащий специальные исключения, генерируемые службой имен import org.omg.CosNaming.NamingContextPackage.*; // Пакет, содержащий классы, необходимые любому приложению CORBA import org.omg.CORBA.*; // Пакеты, содержащие классы, реализующие модель наследования переносимых серверов import org.omg.PortableServer.*; import org.omg.PortableServer.POA; // Пакет, содержащий классы, необходимые для инициализации ORB import java.util.Properties; // ----- Реализация класса-серванта ----- // Сервант HelloImpl является подклассом класса HelloPOA и наследует // всю функциональность CORBA, сгенерированную для него компилятором class HelloImpl extends HelloPOA { private ORB orb; // Необходима для хранения текущего ORB (используется в методе shutdown) public void setORB(ORB orb_val) { orb = orb_val; } public String sayHello() { return "\nHello, world !!\n"; } public void shutdown() { // Использует метод org.omg.CORBA.ORB.shutdown(boolean) для завершения ORB, //false означает, что завершение должно быть немедленным orb.shutdown(false); } } // ----- Реализация класса-сервера ----- public class HelloServer { // Сервер создает один или несколько объектов-серванов. // Сервант наследует интерфейсу, сгенерированному компилятором idlj, // и реально выполняет все операции этого интерфейса. public static void main(String args[]) { try{ // Создаем и инициализируем экземпляр ORB ORB orb = ORB.init(args, null); // Получаем доступ к Root POA и активируем POAManager POA rootpoa = POAHelper.narrow(orb.resolve_initial_references("RootPOA")); rootpoa.the_POAManager().activate(); // Создаем объект сервант и регистрируем в нем объект ORB HelloImpl helloImpl = new HelloImpl(); helloImpl.setORB(orb); // Получаем доступ к объекту серванта org.omg.CORBA.Object ref = rootpoa.servant_to_reference(helloImpl); Hello href = HelloHelper.narrow(ref); // Получаем корневой контекст именования org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService"); // NamingContextExt является частью спецификации Interoperable // Naming Service (INS) NamingContextExt ncRef = NamingContextExtHelper.narrow(objRef); // Связывание идентификатора "Hello" и объекта серванта String name = "Hello"; NameComponent path[] = ncRef.to_name( name ); ncRef.rebind(path, href); System.out.println("HelloServer готов и ждет обращений ..."); // Ожидание обращений клиентов orb.run(); } catch (Exception e) { System.err.println("ОШИБКА: " + e); // Выводим сообщение об ошибке e.printStackTrace(System.out); // Выводим содержимое стека вызовов }; System.out.println("HelloServer работу завершил ..."); } }
Далее следуют пояснения отдельных строк кода.
ORB orb = ORB.init(args, null);
Любому серверу CORBA необходим локальный объект ORB.
Каждый сервер создает экземпляр ORB и регистрирует в нем свои объекты-серванты,
дабы ORB мог найти объекты при получении соответствующих запросов.
Передача аргументов args метода main методу ORB.init позволяет задавать
некоторые свойства ORB в командной строке его вызова на выполнение.
rootpoa.the_POAManager().activate();
Операция activate делает состояние POAManager'а активным, заставляя ассоциированные
с ним POA начать обработку запросов. Каждый объект POA ассоциирован с одним объектом
POAManager, но объект POAManager может быть ассоциирован с несколькими объектами POA.
org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService");
Для обеспечения доступа клиентов к операциям серванта сервер использует службу имен под
названием Common Object Services (COS). Серверу необходим доступ к службе имен для того,
чтобы он мог опубликовать ссылки на объекты, реализующие различные интерфейсы.
В настоящее время реализовано 2 типа службы имен: tnameserv (временная) и
orbd (временная и постоянная). Наш пример использует orbd. Аргумент в виде
"NameService" понимается службами имен обоих типов, при этом для orbd
он означает постоянный режим, а для tnameserv - временный. Для приведения orbd
во временный режим необходимо использовать "TNameService".
NamingContextExt ncRef = NamingContextExtHelper.narrow(objRef);
objRef - "первородный" (исходный) объект CORBA. Для его использования
в качестве объекта NamingContextExt необходимо "преобразование типа",
выполняемое методом narrow() вспомогательного класса NamingContextExtHelper,
сгенерированного компилятором idlj. После этого объект ncRef применяется для доступа
к службе имен и регистрации в ней сервера.
Для компиляции сервера HelloServer.java выполнить следующую последовательность действий:
Ниже представлен пример кода (в файле HelloClient.java каталога Hello) клиентской части приложения.
import HelloApp.*; import org.omg.CosNaming.*; import org.omg.CosNaming.NamingContextPackage.*; import org.omg.CORBA.*; public class HelloClient { static Hello helloImpl; public static void main(String args[]) { try{ // Создание и инициализация ORB ORB orb = ORB.init(args, null); // Получение корневого контекста именования org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService"); // NamingContextExt является частью спецификации Interoperable // Naming Service (INS) NamingContextExt ncRef = NamingContextExtHelper.narrow(objRef); // Получение доступа к серверу по его имени String name = "Hello"; helloImpl = HelloHelper.narrow(ncRef.resolve_str(name)); System.out.println("Получен доступ к объекту " + helloImpl); System.out.println(helloImpl.sayHello()); helloImpl.shutdown(); } catch (Exception e) { System.out.println("ОШИБКА : " + e) ; e.printStackTrace(System.out); } } }
Для компиляции клиента HelloClient.java выполнить следующую последовательность действий:
Запустить на выполнение службу имен orbd командой
orbd -ORBInitialPort 1050 -ORBInitialHost localhost &
Запустить на выполнение сервер командой
java HelloServer -ORBInitialPort 1050 -ORBInitialHost localhost &
Выполнить клиента командой
java HelloClient -ORBInitialPort 1050 -ORBInitialHost localhost
Базовое задание - разработать средствами языка программирования Java распределенное приложение, имитирующее работу соты мобильной телефонной связи при передаче коротких текстовых сообщений (SMS). Сота состоит из одной базовой станции и произвольного количества мобильных телефонных трубок и реализует две операции:
Имитатор базовой станции естественно реализовать в виде сервера. С имитаторами трубок ситуация сложнее: при регистрации в базовой станции и отсылке сообщений через станцию они играют роль клиентов, а при принятии сообщений от станции - роль серверов. Ниже приводится пример реализации таких имитаторов средствами CORBA.
IDL-файл Cell.idl описания соты выглядит следующим образом.
/* Модуль для приложения "Сота" */ module Cell { /* Интерфейс обратного вызова трубки */ interface TubeCallback { /* Принять сообщение message от номера fromNum */ long sendSMS (in string fromNum, in string message); /* Вернуть свой номер */ string getNum(); }; /* Интерфейс базовой станции */ interface Station { /* Зарегистрировать трубку с номером phoneNum, */ /* для обратного вызова трубки использовать ссылку TubeCallback */ long register (in TubeCallback objRef, in string phoneNum); /* Отправить сообщение message от номера fromNum к номеру toNum */ long sendSMS (in string fromNum, in string toNum, in string message); }; };
Для трансляции IDL-файла во вспомогательные файлы java используется
команда
idlj -fall Cell.idl
Примечание. Ключевое слово -fall означает создание в каталоге Cell как
серверных, так и клиентских java-файлов. Это ключевое слово может быть
заменено на -fserver или -fclient.
Компиляции сгенерированных java-файлов выполняется командой
javac Cell/*.java
Файл StationServer.java, содержащий код серванта объекта "Базовая
станция" и сервера, может выглядить следующим образом.
import Cell.*; import org.omg.CosNaming.*; import org.omg.CosNaming.NamingContextPackage.*; import org.omg.CORBA.*; import org.omg.PortableServer.*; import org.omg.PortableServer.POA; import java.util.Properties; // Класс, реализующий IDL-интерфейс базовой станции class StationServant extends StationPOA { // Вместо представленных ниже двух переменных здесь // должен быть список пар "номер - объектная ссылка" TubeCallback tubeRef; String tubeNum; // Метод регистрации трубки в базовой станции public int register (TubeCallback objRef, String phoneNum) { tubeRef = objRef; tubeNum = phoneNum; System.out.println("Станция: зарегистрирована трубка "+tubeNum); return (1); }; // Метод пересылки сообщения от трубки к трубке public int sendSMS (String fromNum, String toNum, String message) { System.out.println("Станция: трубка "+fromNum+" посылает сообщение "+toNum); // Здесь должен быть поиск объектной ссылки по номеру toNum tubeRef.sendSMS(fromNum, message); return (1); }; }; // Класс, реализующий сервер базовой станции public class StationServer { public static void main(String args[]) { try{ // Создание и инициализация ORB ORB orb = ORB.init(args, null); // Получение ссылки и активирование POAManager POA rootpoa = POAHelper.narrow(orb.resolve_initial_references("RootPOA")); rootpoa.the_POAManager().activate(); // Создание серванта для CORBA-объекта "базовая станция" StationServant servant = new StationServant(); // Получение объектной ссылки на сервант org.omg.CORBA.Object ref = rootpoa.servant_to_reference(servant); Station sref = StationHelper.narrow(ref); org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService"); NamingContextExt ncRef = NamingContextExtHelper.narrow(objRef); // Связывание объектной ссылки с именем String name = "BaseStation"; NameComponent path[] = ncRef.to_name( name ); ncRef.rebind(path, sref); System.out.println("Сервер готов и ждет ..."); // Ожидание обращений от клиентов (трубок) orb.run(); } catch (Exception e) { System.err.println("Ошибка: " + e); e.printStackTrace(System.out); }; }; };Компиляции данного файла выполняется командой
Файл Tube.java, содержащий код имитатора телефонной трубки,
может выглядить следующим образом.
import Cell.*; import org.omg.CosNaming.*; import org.omg.CORBA.*; import org.omg.PortableServer.*; import org.omg.PortableServer.POA; import java.io.*; // Класс вызова телефонной трубки class TubeCallbackServant extends TubeCallbackPOA { String myNum; // Номер трубки // Конструктор класса TubeCallbackServant (String num) { myNum = num; }; // Метод обработки принятого сообщения public int sendSMS(String fromNum, String message) { System.out.println(myNum+": принято сообщение от "+fromNum+": "+message); return (0); }; // Метод, возвращающий номер трубки public String getNum() { return (myNum); }; }; // Класс, используемый для создания потока управления class ORBThread extends Thread { ORB myOrb; // Конструктор класса ORBThread(ORB orb) { myOrb = orb; }; // Метод запуска потока public void run() { myOrb.run(); }; }; // Класс, имитирующий телефонную трубку public class Tube { public static void main(String args[]) { try { String myNum = "1234"; // Номер трубки // Создание и инициализация ORB ORB orb = ORB.init(args, null); //Создание серванта для IDL-интерфейса TubeCallback POA rootPOA = POAHelper.narrow(orb.resolve_initial_references("RootPOA")); rootPOA.the_POAManager().activate(); TubeCallbackServant listener = new TubeCallbackServant(myNum); rootPOA.activate_object(listener); // Получение ссылки на сервант TubeCallback ref = TubeCallbackHelper.narrow(rootPOA.servant_to_reference(listener)); // Получение контекста именования org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService"); NamingContext ncRef = NamingContextHelper.narrow(objRef); // Преобразование имени базовой станции в объектную ссылку NameComponent nc = new NameComponent("BaseStation", ""); NameComponent path[] = {nc}; Station stationRef = StationHelper.narrow(ncRef.resolve(path)); // Регистрация трубки в базовой станции stationRef .register(ref, myNum); System.out.println("Трубка зарегистрирована базовой станцией"); // Запуск ORB в отдельном потоке управления // для прослушивания вызовов трубки ORBThread orbThr = new ORBThread(orb); orbThr.start(); // Бесконечный цикл чтения строк с клавиатуры и отсылки их // базовой станции BufferedReader inpt = new BufferedReader(new InputStreamReader(System.in)); String msg; while (true) { msg = inpt.readLine(); stationRef .sendSMS(myNum, "7890", msg); // Обратите внимание: номер получателя 7890 в описанной ранее // реализации базовой станции роли не играет } } catch (Exception e) { e.printStackTrace(); }; }; };Компиляции данного файла выполняется командой
Запуск разработанного приложения на выполнение осуществляется следующей
последовательностью команд.
orbd -ORBInitialPort 1050 -ORBInitialHost localhost & java StationServer -ORBInitialPort 1050 -ORBInitialHost localhost & java Tube -ORBInitialPort 1050 -ORBInitialHost localhost