Продолжается подписка на наши издания! Вы не забыли подписаться?

Использование callback-вызовов и сервисов событий в CORBA

А.Цимбал

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

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

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

Таким образом, callback-объект (или callback-функция, если речь идет не об объектных технологиях), характеризуется следующим:

Callback-объекты при использовании CORBA

Итак, в простейшем случае (на уровне учебных примеров) использование callback-объектов не составляет особого труда. Вот как это могло бы выглядеть на уровне IDL-интерфейсов:

interface MyCallbackInterface
{
   void MyNotificationMethod (in any info);
};

interface MyInterface
{
   ...
   void registration (in MyCallbackInterface callback_objref);

   void remove_registration (in MyCallbackInterface callback_objref);
};

К сожалению, такой подход в общем случае не будет работать в CORBA. Связано это с тем, что при попытке отмены регистрации callback-объекта серверное приложение должно будет выполнить сравнение на равенство двух объектных ссылок - полученной ранее как аргумент метода registration (и сохраненной тем или иным образом) и полученной как аргумент метода remove_registration(). Но CORBA не предоставляет гарантии того, что операция is_equivalent() вернет значение true тогда и только тогда, когда сравниваемые объектные ссылки относятся к одному и тому же CORBA-объекту. Это означает, что для нормальной работы нужно обеспечить более тесную связь клиентского и серверного приложений, например, так:

interface MyCallbackInterface 
{
   void MyNotificationMethod (in any info);
};

interface RemoveRegistration
{
   void unregister ();
};

interface MyInterface {
   ...
   RemoveRegistration registration (in MyCallbackInterface callback_objref);
};

Таким образом, клиентское приложение должно создать и хранить не только сами callback-объекты, но и объектные ссылки на них, полученные как результат вызовов метода регистрации.

Теперь представьте себе реальный CORBA-проект. При написании серверов приложений вы в общем случае должны:

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

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

Самую существенную помощь мог бы оказать Сервис Сообщений (Messaging Service) CORBA, но его спецификация еще не утверждена OMG, и в настоящий момент еще нет реализаций этого сервиса. Краткий обзор возможностей Messaging Service можно найти в статье о технологии CORBA в номере за второй квартал 2000 г.

Сервис Событий (Event Service) CORBA

Сервис Событий - один из самых «старых» сервисов (его первый вариант был утвержден в 1993 г). Описание ныне действующей спецификации вы можете найти на сайте www.omg.org (документ 97-1-11.pdf). Этот сервис в настоящий момент занимает несколько странное положение.

С одной стороны, он очень прост для понимания и использования. Кроме того, его реализация (правда, неполная) входит в комплект поставки VisiBroker, что, конечно же, очень удобно для тех разработчиков, которые используют этот ORB.

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

Все это приводит к тому, что Event Service не является по-настоящему подходящим инструментом для создания реальных приложений.

Возникает вопрос: зачем в таком случае его рассматривать в данной статье вообще?

Причина состоит в том, что спецификация Event Service является, по сути, подмножеством более общего, мощного и комплексного Сервиса Уведомлений (Notification Service). Существование Event Service связано еще и с тем, что на момент написания статьи спецификация Notification Service не была окончательно утверждена OMG. Кстати, здесь имеет место довольно редкий случай, когда качественная реализация сервиса появляется раньше формального утверждения соответствующей спецификации. Реализация Notification Service уже доступна в рамках проекта OpenFusion фирмы PrismTech.

Здесь мы будем рассматривать основные концепции, которые являются общими и для Event Service, и для Notification Service.

Основные положения

Идея использования сервиса событий состоит в том, чтобы разъединить инициаторов событий («серверов») и их «получателей» («клиентов»). На языке Event Service первые называются Поставщиками Событий (event supplier), вторые - их «Получателями» (consumer). Поставщики и Поолучатели событий «общаются» не непосредственно друг с другом, а с некоторым посредником между ними - так называемым «Каналом Событий» (channel).

Важно понимать следующее:

Канал событий (Event Channel)

Канал событий представляет собой магистраль, которая передает ВСЕ события от ВСЕХ зарегистрированных Поставщиков ВСЕМ зарегистрированным Получателям. Нечего и говорить, что Канал является CORBA-объектом, со всеми вытекающими следствиями.

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

