FreeSource : ОбзорIce

Введение в ICE

Архитектура Ice


Ice это объектно – ориентированная платформа среднего слоя. Ice снабжена инструментами, API и библиотеками для разработки объектно-ориентированных клиент – серверных приложений. Ice приложения могут быть написаны на разных программных языках, запущены под разными операционными системами и машинных архитекторах, и могут общаться используя разнообразные сетевые технологии.

Ice объект


Ice объект – это абстракция. Он характеризуется следующим:
Возвращаемые значения инициализируются сервером и передаются клиенту.
Клиенты выбирает с какими гранями объекта им хочется работать.

Прокси (proxy)


Что бы клиент мог связаться с Ice объектом ему необходимо иметь прокси этого объекта. Прокси это артефакт который локален для клиентского адресного пространства.
Он представляет Ice объект для клиента. Когда клиент вызывает операцию по прокси Ice делает следующее:
1. Определяет местоположение Ice объекта
2. Активирует сервер если он не запущен
3. Активирует Ice объект на сервере
4. Передаёт входные параметры Ice объекту
5. Ждёт когда операция закончится
6. Передаёт возвращаемые значения или генерирует исключения

Прокси инкапсулирует информацию этих действий.
Прокси содержит:
1. Адресную информацию которая позволяет клиентской стороне соединиться с нужным сервером.
2. Идентификатор объекта который является целью запроса.
3. Дополнительный идентификатор, который определят грань объекта к которой прокси обращается.

Ссылочная прокси


Информация о прокси может быть представлена строкой. Например
Simple Printer: default -p 10000

Ice предоставляет API вызовы которые позволяют преобразовать прокси в строковую форму. Это полезно например для сохранения ссылок в таблице базы данный или текстовом файле. Обеспеченный этим, клиент получает доступ к Ice объекту.

Прямая прокси (direct proxy)


Прямая прокси – это поркси которая содержится в объектном идентификаторе вместе с адресом сервера на котором запущен объект.
Адрес характеризуется:

Для связи с объектом обозначенным в прямой прокси, Ice использует адресную информацию в прокси для связи с сервером. Идентификатор объекта посылается серверу с каждым запросом клиента.

Косвенная прокси (indirect proxy)


Косвенная прокси имеет две формы. Она может иметь вид объектного идентификатора или объединённого объектного идентификатора. Объектный идентификатор может иметь вид строки. Например

Simple Printer

Косвенная прокси которая включает объединённый объетный идентификатор имеет строковую форму

Simple Printer@Printer Adapter

Каждый объект из объекта адаптера может быть доступен по такой прокси. Косвенная ссылка не содержит адресной информации. Для определения правильного сервера, клиентская сторона передаёт прокси информацию службе местоположения. Она в свою очередь использую идентификатор объекта или идентификатор адаптера объекта как ключ для поиска по таблице адреса сервера и возвращает найденный адрес клиенту. Клиент в свою очередь используя этот адрес совершая запрос.
Этот процесс похож на преобразование интернетовских доменных имён в IP адреса (DNS).

Прямое против косвенного связывание


Процесс преобразования прокси в протокольный адрес известен как связывание (binding). Не удивительно что прямое связывание использует прямые прокси, а косвенное косвенные прокси. Главное преимущество косвенное связывания в том что он позволяет перемещать сервер (изменять его адрес). Прямые же прокси не требуют определения места положения сервера, но они и бубут непригодны если сервер переместится на другую машину. Косвенные ссылки будут продолжать работать даже если сервер изменит свое местоположение.

Фиксированные прокси (fixed proxy)


Фиксированная прокси – это прокси которая привязывается к конкретному соединению: вместо размещения адресной информации или имени адаптера, прокси содержит обработчик соединения. Обработчик соединения остаётся доступным столько, сколько открыто соединение. Если соединение закроется, то прокси больше не будет работать. Фиксированные прокси используются для двухстороннего соединения.

Маршрутизирующий прокси (routed proxy)


Маршрутизирующий прокси – это прокси который переводит все вызовы специализированному объекту, взамен посылки запроса непосредственно целевому объекту. Маршрутизирующие прокси полезны для объявления сервиса, такого как Glacier 2, который позволяет соединятся с серверами которые закрыты файрволоми.

Дублирование


