Цель работы: начальное ознакомление с архитектурой брокера объектных запросов 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