Важнейшей особенностью Канала является то, что он реализует те же интерфейсы, что и Поставщики и Получатели событий. Это означает, что вы можете использовать (в общем случае) столько Каналов, сколько вам необходимо (например, один Канал может быть зарегистрирован в другом Канале как обычный Получатель событий). При использовании нескольких взаимодействующих друг с другом Каналов говорят об их «федерации». Будьте аккуратны при создании федерации Каналов - в общем случае для правильной работы не безразлично, в каком порядке выполняются необходимые действия по регистрации компонентов.

Пример создания канала и обсуждение способов, специфических для VisiBroker, приведены ниже.

Модели взаимодействия Сервиса Событий

Сервис Событий поддерживает две основные модели взаимодействия компонентов - push-модель и pull-модель. Кроме того, вы можете при создании приложений смешивать эти модели произвольным образом.

Под push-моделью подразумевают способ взаимодействия, когда Поставщик событий генерирует событие и отсылает его Каналу, который, в свою очередь, отсылает его зарегистрированным Получателям. Инициатором обработки события является Поставщик. Это наиболее распространенная и простая в реализации модель.

Вы можете также использовать pull-модель, при которой Поставщик, создав некоторое событие, помещает его в буфер событий в ожидании, пока Получатель явно не запросит его у Канала, который, в свою очередь, не обратится за получением события к Поставщику. Инициатором обработки события является Получатель. Этот метод взаимодействия, очевидно, более сложен и используется гораздо реже.

Сервис событий поддерживает произвольное смешение push- и pull-стилей. Например, не составляет труда зарегистрировать часть Поставщиков для использования push-модели, а часть - для pull-модели. Это же касается и Получателей. Доставка события Каналу может выполняться по push-модели, а от канала Потребителю - по pull-модели, и наоборот. Обратите внимание, что в «чистых» push- и pull-моделях Канал никогда не является инициатором обработки события, но это можно легко сделать, используя модель смешанную.

Push- и pull-модели будет рассмотрены отдельно в последующих двух подразделах.

Push-модель

На рис. 1 изображена схема обработки событий согласно правилам Push-модели.

 

Рис. 1 Push-модель

Поставщик_1 создает событие. Код серверного приложения вызывает метод push(), но не для объекта-поставщика, а другого объекта, который расположен между Поставщиком_1 и Каналом и на рисунке не показан (вспомните, что Поставщики, как и Получатели, являются callback-объектами).

Pull-модель

На рис.2 изображена схема обработки событий согласно правилам Pull-модели.

Рис. 2 Pull-модель

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

Основные этапы создания приложения

Мы рассмотрим основные этапы на примере использования push-модели. Причин для этого несколько.

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

Во-вторых, push-модель гораздо более естественна, и разбираться с ней для начинающего CORBA-программиста несколько проще.

Начнем рассмотрение основных шагов.

Шаг 1. Необходимо получить доступ к нужному Каналу Событий (IDL-типом канала является тип CosEventChannelAdmin::EventChannel). Это можно сделать различными способами. Самый универсальный из них - использовать Naming Service (или Trading Service) CORBA, т.е. стандартные средства поиска произвольных CORBA-объектов. Естественно, может возникнуть необходимость выполнения преобразования типов от CORBA::Object к типу «Канал Событий». Как обычно, для этого предусмотрен соответствующий метод narrow().

Шаг 2. Используя Канал как фабрику, необходимо получить административные объекты как для стороны Поставщика, так и для стороны Получателя. Эти объекты имеют типы CosEventChannelAdmin::SupplierAdmin и CosEventChannelAdmin::ConsumerAdmin соответственно. Сами по себе эти объекты не очень интересны, и их назначение - служить фабриками для объектов-«представителей» (proxy). Разумеется, это название не имеет никакого отношения к proxy-объектам CORBA.

Шаг 3. Создание на стороне Поставщика proxy-объекта Получателя (с использованием в качестве фабрики объекта SupplierAdmin), а на стороне Получателя - proxy-объекта Поставщика (для этого в качестве фабрики используется объект ConsumerAdmin). Эти объекты имеют типы CosEventChannelAdmin::ProxyPushConsumer и CosEventChannelAdmin::ProxyPushSupplier соответственно, и именно их методы вызываются кодом серверного и клиентского приложений. С учетом наличия этих объектов, фрагмент рисунка 1 должен был бы выглядеть так:

 