В Ice дублирование включает в себя создание объекта адаптера (и его объектов) доступных по разным адресам. Цель дублирование предоставить возможность запускать одинаковые серверы на нескольких компьютерах. Если случится так что один из компьютеров выйдет из строя, то его функции выполнит другой.
Использование дублирования подразумевает что приложения специально разработаны для этого. В частности это значит что клиенту доступен объект больше чем по одному адресу и он получит одинаковые результаты с этих адресов. Каждый объект объявляется так что бы поддерживать непротиворечивость их состояний.
Ice поддерживает ограниченную форму дублирования когда прокси описывает множественный адрес объекта. Ice во время работы выбирает один из адресов произвольно и проверяет его на работоспособность. Например прокси:

Simple Printer: tcp – h server1 -p 10001: tcp -h server2 -p 10002

описывает объект с идентификатором Simple Priner который доступен по TCP по двум адресам, один на хосте server1, а другой на хосте server2. Ответственность ложится на пользователей и системных администраторов которым надо быть уверенным в том что серверы действительно запущены на компьютерах по указанным портам.

Дублирование групп (replica groups)


В дополнение дублирования основанного на прокси описанного выше, Ice поддерживает более полезную форму дублирования известную как дублирование групп, которое требует использование сервиса местоположения. Дублирующая группа имеет уникальный идентификатор и состоит из набора объектных адаптеров. Объектный адаптер может быть членом больше чем одной дублирующей группы. После установки дублирующей группы её идентификатор может быть использован в косвенной прокси совместно с идентификатором адаптера. Например идентификатор дублируемой группы Printer Adapters может быть использован в прокси как показано ниже

Simple Printer@Printer Adapters

Синхронный вызов методов


По умолчанию, запрос на диспетчеризацию Ice использует синхронный вызов процедур. Это означает что клиент совершивший вызов, повисает на время выполнения процедуры, до тех пор покуда она не завершится.

Асинхронный вызов методов


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

Асинхронный метод диспетчеризации


Асинхронный метод диспетчеризации это серверный эквивалент асинхронному вызову процедур. По умолчанию используется синхронная диспетчеризация при котором после вызова процедуры поток повисает в ожидании завершения операции. При асинхронной диспетчеризации серверная сторона информирована о вызове процедуры. Однако поток сервера не блокируется, а сначала ждёт некоторое время результата и если его не получает то запускает для этого запроса дополнительный поток. Теперь серверный поток свободен и может делать что угодно. Когда же результат операции все таки становится доступным, серверное приложение делает вызов функции, что бы информировать Ice окружение о том что запрос совершенный недавно завершился, в этот момент результат передаётся клиенту.
Асинхронный метод диспетчеризации полезен в том случае если например сервер поддерживает операции которые блокируют клиентов на некоторый период времени. С синхронной диспетчеризацией каждый клиент ждёт когда завершится поток на сервере. Очевидно, этот подход не годится в случае наличия дюжины клиентов. С синхронной диспетчеризацией сотни и даже тысячи клиентов могут быть заблокированы при вызове одной и той же операции без всякого вреда серверу.
Другой возможностью использования асинхронного метода диспетчеризации является завершение операции, то есть после того как результат передан клиенту можно в этом же потоке произвести чистку или запись обновления в базу. Синхронный и асинхронный методы диспетчеризации одинаковы для клиента и он не может сказать когда использовать ту или иную диспетчеризацию.

Односторонний вызов метода


Клиент может вызвать процедуру в одностороннем порядке. Сервер в этом случае не отвечает на односторонний вызов клиента и трафик направлен только от клиента к серверу. Односторонний вызов недостоверный. Например если целевого объекта не существует, то вызов просто потеряется и клиент не узнает что что-то не так.
Односторонние вызовы могут быть использованы с операциями в который нет возвращаемого значения, out-параметров, и не бросается пользовательского исключения.
Приложение серверной стороны не различает односторонний вызов от двухстороннего. Односторонний вызов доступен если целевой объект предоставляет потоко ориентированный транспорт, такой как TCP/IP или SSL.

Группа односторонних вызовов метода


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

Дейтаграммные вызовы


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

Ice Grid


Ice Grid — это определение сервиса расположения, он преобразует символьную информацию в косвенную прокси в протокол — адрес для косвенного связывания.
У Ice Grid есть и другие возможности:

Ice Box


