Московский Государственный Технический Университет

имени Н.Э.Баумана

Кафедра САПР


Лабораторная работа

Разработка распределенного приложения на базе CORBA средствами языка программирования Java

Федорук В.Г.

Цель работы: начальное ознакомление с архитектурой брокера объектных запросов CORBA и разработка простого распределенного приложения на основе CORBA средствами языка программирования Java.

Теоретическая часть

CORBA (Common Object Request Broker Architecture) - многофункциональная, гибкая и достаточно сложная система, позволяющая создавать и выполнять масштабируемые распределенные приложения с использованием различных языков программирования. Ниже дается самое начальное описание базовых средств CORBA.

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

Процесс разработки распределенного приложения на языке Java с использованием технологии CORBA включает в себя следующие шаги.

Пример простого приложения "клиент-сервер"

Создание описания интерфейса на языке IDL

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);
      };
    };
  };
Компиляции данного файла выполняется командой
javac StationServer.java

Файл 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();
      };


    };

  };
Компиляции данного файла выполняется командой
javac Tube.java

Запуск разработанного приложения на выполнение осуществляется следующей последовательностью команд.

orbd -ORBInitialPort 1050 -ORBInitialHost localhost &
java StationServer -ORBInitialPort 1050 -ORBInitialHost localhost &
java Tube -ORBInitialPort 1050 -ORBInitialHost localhost

Варианты изменений базового задания

  1. Обеспечить передачу мультимедийных сообщений в виде двоичных файлов.
  2. Обеспечить передачу и воспроизведение изображений.
  3. Реализовать передачу коротких текстовых сообщений между трубками в режиме PTT, когда трубки соединяются через базовую станцию, а потом общаются напрямую в режиме рации.
  4. Реализовать на базовой станции учет и выставление счетов клиентам-трубкам.
  5. Обеспечить на базовой станции поддержку СОРМ (ведение журнала переданных сообщений).
  6. Обеспечить взаимодействие двух сот в таком режиме: если базовая станция обнаруживает, что трубка с номером получателя сообщения в ней не зарегистрирована, то она направляет это сообщение соседней базовой станции.
  7. Реализовать взаимодействие произвольного количества сот. При этом для хранения информации о зарегистрированных трубках и их принадлежности сотам использовать единый сервер базы данных.

Порядок выполнения лабораторной работы

  1. Из студентов группы сформировать бригады численностью 3 человека. В каждой бригаде назначить бригадира, в обязанности которого входит программирование имитатора базовой станции и общение с другими бригадирами. Остальные члены бригады создают имитаторы трубок в стиле собственных мобильных телефонов.
  2. Каждая бригада демонстрирует работу разработанного приложения и после этого получает задание на его изменение.
  3. Каждая бригада демонстрирует работу изменного приложения.
  4. Каждый студент оформляет отчет, содержащий тексты разработанных им программ.