Рис. 3 Компоненты при использовании Push-модели.

Обратите внимание, что при обработке события используются две разных реализации метода push() (точнее, две разных реализации интерфейса CosEventComm::PushConsumer) : одну реализацию - для callback-объекта на стороне клиента - пишете вы сами, вторую - для «обычного» объекта типа ProxyPushConsumer - предоставляет сама реализация Сервиса событий.

Из рисунка видно, что для оповещения Получателей о произошедшем событии на стороне сервера используется объект ProxyPushConsumer - именно его метод push() вызывается из кода серверного приложения. Callback-объект Поставщик_1 для этого совершенно не нужен. Какую же роль он играет в происходящих событиях?

Его задача - обеспечить реакцию на вызов метода ProxyPushConsumer::disconnect_push_consumer() из кода серверного приложения. Название метода подразумевает, что у КОНКРЕТНОГО поставщика есть один ОБОБЩЕННЫЙ потребитель. Этот метод отсоединяет ПОСТАВЩИКА событий от канала. Если вы создали и зарегистрировали callback-объект Поставщик_1, то для него будет вызван метод disconnect_push_supplier(). Это чисто информационное сообщение. Если вы не хотите как-то реагировать на отсоединение Поставщика от Канала, то просто не создавайте и не регистрируйте callback-объект на стороне сервера.

Шаг 4. Регистрация (если нужно) callback-объекта (Поcтавщик_1 на рис. 3) на серверной стороне и обязательно - callback-объектов (Получатель_1 и Получатель_2 на рис. 3) на стороне клиента. Эту регистрацию выполняют соответствующие proxy-объекты. Именно в регистрации callback-объектов состоит главная задача объектов типа ProxyPushSupplier на стороне клиента.

Шаг 5. Вызов метода ProxyPushConsumer::push() на стороне сервера, т.е. инициация обработки события. Вызов этого метода приводит к вызову методов push() callback-объектов на стороне клиента.

Шаг 6. Отсоединение (или, если хотите, отмена регистрации) callback-объектов как на стороне сервера, так и на стороне клиента. Для этого вы вызываете методы ProxyPushConsumer::disconnect_push_consumer() и ProxyPushSupplier::disconnect_push_supplier(), соответственно (хотя, возможно, правильнее было бы поменять названия местами). В ответ на вызов этих методов для callback-объектов происходит вызов, соответственно, методов disconnect_push_supplier() (если был создан и зарегистрирован callback-объект на стороне сервера) и disconnect_push_consumer() (всегда). Почему второй нотификационный метод вызывается всегда? Потому что он и метод push() объявлены в одном IDL-интерфейсе, а так как вы обязаны реализовать push(), попутно вам придется написать код и для disconnect_push_consumer().

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

Теперь имеет смысл подробнее ознакомиться с IDL-интерфейсами Сервиса Событий. Те, которые мы рассматриваем в данной статье, находятся в модулях CosEventChannelAdmin и CosEventComm.

Модуль CosEventComm

module CosEventComm
{
   exception Disconnected{};
   
   // интерфейс callback-объекта на стороне клиента для push-модели
   interface PushConsumer
   {
      void push (in any data) raises(Disconnected);
      void disconnect_push_consumer();
   };
   
   // интерфейс callback-объекта на стороне сервера для push-модели
   interface PushSupplier
   {
      void disconnect_push_supplier();
   };
   
   // интерфейс callback-объекта на стороне сервера для pull-модели
   interface PullSupplier
   {
      any pull () raises(Disconnected);
      any try_pull (out boolean has_event) raises(Disconnected);
      void disconnect_pull_supplier();
   };
   
   // интерфейс callback-объекта на стороне клиента для pull-модели
   interface PullConsumer
   {
      void disconnect_pull_consumer();
   };
};

Так как ранее мы говорили только о push-модели, здесь нужно подробнее рассмотреть два последних интерфейса.