Ice Box это серверное приложение предоставляющее инструменты для запуска и остановки компонентов приложения. Программные компоненты могут быть развернуты как динамические библиотеки взамен порождению процесса. Это ослабляет загрузку системы.

Ice Storm


Ice Strom это сервис публикации-подписки для клиентов и серверов. Ice Storm действует как рассылка на события. Издатель посылает события сервису который в свою очередь передаёт события подписчикам. Т.е. сигнальное событие опубликованное издателем может быть послано подписчикам. События разделяются по темам и подписчики выделяют темы которые им интересны. К подписчикам попадают только те события, темы на которые они подписаны. Ice Storm полезен в том случае если вам надо рассылать информацию между большим количеством программных компонент.

Ice Patch 2


Ice Patch 2 это программа исправления. Она позволяет легко распространять обновления для клиентов. Клиенты просто подсоединяются к Ice Patch 2 серверу и запрашивают обновление для определённого приложения. Сервис автоматически проверяет версию программы на клиенте и загружает обновлённые компоненты в сжатом формате. Программные исправления могут охраняться с использованием Glacier 2, то есть только авторизованные клиенты могут загружать обновления.

Glacier 2


Glacier 2 это Ice файрвол. Он позволяет клиентам и серверам надёжно соединяться через файрвол без дополнительных средств защиты. Клиент-серверный трафик полностью кодируется с использованием публичных ключей и сертификатов. Glacier 2 предоставляет взаимную аутентификацию.


3.2. Написание описания Slice


Первым шагом написания Ice приложения будет написание описания Slice, содержащего интерфейсы, которые будут использоваться в приложениях. Для нашего простого приложения, которое будет только выводить на экран, мы напишем следующее описание Slice:



Сохраним текст в файл Printer.ice.

Наше описание Slice состоит из модуля Demo, содержащего простой интерфейс, названный Printer. Интерфейс очень простой и описывает только простую операцию, названную printString. Операция printString принимает строку как единственнй входной параметр; текст этой строки появляется на (возможно удаленном) принтере.

3.7. Написание Ice приложение на Python

Компилирование описания Slice в Python


Первым шагом к созданию приложения на Python будет компиляция нашего описания Slice для генерации прокси и скелетов на Python. Компилировать описание так:



Компилятор slice2py производит один исходный файл, Printer_ice.py, из этого определения. Компилятор также создает пакет Python для модуля Demo, попадающий в директорию Demo. Точный смысл исходного файла нас не беспокоит – он содержит сгенерированный код, относящийся к интерфейсу Printer, который мы описали в Printer.ice.

Написание Server


Для реализации интерфейса Printer мы должны создать класс-сервант. По соглашению, классы серванты, используют имя интерфейса с суффиксом I, так наш класс сервант называется Printer I:



Класс Printer I наследуется их базового класса Demo.Printer, который сгенерирован компилятором slice2py.Базовый класс абстрактный и содержит метод printString, который принимает строку для печати в принтер и параметр типа Ice.Current. (Сейчас мы игнорируем параметр Ice.Current. Мы рассмотрим его далее, в пункте 28.6.) Наша реализация метода printString просто выводит его аргумент в терминале.

Запишем код сервера в Server.py, следовательно это наш класс-сервант и вот он в полном виде:



Основная конструкция кода:



Основное тело программы содержит блок try, в который мы поместим весь код сервера, и следом за ним блок except. Блок except ловит все исключения, которые могут случиться в коде. Замысел в том, что если в коде случится непредвиденное исключение в любом месте, то раскрутка стека в любом случае приведет нас в основную программу, которая напечатает исключение и возвратит ошибку в операционную систему.

Перед выходом из кода, он уничтожает коммуникатор (если он был успешно создан). Делая это, необходимо корректно завершить Ice run time: программа _должна_ вызвать destroy на каждый созданный ей коммуникатор, иначе мы получим неопределенное поведение в результате.

Тело блока try содержит код сервера:



В коде следующие шаги:

1. Мы инициализируем Ice run time вызовом Ice.initialize. (Допишем sys.argv для этого вызова потому что сервер может иметь аргументы командной строки, которые интересны для run time; для этого примера серверу не обязательны аргументы командной строки). Запуск initialize возвращает указатель на Ice::Communicator, который является основным обработчиком для Ice run time.

