Занятие 1
Итак, вы уже сделали первые шаги в № 1 и в № 5 за 1999 г. в мир новых информационных технологий и обратили внимание на CORBA. Что нужно, чтобы приступить к созданию приложений на ее основе? В принципе не так уж и много: прежде всего научитесь описывать объекты, используя язык IDL, с которым вы можете познакомиться в цикле статей «IDL-заклинания эпохи распределенных вычислений» («Мир ПК», № 6-10/99). С теорией CORBA вы встретитесь на наших занятиях.
Для начала установите инструментарий: мы будем использовать VisiBroker 3.3 for Java и VisiBroker 3.3 for C++. Данные пакеты можно получить с Web-сервера компании Inprise в виде пробных версий дистрибутива. Кроме того, VisiBroker входит в достаточно распространенные в нашей стране Enterprise-редакции пакетов JBuilder, C++Builder и Delphi корпорации Borland/Inprise. Еще один плюс в пользу выбора именно этих пакетов: существуют версии для множества операционных систем, включая восходящую звезду Linux.
Установка VisiBroker происходит автоматически, поэтому, скорее всего, проблем у вас не возникнет. Пакет прописывает необходимые ключи реестра Windows, а также модифицирует файл сервисов и переменные среды. Важно лишь помнить, что переменная PATH должна указывать на подкаталог bin основного каталога, где установлен VisiBroker.
Конфигурирование
Прежде чем заниматься настройкой VisiBroker, следует познакомиться с термином «виртуальный домен» и с тем, как его понимают разработчики CORBA-систем. Виртуальный домен — это один или несколько компьютеров, логически объединенных для выполнения некоторой задачи. Виртуальным домен называют потому, что для сетевого администратора его нет, а существует он лишь за счет каких-то установленных пользователями правил. К примеру, в VisiBroker это некий выделенный порт. Так что виртуальный домен можно считать маленькой подсетью внутри основной сети.
Когда разработка ведется не на одном компьютере, а в сети, могут потребоваться дополнительные настройки. Следует обратить внимание на две переменных среды:
- OSAGENT_PORT (по умолчанию равна 14 000) служит для настройки виртуальных доменов в рамках локальной сети;
- VBROKER_ADM — системный каталог; обычно это подкаталог adm внутри основного каталога, где установлен VisiBroker; служит для хранения важной информации репозитария интерфейсов, демона активизации объектов и Smart Agent, а также является основным местом хранения конфигурационных файлов.
Настройка этих переменных вручную нужна лишь в UNIX, так как в Windows эти значения хранятся в системном реестре в виде ключей и могут быть изменены в любой момент с помощью утилиты vregedit, поставляемой с VisiBroker.
Значение OSAGENT_PORT важно, когда вы хотите изолировать компьютеры, подключенные к одной и той же локальной сети. Кроме того, изменив OSAGENT_ PORT, вы можете разделить уже работающие на серверах объекты и их неотлаженные версии, «живущие» где-нибудь на компьютере разработчика. Если этого не сделать, то при запросе объекта приложению-клиенту может быть возвращена ссылка на еще «сырой» экземпляр объекта, которая случайно «подвернулась под руку» административной утилите Smart Agent. Напротив, если в локальной сети стандартным является порт 14 000, а вы, разрабатывая новые версии CORBA-объектов, настроили переменную OSAGENT_PORT на 14 500, можете быть спокойны — ваши приложения не «зацепят» объекты в основном домене, а последние не заберутся в ваш виртуальный домен. Это равносильно тому, что вы переключили рацию на канал, недоступный другим.
Следующий тонкий момент — организация взаимодействия частей CORBA-приложений, находящихся в разных локальных сетях, но связанных друг с другом (для крупных фирм сети со сложной топологией — не редкость). В VisiBroker по умолчанию поиск объектов и балансировку нагрузки выполняют запущенные в сетях экземпляры утилиты Smart Agent. Поэтому, для того чтобы они могли беспрепятственно связываться друг с другом, в каталоге, заданном переменной среды VBROKER_ADM, должен быть файл с именем agentaddr. Внутри него на каждой строчке будет помещаться одно имя или IP-адрес компьютера, на котором запущен другой экземпляр Smart Agent. Редактируя этот список, можно формировать топологию связей объектов. Изменить имя и место файла agentaddr легко, если настроить переменную среды OSAGENT_ADDR_FILE.
Вот вы и готовы к работе с CORBA.
Порядок действий
Создавая CORBA-приложения, нужно помнить, что их модель отличается от модели традиционных монолитных программ и даже клиент-серверных систем, хотя с последними есть и нечто общее. Связку объектов CORBA и клиентов трудно назвать приложением как таковым. Подобные системы похожи на паутину, где все переплетено: клиент может в любую минуту стать сервером, и пользователь вряд ли узнает, с каким сервером объектов он работает в данный отрезок времени, а если проект выполнен грамотно, может даже и не заметить сбоя. Типичная тактика действий программы, использующей технологию CORBA, такова: соединиться с нужным объектом, использовать его функции и отсоединиться от него. И таких атомарных циклов могут быть сотни. Схожая схема принята и в Microsoft COM+, где также приложения как такового нет. Подобные действия можно сравнить с работой слесаря: когда требуется, ключи и отвертки он берет в определенной последовательности, а уже ненужный инструмент складывает обратно в ящик — нет никакой необходимости держать весь свой арсенал в руках (сравните с монолитными приложениями, в которых все функции зашиты внутри). К тому же в любой момент ремонтник может воспользоваться инструментом, одолженным у коллеги, потому что для гайки на 13 нет никакой разницы, чей ключ ее будет отворачивать, важно лишь, чтобы его размер совпадал.
Добиться хороших результатов в создании программ на основе CORBA можно, придерживаясь определенного порядка действий:
- объектно-ориентированный анализ и моделирование;
- описание и трансляция объектов;
- создание сервера;
- создание клиента;
- отладка объектов.
Это не аксиома, но опыт подтверждает эффективность данного подхода. Скорее всего, в вашем проекте объекты будут весьма зависеть друг от друга, так что иной раз следует хорошенько продумать порядок их реализации.
Объектно-ориентированный анализ и моделирование
Не стоит пытаться делать проект с использованием CORBA «наскоком». Технология CORBA рассчитана на реализацию таких систем, которые будут работать не один год, и поэтому именно хороший объектно-ориентированный анализ и моделирование могут стать залогом того, что в недалеком будущем не придется подвергать ваши приложения радикальной переделке.
В принципе для обдумывания модели проекта достаточно острого карандаша и нескольких листов бумаги. Однако чтобы ваша идея была понятна и разработчикам, нужно задокументировать ее. Построить IDL-описания по вашим моделям поможет пакет Rational Rose, нацеленный на создание UML-моделей (UML — универсальный язык описания моделей).
Создавая модели будущих объектов, следует помнить, что любой объект CORBA наследуется от CORBA::Object, поэтому ваши объекты всегда могут быть приведены к этому типу. И все же можно рекомендовать создать еще одного промежуточного предка. Пусть это будет некоторый абстрактный объект с некоторыми базовыми функциями. Это упростит внесение глобальных изменений в будущую систему. На любом этапе реализации, внедрения и эксплуатации можно будет добавить ко всем объектам новые функции, просто заменив описание промежуточного родителя. В этом несомненное преимущество компонентно-ориентированного ПО.
Еще одна подсказка. Разработайте порядок действий, в соответствии с которым будете создавать реализации объектов. Выделите в готовой модели атомарные объекты, не зависящие от других, они и станут кандидатами на первоочередное создание и отладку.
CORBA — новая технология, и это привносит в процесс разработки некоторые расширения. К примеру, неплохо было бы подумать о размещении объектов в сети, согласуясь с топологией последней. В итоге образуется четкая последовательность инсталляции готового кода, определятся виртуальные домены.
Описание и трансляция объектов
Готовая модель содержит объекты (точнее было бы сказать «классы»), которые должны быть описаны с помощью языка IDL, что необходимо не только для трансляции этих описаний в базовые исходные тексты на конкретном языке программирования, но и для последующего добавления IDL-описаний в репозитарий интерфейсов. (О репозитарии — в одном из следующих занятий.) Обратимся к трансляции объектов.
В каждой версии VisiBroker имеется свой компилятор IDL: VisiBroker for C++ оснащен компилятором idl2cpp, а VisiBroker for Java — idl2java. Первый на основе IDL-описания объектов генерирует исходные тексты на языке Cи++ и включаемые заголовочные файлы, а второй делает описания классов на языке Java и пакетную структуру имен.
Правда, VisiBroker for Java обладает еще парочкой компиляторов java2iiop и java2idl, иначе называемых технологией Caffeine. Однако пока они реализованы лишь в Java-версии VisiBroker да и больше подходят для переноса в среду CORBA старых классов Java, а не для создания новых объектов.
Итак, предположим, что у нас уже есть готовое описание некоего объекта, представляющего собой абстрактный предок для компонентов системы. Мы так и назовем его — AbstractComponent. Предположим также, что каждый компонент должен возвращать свое краткое текстовое описание, скажем, для того, чтобы сетевой администратор мог выбрать из списка объектов самый подходящий. Кроме того, по запросу программы компонент должен возвратить некий интерфейс, с помощью которого его клиент сможет получить дополнительную информацию нижнего уровня: например, к какой категории относится объект, какова его логическая модель. Да мало ли что можно реализовать подобным способом! Еще одна операция присваивает объекту уникальный идентификатор, который может быть ключом поиска в базе данных объектов:
Файл component.idl #pragma prefix ?pcworld.ru? module AbstractComponent { // Опережающее описание некоего интерфейса // для получения информации о компоненте interface ComponentInfo; interface ServiceProvider { // Компонент возвращает строку со своим // кратким текстовым описанием string getDescription(); // Операция получения информации о компоненте // в машинном виде ComponentInfo getComponentInfo(); // Операция присвоения компоненту // уникального идентификатора void setUniqueID(in long id); }; };
Дальнейшие действия для программистов на Java и Cи++ будут несколько различаться. Трансляция на Cи++ должна быть запущена командой:
idl2cpp -src_suffix cpp component.idlа трансляция на Java командует:
idl2java corba.idl
Если ошибок в IDL не было, то в результате на диске появятся всего четыре файла для Cи++, в то время как для Java файлов может оказаться гораздо больше, да и размещаться они будут в каталогах со сложной структурой. Об именах полученных файлов и их назначении вы узнаете из следующих разделов.
Создание сервера
Сервер — это программа, предоставляющая некий сервис, удаленные объекты в случае с CORBA. Серверы CORBA могут активизироваться самостоятельно, будучи запущенными системным администратором либо при загрузке операционной системы. Также программист может зарегистрировать серверы CORBA с помощью утилит, после чего специальный демон будет отслеживать входящие запросы программ-клиентов и активизировать нужные серверы автоматически. Для этой цели в VisiBroker for C++ имеется OAD (Object Activation Daemon), в VisiBroker for Java — OADJ.
Собственно, написать сервер не так сложно, как это может показаться. Схематично процесс работы типичного CORBA-сервера описывается всего несколькими шагами:
- инициализация брокера объектных запросов (далее ORB);
- инициализация объектного адаптера;
- создание экземпляра CORBA-объекта;
- экспорт созданного экземпляра;
- переход в состояние ожидания запросов.
Инициализация ORB нужна для создания коммуникационного канала между клиентом и объектами. Ниже приведен вариант инициализации для обеих версий VisiBroker. Обратите внимание на то, что метод инициализации ORB принимает аргументы командной строки, с помощью которых можно производить тонкую настройку системы:
Для Си++: CORBA::ORB_var orb = CORBA::ORB_init (argc, argv); Для Java: org.omg.CORBA.ORB orb = org.omg .CORBA.ORB.init(args,System. getProperties());
Объектный адаптер в CORBA играет особую роль. Это по сути координатор действий. Если ORB не отличается интеллектом и просто выполняет транспортные функции, то объектный адаптер занимается сложной работой по запуску и экспорту объектов, а также заведует информацией, хранящейся в репозитарии реализаций (implementation repository) — специальном хранилище данных, которым пользуется демон активизации объектов. Если сравнивать ORB с системной шиной компьютера, то объектный адаптер нечто вроде драйвера платы расширения.
В спецификации CORBA упоминаются два типа объектных адаптеров — основной (BOA) и переносимый (POA). Последний постепенно вытеснит BOA, но окончательно станет доступным лишь в четвертой версии VisiBroker. Так что мы пока будем использовать BOA.
Инициализация BOA выглядит так:
Для Си++: CORBA::BOA_var boa = orb->BOA_init(argc, argv); Для Java: org.omg.CORBA.BOA boa=((com.visigenic .vbroker.orb.ORB)orb). BOA_init();
Существуют разные методы инициализации BOA — с передачей параметров командной строки и без них, поэтому на примере выше показаны две разные вариации.
Перейдем к созданию собственно объекта. Однако уточним понятие объекта, придерживаясь терминологии, заложенной в спецификации CORBA 2.3. Объект — это абстрактная, не зависящая от языка реализации сущность, которой оперируют разработчики и пользователи при описании работы системы. Физическая же реализация объекта на языке программирования, генерируемая компилятором IDL, называется се,рвант.
Возможно, вы будете разочарованы, но объект создается тривиальным вызовом конструктора класса его серванта:
Для Си++: ServiceImpl implObject(?ServiceObj?); Для Java: Test.Service implObject = new Test .ServiceImpl(?ServiceObj?);
Разница между реализациями на языках Cи++ и Java состоит лишь в способе его вызова. Язык Cи++ умеет создавать объекты на стеке, поэтому достаточно описать локальную переменную. В Java экземпляры объектов всегда создаются динамически с помощью ключевого слова new. Все это сделано не случайно: использование локальной переменной в Си++ гарантирует вызов деструктора серванта при покидании блока, где он описан. Если создавать объект динамически, то позже придется удалять его явным вызовом ключевого слова delete. Напротив, Java может позволить себе динамическое создание серванта, потому что в конце работы он будет уничтожен сборщиком мусора виртуальной машины.
Обратите внимание, что оба серванта создаются с параметром, задающим имя для их экземпляров. Это позволяет клиенту найти конкретный экземпляр объекта по имени. Клиент, который при попытке подключиться к объекту не указывает его имя, получит ссылку на произвольный экземпляр запрашиваемого объекта с любого сервера в сети. Такой вариант удобен, когда в CORBA-системе имеется механизм балансировки загрузки серверов. Так или иначе сервант с именем реализует долгоживущий (persistent) объект, т. е. такой объект, который может пережить свой сервер. Не поймите превратно — конечно же, уничтожив программу-сервер, породившую сервант (а вместе с ним и объект), вы уничтожите все, что им было запущено, в том числе и серванты с объектами. Говоря о продолжительности жизни, следует думать о долгоживущих объектах как о сущностях, состояние которых можно как-то сохранить, и впоследствии при следующем запуске сервера восстановить объекты в том виде, который они имели во время предыдущей сессии. Если, скажем, вы создаете объект, описывающий банковский счет, то, разумеется, его лучше делать долгоживущим, потому что данные (например, сумма на счету), из которых складывается состояние объекта, не должны изменяться только потому, что администратор остановил сервер и запустил его на следующий день. Напротив, объект «Часы» не имеет смысла делать долгоживущим — после перезапуска сервера время будет безнадежно сбито, и уж лучше заново обратиться к службе точного времени (вот воистину образцовый долгоживущий объект!) и получить текущее время. Так что во многих книгах по CORBA имеет место терминологическая путаница. Следует отметить, что такие утилиты, как Object Activation Daemon и Smart Agent, регистрируют лишь долгоживущие объекты.
Другой тип объектов — временные (transient) — существует лишь в рамках того процесса, который запустил их серванты. Отслужив свой срок, временный объект «умирает». Как правило, временные объекты являются утилитами, а не сущностями. В примере с банковским счетом временными объектами будут утилиты для изменения суммы на счету, а в примере с часами временный объект может служить программным интерфейсом к службе точного времени. Как бы то ни было, после выключения сервера состояние временных объектов никого уже не интересует — в следующий раз можно создать новые объекты.
Создать временный объект легко: просто используйте для его серванта конструктор без параметров. Таким образом, любой безымянный объект по умолчанию будет временным — зачем нам несколько одинаковых долгоживущих объектов с одинаковым состоянием?! Тем не менее в CORBA есть средства и для создания именованных временных объектов. Отметим, что ссылку на временный объект программа-клиент сможет получить лишь в том случае, если она вернется как результат выполнения некоторого запроса к другому объекту или как параметр вызова, описанный на IDL в виде out или inout.
Наверняка возник вопрос: а где его взять-то, этот сервант? Разумеется, написать. Если вы не используете специальные возможности CORBA, скажем, Dynamic Skeletons (DSI), то это несложно, хотя между реализациями на Cи++ и на Java разница размером в пропасть. С Java все проще простого. После трансляции IDL вы получите не только системные классы, но и пример описания класса серванта. Имя класса будет соответствовать имени интерфейса, но к нему будет добавлен префикс _example_ (например: _example_Service для IDL-интерфейса с именем Service), и найти его можно в одноименном файле с расширением .java. По сути дела, это уже готовый сервант. Можете скопировать его и изменить по своему усмотрению, а можете просто проконсультироваться с ним. А вот idl2cpp подобными излишествами разработчика не балует, поэтому писать сервант нужно вручную. Однако важно понять, что техника создания серванта использует наследование его от класса скелета, который генерируется компилятором IDL автоматически. Найти его проще пареной репы. В Java-реализациях имя класса скелета состоит из имени IDL-интерфейса с префиксом _ и суффиксом ImplBase (например: _ServiceImplBase). VisiBroker for С++ все еще использует устаревшую схему именования скелетов, и имя таких классов состоит из имени IDL-интерфейса с префиксом _sk_ (_sk_Service). В любом случае класс создаваемого серванта наследуется от скелета, и требуется реализовать только те методы, которые относятся к вашему объекту, и ни в коем случае не трогать остальные. Как же обнаружить нужные методы? В версиях для Cи++ загляните в файл, имя которого заканчивается на _s.hh, и найдите в описании класса скелета все чистые виртуальные методы. Там же idl2cpp оставляет для вас специальный комментарий:
// The following operations need to be implemented
При использовании Java-версии VisiBroker найдите класс, чье имя совпадает с именем IDL-интерфейса, и ознакомьтесь с его методами — все они потребуют реализации в серванте.
Но вернемся к разбору порядка действий сервера. Завершающим штрихом к его запуску будет экспорт объекта и переход в состояние ожидания:
Для Си++: boa->obj_is_ready(&implObject); boa->impl_is_ready(); Для Java: boa.obj_is_ready(implObject); boa.impl_is_ready();
Первая строчка, вызывающая метод obj_is_ready, сообщает через адаптер объектов, что сервант готов обслуживать запросы. Если объекты долгоживущие, то BOA попутно зарегистрирует их в таблице запущенных объектов, которую ведет утилита Smart Agent. С этого момента объект может использоваться клиентами и другими объектами. А чтобы предотвратить завершение серверного приложения, его сознательно блокируют вызовом impl_is_ready(). В результате происходит остановка потока, на котором выполняется приложение-сервер. Разблокировать его можно, лишь вызвав из другого потока метод shutdown() серванта или вручную закрыв приложение сервера. Следует отметить, что нет нужды вызывать impl_is_ready(), если в вашем серверном приложении уже имеется свой цикл обработки сообщений — пусть он и следит за временем жизни сервера.
Создание клиента
Приложение, выполняющее роль клиента для объекта, намного проще, чем сервер. Хотя и для него установлена некоторая последовательность действий:
- инициализация ORB;
- получение ссылки на экземпляр CORBA-объекта;
- использование объекта в мирных целях.
Для Си++: Test::Service_var service = Test::Service::_bind (?ServiceObj?); Для Java: Test.Service service = Test.ServiceHelper.bind(orb, «?ServiceObj?);
Обратите внимание на небольшую разницу в именах методов и на их разное расположение. Для Си++ метод _bind() описывается непосредственно в классе объекта, а для Java метод bind() помещается в отдельный класс-хэлпер.
Важное отличие VisiBroker for C++ от VisiBroker for Java: в качестве ссылки на объект используются экземпляры специального класса. Если вернуться к примеру выше, то видно, что имя типа ссылки начинается с имени интерфейса и заканчивается суффиксом _var. Описание класса данного типа делается компилятором idl2cpp. Прелесть _var-классов состоит в прозрачности их использования. Перегруженные операторы дают возможность присваивать значения ссылок, пользоваться для доступа к методам объекта операцией разыменовывания указателя (->) и автоматически удалять объекты после окончания их использования.
Осталось разобраться, как клиент может вызывать операции объекта. Вот типичный пример, когда клиент хочет узнать значение числа p:
Для Си++: CORBA::Float value = service->get_PI_value(); Для Java: float value = service.get_PI_value();
Отладка объектов
Неоценимую помощь при отладке CORBA-программ могут оказать файлы протокола. К примеру, текстовый вывод в стандартные потоки cout, clog и cerr попадут в файлы visout.log, vislog.log и viserr.log соответственно.
Много полезного можно узнать из файлов протокола osagent.log и oad.log, если добавить ключ командной строки -v при запуске Smart Agent и Object Activation Daemon.
В VisiBroker for Java вы найдете специальный отладчик vbdebug, позволяющий просмотреть имеющиеся объекты и обращения к ним. Но, по правде сказать, инструмент этот рудиментарный и неудобный. Он требует многих манипуляций и постоянной работы руками. Поэтому лучше всего использовать старый проверенный опыт трассировки — вывод текстовой информации в поток. Этот способ прекрасно подходит для отслеживания обращений к объектам. С клиентской частью все намного проще. Стандартный отладчик способен распознать висячие ссылки. А большего и не нужно.
* * *
Скорее всего, у вас уже накопилась куча вопросов. Приберегите их до следующих занятий. При разборе практического примера все, что было непонятно, станет очевидным. А до этого момента еще раз проштудируйте язык IDL, обратив внимание на то, как он транслирует свои конструкции на языки программирования.