Интерфейс PullConsumer большого интереса не вызывает - его единственный callback-метод вызывается в случае отсоединения конкретного Получателя от обобщенного Поставщика (Канала). Что касается интерфейса callback-объекта на стороне сервера, то единственной его особенностью по сравнению с push-моделью является наличие двух, а не одного метода. Когда из кода клиентского приложения вы вызываете метод ProxyPullSupplier::pull(), то вы блокируете поток, вызвавший этот метод, до получения события. Событие (в виде объекта any) возвращается как результат вызова метода. Метод try_pull(), наоборот, возвращает результат немедленно. Если событие было доступно, то оно возвращается как результат метода, а его выходной параметр has_event получает значение true. Если же событие еще недоступно, то has_event на выходе имеет значение false.

Исключение Disconnected возбуждается в случае, если соединение proxy-объекта с каналом в момент вызова метода было уже разорвано.

Модуль CosEventChannelAdmin

Этот модуль определяет интерфесы, необходимые для создания вспомогательных объектов и proxy-объектов. Интерфейсы для proxy-объектов наследуют интерфейсы для callback-объектов, например:

interface ProxyPushConsumer: CosEventComm::PushConsumer { ... };

Повторимся еще раз: в общем случае присутствуют две реализации интерфейса, например, PushConsumer(). Код одной (для callback-объекта на стороне клиента) вы пишете сами, код второй (для proxy-объекта на стороне сервера) написан поставщиками реализации Сервиса Событий.

#include "CosEventComm.idl"

module CosEventChannelAdmin {
   exception AlreadyConnected {};
   exception TypeError {};
   
   // Интерфейс proxy-объекта на стороне сервера для push-модели. 
   // Задача его единственного метода, не унаследованного от 
   // PushConsumer - зарегистрировать callback-объект на стороне сервера
   // (если нужно). Если в этом нет необходимости, то в качестве значения
   // единственного аргумента метода connect_push_supplier вы можете 
   // передать null
   interface ProxyPushConsumer: CosEventComm::PushConsumer
   {
      void connect_push_supplier(
         in CosEventComm::PushSupplier push_supplier)
         raises(AlreadyConnected);
   };
   
   // Интерфейс proxy-объекта  на стороне клиента для pull-модели
   
   interface ProxyPullSupplier: CosEventComm::PullSupplier
   {
      void connect_pull_consumer(
         in CosEventComm::PullConsumer pull_consumer)
         raises(AlreadyConnected);
   };
   
   // Интерфейс proxy-объекта на стороне сервера для pull-модели.
   // При отсутствии необходимости регистрации callback-объекта на стороне 
   // сервера в качестве аргумента метода connect_pull_supplier() можете 
   // передать пустую ссылку (null)
   interface ProxyPullConsumer: CosEventComm::PullConsumer
   {
      void connect_pull_supplier(
         in CosEventComm::PullSupplier pull_supplier)
         raises(AlreadyConnected,TypeError);
   };
   
   // Интерфейс proxy-объекта  на стороне клиента для push-модели
   interface ProxyPushSupplier: CosEventComm::PushSupplier
   {
      void connect_push_consumer(in CosEventComm::PushConsumer push_consumer)
         raises(AlreadyConnected, TypeError);
   };
   
   // интерфейс вспомогательного объекта-администратора на стороне клиента.
   // Его задача - служить фабрикой для proxy-объектов
   interface ConsumerAdmin
   {
      ProxyPushSupplier obtain_push_supplier();
      ProxyPullSupplier obtain_pull_supplier();
   };
   
   // интерфейс вспомогательного объекта-администратора на стороне сервера.
   // Его задача - служить фабрикой для proxy-объектов
   interface SupplierAdmin
   {
      ProxyPushConsumer obtain_push_consumer();
      ProxyPullConsumer obtain_pull_consumer();
   };
   
   // Интерфейс Канала Событий. Методы служат для создания 
   // объектов-администраторов и для уничтожения ранее созданного канала 
   // (стандартный интерфейс создания канала спецификацией не предусмотрен)
   interface EventChannel
   {
      ConsumerAdmin for_consumers();
      SupplierAdmin for_suppliers();
      void destroy();
   };
};

Понятие о типизированных событиях (Typed Events)

Мы рассмотрели часть интерфейсов Event Service, который используются для передачи событий в виде объектов типа аny. Этот способ имеет как достоинства, так и недостатки. Главным достоинством является то, что клиентские и серверные приложения слабо связаны друг с другом в том смысле, что для посылки и обработки событий не нужно иметь заранее созданные и согласованные IDL-объявления. Очевидным недостатком является трудоемкость анализа полученного сообщения.