2. Мы создаем объект адаптер вызывая createObjectAdapterWithEndpoints в экземпляре Communicator. Аргументами мы положим "Simple Printer Adapter" (который является именем адаптера) и “default -p 10000”, который говорит адаптеру, что надо слушать входящие запросы по протоколу default (TCP/IP) порт 10000.

3. В этом пункте run time на серверной стороне инициализировано и мы создаем сервант для нашего интерфейса Printer путем создания экзумпляра объекта Printer I.

4. Мы информируем объект адаптер о наличии нового серванта при помощи вызова add для адаптера; аргументами к add будут только что созданный сервант и идентификатор. В данном случае, строка "Simple Printer" будет именеи серванта. Если мы имеем много принтеров, каждый должен иметь различное имя, или, точнее, различный object identity.

5. Затем мы активируем адаптер вызывая его метод activite. Созданный адаптер заблокирован; (это используется, если имеется много сервантов, совместно использующих некоторый адаптер и не желающих запрашивать выполнение до того, как для всех сервантов будут созданы экземпляры). (original: The adapter is initially created in a holding state; this is useful if we have many servants that share the same adapter and do not want requests to be processed until after all the servants have been instantiated.)


6. В конце мы вызываем waitForShutdown. Этот вызов задерживает вызов нити до того, как реализация сервера прекратится, как вызовом shut down the run time, так и в отклике на сигнал. original: This call suspends the calling thread until the server implementation terminates, either by making a call to shut down the run time, or in response to a signal. (For now, we will simply interrupt the server on he command line when we no longer need it.)

Заметим, что (ниасилил). original: Note that, even though there is quite a bit of code here, that code is essentially the same for all servers. You can put that code into a helper class and, thereafter, will not have to bother with it again. (Ice ships with such a helper class, called Ice.Application—see Section 20.3.1.) As far as actual application code is concerned, the server contains only a few lines: three lines for the definition of the Printer I class, plus two lines to instantiate a Printer I object and register it with the object adapter.

Написание Client


Код клиента, в Client.py, очень похож на сервер. Вот весь код:



Заметим, что структура кода такая же, как и для сервера. Мы используем блоки try и except для обработки ошибок. Рассмотрим код блока try:

1. Как и для сервера, мы инициализируем Ice run time вызовом Ice.initialize.

2. Следующим шагом будет получение прокси для удаленного принтера Мы создаем прокси вызовом stringToProxy в коммуникаторе, с параметром "SimplePrinter:default -p 10000". Заметим, что строка созержит идентификатор объекта и номер порта, которые использует сервер. Очевидно, прямое указание идентификатора объекта и номера порта в приложении это плохая идея, но сейчас сделаем так.

3. Прокси, созданная вызовом stringToProxy – объект типа Ice::ObjectPrx, который есть корень дерева наследования интерфейсов и классов. Но, для фактического общения с принтером, нам нужна прокси для интерфейса Demo::Printer, а не интерфейса Object. Для этого нам нужно сделать down-cast вызовом Demo.PrinterPrx.checkedCast. Проверенный cast шлет сообщение на сервер, которое спрашивает: «Это прокси для интерфейса Demo::Printer?". Если так, то вызов возвращает прокси типа Demo.PrinterPrx, иначе, если прокси означает интерфейс некоторого другого типа, вызов вернет None.

4. Проверим, что down-cast успешно и если нет, пошлем сообщение и свернем клиент.

5. Сейчас мы имеем работающую прокси в нашем адресном пространстве и мы можем вызвать метод print-String, передающий строку “Helo World!”. Сервер выводит строку в терминале.

Запуск клиента и сервера

Для запуска клиента и сервера, надо сперва запустить сервер в отдельном окне:



Пока мы ничего не наблюдаем, потому что сервер просто ждёт, пока клиент с ним соединится. Запустим клиента в другом окне:



Клиент сработал и ничего не вывел, однако, в окне, где запущен сервер, мы можем выдеть строку “Hello World!”, которую напечатал принтер. Для завершения работы сервера, мы прерываем его работу в командной строке. Более чистые методы остановки сервера рассмотрены в главе 20.

Если что-то пойдет не так, клиент выведет строку об ошибке. К примеру, если если мы запустим клиент, при остановленном сервере, то получим примерно такое сообщение:



Заметим, что для успешного запуска клиента и сервера, интерпретатор Python должен иметь расширение Ice. В ALTLinux – это пакет python-module-Ice