Сервис событий в дополнение к рассмотренному способу (или, если это вам удобнее, вместо него) предлагает набор интерфейсов, который позволяют обмениваться информацией, тип которой известен на стадии компиляции программ - другими словами, определенной на уровне IDL.

Суть новых интерфейсов - в предоставлении возможности клиентам и серверам обмениваться объектными ссылками на некий заранее определенный и согласованный интерфейс, а затем в вызове методов этого интерфейса. Рассмотрим IDL-декларации для той же push-модели:

#include "CosEventComm.idl"

module CosTypedEventComm
{
   interface TypedPushConsumer : CosEventComm::PushConsumer
   {
      Object get_typed_consumer();
   };
   ...
};

Для PushSupplier не предусмотрена typed-версия (за ненадобностью).

Как видно, разработчик имеет полную возможность использовать «нетипизированный» способ обмена сообщениями. Если же вы хотите использовать только типизированный способ, то, реализовав каким-либо упрощенным способом метод push() (например, сделав его пустым или возбуждающим исключение NO_IMPLEMENT), вы реализуете для callback-объекта метод get_typed_consumer(). Этот метод должен возвращать объектную ссылку на некоторый интерфейс (например, MyCallbackInterface). В этом случае в коде серверного приложения вы вызываете метод TypedProxyPushConsumer::get_typed_consumer(), затем преобразовываете полученную ссылку типа CORBA::Object к известному вам типу MyCallBackInterface() и просто вызываете его методы.

Мы не будем приводить здесь описание Typed-версий интерфейсов Сервиса Событий - читатель может найти их в спецификации Event Service.

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

Сервис Уведомлений (Notification Service) CORBA

На сайте www.omg.org вы можете найти документ 98-01-01.pdf, который содержит текст submission (т.е. законченного описания структуры и возможностей) Сервиса Уведомлений CORBA. Это очень пространный документ (он содержит 216 страниц), и мы, разумеется, не собираемся приводить здесь подробное описание его интерфейсов. Здесь мы просто перечислим основные расширения возможностей Сервиса Событий, которые предлагается включить в состав Сервиса Уведомлений. Подчеркнем, что разработчики документа (BEA, DSTC, FUJITSU, IBM, INPRISE/Visigenic, IONA, NEC, ORACLE, TIBCO и др.) декларируют необходимость обеспечения совместимости между Event Service и Notification Service.

Итак, основные расширения:

Кроме того, этот документ предлагает формализовать (т.е. рассматривать как стандартные) некоторые события, ориентированные на использование в области телекоммуникаций.

Submission предлагает добавить в стандартный список разрешенных параметров метода resolve_initial_reference() аргумент «NotificationService», который должен возвращать тип, приводимый к типу CosNotifyChannelAdmin::EventChannelFactory.

Пример использования Сервиса Событий

В этой главе подробно рассматриваются C++- и Java-реализации Поставщика (supplier) и Потребителя (consumer) событий при использовании push-модели. Кроме того, здесь же описан нестандартный класс EventLibrary, который входит в поставку VisiBroker и предоставляет методы для создания Канала Событий и Фабрики Канала Событий.

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

Создание Канала событий с помощью утилит VisiBroker

Как уже говорилось, спецификация Event Service не предусматривает наличия стандартной фабрики Каналов событий. VisiBroker содержит утилиту, которая позволит вам создать как Канал событий, так и фабрику каналов.

Перед запуском утилиты необходимо иметь в сети хотя бы один работающий smart agent.

Эту утилита написана на Java, и хотя для ее запуска в «стиле EXE-файла» предусмотрена команда

>events

, удобнее работать с «настоящей» командой Java, которая выглядит так:

vbj [опции_драйвера] cos.inprise.vbroker.CosEvent.EventServer [опции] имя_канала/фабрики

В зависимости от опций_драйвера, последний параметр команды определяет либо имя создаваемого канала событий, либо имя фабрики каналов. Вы не можете создать более одной фабрики одновременно.

Ниже приведены основные опции_драйвера:

Важнейшей опцией является опция

-ior <имя_файла>

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

Создание Канала событий с использованием интерфейса EventChannelFactory и классов VisiBroker

Использование утилиты VisiBroker не всегда является удобным - часто разработчику проще создать один или несколько каналов непосредственно из программы. Для создания канала необходимо тем или иным образом создать фабрику каналов. Как это сделать с помощью утилиты events, было рассказано в предыдущем разделе. Программные средства создания фабрики будут рассмотрены позже.

Сейчас предположим, что объект-фабрика у нас уже есть (мы имеем на него объектную ссылку). В этом случае мы можем вызывать методы нестандартного IDL-интерфейса EventChannelFactory, который разработчиками INPRISE добавлен в модуль CosEventChannelAdmin.

IDL-объявление интерфейса EventChannelFactory

module CosEventChannelAdmin
{
   ...
   interface EventChannelFactory
   {
      exception AlreadyExists {};   
      exception ChannelsExist {};
      
      // Создание безымянного канала 
      EventChannel create();
      
      // Создание канала  с указанным именем
      EventChannel create_by_name(in string name) raises(AlreadyExists);
      
      // получение объектной ссылки на существующий канал с указанным именем
      EventChannel lookup_by_name(in string name);
      
      // уничтожение объекта-фабрики
      void destroy()  raises (ChannelsExist);
   };
};

Исключение AlreadyExists говорит о том, что канал с указанным именем уже существует и фабрика не может создать второй экземпляр. Исключение ChannelExists возбуждается, если программист делает попытку удалить фабрику, которая создала еще существующий канал.

Отображение интерфейса EventChannelFactory на Java

package org.omg.CosEventChannelAdmin;

import org.omg.CosEventChannelAdmin.EventChannel;
import org.omg.CosEventChannelAdmin.EventChannelFactoryPackage.ChannelsExist;
import java.lang.String;
import org.omg.CosEventChannelAdmin.EventChannelFactoryPackage.AlreadyExists;

public abstract interface EventChannelFactoryOperations
{
   EventChannel create();
   EventChannel create_by_name (String name) throws AlreadyExists;
   void destroy() throws ChannelsExist;
   EventChannel lookup_by_name (String name);
}

import org.omg.CosEventChannelAdmin.EventChannelFactoryOperations;
import org.omg.CORBA.Object;
import org.omg.CORBA.portable.IDLEntity;

public abstract interface EventChannelFactory 
   extends Object, EventChannelFactoryOperations, IDLEntity
{
}

Создание объекта типа EventChannelFactory

В этом разделе будет рассказано о том, как можно создать объект типа EventChannelFactory (и объект типа EventChannel) c помощью API, а не готовых утилит VisiBroker.

VisiBroker for Java позволяет создавать Каналы как transient или persistent CORBA-объекты, а также содержит методы активизации созданных объектов, чтобы программисту не нужно было вызывать метод POA::acticvate_object() или его аналоги:

package com.inprise.vbroker.CosEvent;

import org.omg.CosEventChannelAdmin.EventChannel;
import org.omg.CORBA.ORB;
import org.omg.CosEventChannelAdmin.EventChannelFactory;
import java.lang.Class;
import org.omg.PortableServer.POA;
import java.lang.String;
import org.omg.CORBA.Object;
import org.omg.PortableServer.Servant;

public synchronized class EventLibrary
{
   private static int MQL_DEFAULT;   // размер буфера сообщений
   private static POA _rootPoa;
   private static POA _transientPoa;   // POA, используемый для активизации
   // временных callback-объектов
   private static POA _persistentPoa;   // POA, используемый для активизации
   // "долгоживущих" callback-объектов
   private static ORB _orb;
   ...
      public EventLibrary() {...}
   
   // методы активизации созданных CORBA-объектов (канала или фабрики)
   public static Object activate (POA poa, Servant servant, String name)
   { ... }
   public static Object activatePersistent (Servant servant, String name)
   {...  }
   public static Object activateTransient (Servant servant) { }
   
   // методы создания канала событий (после создания объект должен быть 
   // активизирован)
   public static EventChannel create_channel () { ... }
   public static EventChannel create_channel (String name) {...}
   public static EventChannel create_channel (String name, boolean debug)
   {...}
   public static EventChannel create_channel (String name, boolean debug, 
      int queue_length) { ... }
   public static EventChannel create_channel (boolean debug, 
      int queue_length) { ... }
   
   // методы создания фабрики каналов (после создания объект должен быть 
   // активизирован). вы  не можете создать более одной фабрики одновременно
   public static EventChannelFactory create_factory (String name) { ... }
   public static EventChannelFactory create_factory (String name, 
      boolean debug )
   { ... }
   public static EventChannelFactory create_factory (String name, 
      boolean debug, int queue_length) { ... }
   
   // вспомогательные методы для получения POA и ORB
   public static POA getPersistentPoa () { ... }
   public static POA getRootPoa () { ... }
   public static POA getTransientPoa () { ... }
   public static ORB orb () { ... }
}

Присоединение к Каналу событий

Важной частью программы, использующей Сервис Событий, является присоединение к нужному Каналу, т.е. получение на него объектной ссылки. Это можно сделать различными способами. Наверное, самым подходящим из них является использование Naming Service.

Тем не менее, разработчики INPRISE предусмотрели «полустандартный» способ получения объектной ссылки - с помощью вызова ORB::resolve_initial_reference(). Этот подход нельзя назвать полностью стандартным - возможность использования значения «EventService» в качестве аргумента этого метода не оговорена в спецификации OMG.

При использовании resolve_initial_reference() для каждого приложения, которое хочет взаимодействовать с нужным каналом, нужно указать, ссылку на какой конкретный канал должен возвратить данный метод. Делается это с помощью задания опции командной строки при запуске приложения:

MySupplier -ORBInitRef EventService=<IOR_info>

Здесь IOR_info может быть как строковым представлением объектной ссылки, так и именем файла, в котором находится строковое представление этой объектной ссылки.

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

> vbj -DORBInitRef EventService=file:///d:/temp/MyChannel.ior  MySupplier

Пример использования push-модели

Полный код проектов находится на www.k-press.ru/tcs/32000/crbevent.zip. Оба проекта не используют IDL-объявления, определенные программистом. Они позволяют создать несколько Поставщиков и Получателей событий. События представляют собой объекты типа any, в которых может находиться либо целое число, либо строка. Программы демонстрируют технику создания необходимых компонентов - административных объектов, proxy-объектов, callback-объектов. Перед запуском приложений необходимо создать канал с именем MyChannel. Для этого можно использовать командный файл es_start.bat, содержащий следующую команду:

vbj -Dvbroker.events.MaxQueueLength=50 com.inprise.vbroker.CosEvent.EventServer %1

Под Windows NT команда создания канала может выглядеть так:

>start es_start MyChannel

Инициализация канала

Код, связанный с получением объектной ссылки на Канал Событий, на стороне клиента (Получателя событий) выглядит следующим образом:

package consumer;

import org.omg.CORBA.*;
import org.omg.PortableServer.*;
import org.omg.CosEventChannelAdmin.*;
import org.omg.CosEventComm.*;

public class Consumer_Frame extends JFrame
{
   public static String[] _args;
   
   private static org.omg.CORBA.ORB orb;
   private org.omg.PortableServer.POA myPoa = null;
   private EventChannel channel = null; // Канал событий
   private ProxyPushSupplier proxySupplier = null;
   byte[] consumer_objId; // ObjectId callback-объекта
   MyConsumer_Impl consumer = null; // сервант callback-объекта
   ...
      public void ConnectConsumerToChannel ()
   {
      try
      {
         org.omg.PortableServer.POA rootPoa =
            org.omg.PortableServer.POAHelper.narrow (
            orb.resolve_initial_references("RootPOA"));
         rootPoa.the_POAManager().activate();
         
         org.omg.CORBA.Policy [] policies =
         {
            rootPoa.create_lifespan_policy(LifespanPolicyValue.PERSISTENT)
         };
         myPoa = rootPoa.create_POA("MyPOA", rootPoa.the_POAManager(), 
            policies );
         
         channel = EventChannelHelper.narrow(
            orb.resolve_initial_references("EventService"));
         System.out.println ("Найден Канал событий: " + channel);
      }
      catch (Exception e)
      {
         e.printStackTrace();
      }
   }
}

Мы не будем приводить почти аналогичный код для серверного приложения.

Создание callback-объекта на стороне сервера (Поставщика событий)

package supplier;

import org.omg.CosEventComm.*;

public class MySupplier_Impl extends PushSupplierPOA
{
   public MySupplier_Impl()  {  }
   public void disconnect_push_supplier()
   {
      System.out.println ("Поставщик отсоединен от Получателей");
   }
}

public class Supplier_Frame extends JFrame
{
   public void RegisterCallbackObject ()
   {
      try
      {
         
         // получение ObjectId для callback-объекта
         supplier_objId = (ON_TextField.getText()).getBytes();
         
         // создание серванта и его активизация
         supplier = new MySupplier_Impl();
         myPoa.activate_object_with_id(supplier_objId, supplier);
         
         // создание proxy-объекта Потребителя на стороне Поставщика
         proxyConsumer = channel.for_suppliers().obtain_push_consumer();
         
         // регистрация callback-объекта
         proxyConsumer.connect_push_supplier(supplier._this());
      }
      catch (Exception e)
      {
         e.printStackTrace();
      }
   }
}

Создание callback-объекта на стороне клиента (Получателя событий)

package consumer;

import org.omg.CORBA.Any;
import org.omg.CosEventComm.*;

public class MyConsumer_Impl extends PushConsumerPOA
{
   private Consumer_Frame _frame = null;
   
   public MyConsumer_Impl(Consumer_Frame fr)
   {
      _frame = fr;
   }
   
   public void push(Any parm1) throws org.omg.CosEventComm.Disconnected
   {
      org.omg.CORBA.TypeCode tc = parm1.type();
      if (tc.kind() == org.omg.CORBA.TCKind.tk_long)
      {
         int i = parm1.extract_long ();
         _frame.CBM_Label.setText(String.valueOf(i));
      }
      else
         if (tc.kind() == org.omg.CORBA.TCKind.tk_string)
         {
            String s = new String();
            s = parm1.extract_string();
            _frame.CBM_Label.setText(s);
         }
         else
         {
            System.out.println("Wrong event type");
         }
   }
   
   public void disconnect_push_consumer()
   {
      System.out.println("Consumer was disconnected");
   }
}

public class Supplier_Frame extends JFrame
{
   public void RegisterCallbackObject ()
   {
      try
      {
         // создание серванта и его активизация
         consumer_objId = (ON_TextField.getText()).getBytes();
         consumer = new MyConsumer_Impl (this);
         myPoa.activate_object_with_id(consumer_objId, consumer);
         
         // создание proxy-объекта Поставщика на стороне Потребителя
         proxySupplier = channel.for_consumers ().obtain_push_supplier();
         
         // регистрация callback-объекта
         proxySupplier.connect_push_consumer (consumer._this());
      }
      catch (Exception e)
      {
         e.printStackTrace();
      }
   }
}
Создание и доставка события
public class Supplier_Frame extends JFrame
{
   ...
   public void SendInteger ()
   {
      try
      {
         org.omg.CORBA.Any arg = orb.create_any();
         arg.insert_long (Integer.parseInt (message_TextField.getText()));
         proxyConsumer.push (arg);
      }
      catch (Exception e)
      {
         e.printStackTrace();
      }
   }
}

Отмена регистрации callback-объектов

Действия на стороне сервера...

public class Supplier_Frame extends JFrame
{
   ...
   public void UnregisterCallbackObject()
   {
      try
      {
         proxyConsumer.disconnect_push_consumer();
         myPoa.deactivate_object (supplier_objId);
      }
      catch(Exception e)
      {
         e.printStackTrace();
      }
   }
}
... и клиента:
public class Consumer_Frame extends JFrame
{
   ...
   public void UnregisterConsumer()
   {
      try
      {
         proxySupplier.disconnect_push_supplier();
         myPoa.deactivate_object(consumer_objId);
      }
      catch(Exception err)
      {
         err.printStackTrace();
      }
   }
}

Заключение

CORBA предоставляет большие возможности по организации двустороннего взаимодействия клиентских и серверных приложений. На момент написания статьи основным инструментом реализации такого взаимодействия является Notification Service CORBA. Для достаточно простых задач имеет смысл использовать Event Service CORBA, который можно рассматривать как упрощенную реализацию Notification Service. Качественно новые возможности разработчики получат при доступности реализаций Messaging Service, которые, видимо, появятся в течение ближайших года-двух.


Copyright © 1994-2016 ООО "К-Пресс"