Распределенные вычисления и технологии Inprise

       

COM и распределенные вычисления


В предыдущей статье данного цикла были рассмотрены общие вопросы организации распределенных вычислений и общие принципы взаимодействия клиентов и серверов в распределенных системах. Данная статья посвящена одной из многочисленных реализаций технологии распределенных вычислений - технологии Microsoft COM (точнее, ее расширению - COM+).

В отличие от технологий CORBA (Component Object Request Broker Architecture) или DCE (Distributed Computing Environment), появившихся изначально в виде спецификаций и лишь затем - в виде конкретных реализаций в виде продуктов тех или иных производителей, Microsoft COM появилась одновременно и в виде спецификации (т.е. правил создания серверов и клиентов, описания соответствующего API, диалекта IDL и др.), и в виде реализации (функции Windows API, утилиты в составе различных SDK, поддержка и широкое использование данной технологии в операционных системах Windows 95/98/NT, вплоть до использования реестра в качестве регистрационной базы данных COM-сервисов и списков пользователей сети при определении прав доступа к сервисам, а также поддержка COM в других программных продуктах Microsoft). Это обусловило широкую популярность COM как технологии, реализующей объектно-ориентированный подход не на уровне реализации кода, а на уровне сервисов и приложений операционной системы, несмотря на ограниченный спектр поддерживаемых этой технологией платформ (пока это различные версии Windows, хотя информация о предстоящих реализациях для других операционных систем уже начинает появляться), а также несмотря на то, что, в сущности, в COM как технологии организации распеределенных вычислений нет, по существу, ничего революционного. И вызовы удаленных процедур, и создание stub- и proxy-объектов, и регистрация сервисов в специализированных базах данных, и язык IDL как средство описания интерфейсов сервера и сервисов - все это было придумано задолго до возникновения COM (и даже задолго до появления Windows).

Отметим, однако, что COM, если следовать ее спецификациям, позволяет решить множество проблем программирования для Windows, таких как существование различных версий одних и тех же библиотек (и возможность замены новых версий библиотек старыми при установке тех или иных программных продуктов), наличие нескольких реализаций одной и той же спецификации сервиса, присутствие нескольких сервисов в одной библиотеке и др., что также существенно повлияло на популярность COM.
Однако подробное обсуждение этих возможностей выходит за рамки данной статьи. Интересующиеся этими аспектами могут более подробно ознакомиться с ними на сайте Microsoft (см., например, Brockschmidt K. What OLE is really about, www.microsoft.com/oledev/olecom/aboutole.html).

Более существенным фактором при рассмотрении имеющихся возможностей организации распределенных вычислений является то, что использование для этой цели COM является одним из самых недорогих решений. Регистрационная база данных (реестр) - это составная часть операционной системы, и, соответственно, не нуждается в отдельном приобретении; поддержка DCOM (Distributed COM) в виде соответствующих сервисов либо также присутствует в операционной системе (Windows NT), либо доступна бесплатно (Windows 95). Сервисы, занимающиеся поиском одной из нескольких реализаций сервера для данного клиента (directory services), в DCOM как таковом отсутствуют - местоположение реализации сервера фиксируется при настройке DCOM для конкретного клиента (есть, конечно, надстройки над COM, обеспечивающие такой сервис, например, Inprise OLEnterprise, но их использование не является обязательным).

Из этого, конечно, не следует, что распределенная информационная система с помощью COM/DCOM может быть создана бесплатно. Если удаленный сервер предоставляет клиентам сервисы доступа к данным, приобретению подлежат лицензии на клиентскую часть серверной СУБД (при этом их число может быть равным не числу серверов, а числу конечных пользователей - все определяется лицензионным соглашением производителя серверной СУБД). Помимо этого, могут быть и другие лицензии, подлежащие приобретению в этом случае, например, лицензия на многопользовательский доступ к Borland Database Engine, входящая в состав продукта Inprise MIDAS. Однако даже с учетом этих затрат общая стоимость такой информационной системы оказывается существенно ниже, чем при использовании, например, Inprise Entera. Естественно, чрезвычайно высоких требований к надежности систем на основе COM при этом предъявлять не стоит, но во многих случаях такое решение может оказаться вполне удовлетворительным.


Как работает MTS?


Серверы MTS создаются точно так же, как и обычные внутренние серверы автоматизации. Иными словами, они представляют собой динамически загружаемые библиотеки, реализующие интерфейс IDispatch. Однако регистрация таких объектов происходит по-другому.

Обычные COM-серверы могут быть найдены их клиентами только в том случае, если они зарегистрированы в реестре Windows; в этом случае местоположение исполняемого файла или библиотеки, содержащей его реализацию, определяется путем поиска в реестре записи, содержащей идентификатор (GUID) данного сервера. Если же COM-сервер выполняется под управлением MTS, он регистрируется не непосредственно в реестре, а в окружении MTS. Клиент при этом взаимодействует с исполняемым файлом mtx.exe как с локальным или удаленным сервером автоматизации.

Серверные объекты могут быть объединены в так называемые "пакеты" (packages). "Пакеты" бывают двух типов: Library package (выполняются в адресном пространстве породившего такой "пакет" клиента; естественно, в этом случае удаленный запуск такого "пакета" невозможен) и Server package (выполняются внутри отдельного процесса; в этом случае возможен их удаленный запуск).



Коллективное использование объектов (object pooling)


Так как при использовании MTS код, отвечающий за соединения с базами данных, обычно содержится в объектах MTS, иногда бывает полезно инициировать создание нескольких экземпляров таких объектов для последующего использования их по запросу клиентов (MTS допускает такой режим использования своих объектов). В этом случае снижается сетевой трафик между MTS как клиентом СУБД и сервером баз данных за счет снижения частоты установки и разрыва соединений с сервером.

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

Для реализации коллективного использования объектов используются специальные объекты, которые называются resource dispencers (слово dispenser означает раздаточное устройство или распределитель). Эти объекты фактически кэшируют ресурсы так, что компоненты, находящиеся в одном "пакете", могут использовать их совместно. Из объектов подобного рода следует особо отметить BDE resource dispenser и Shared property manager.

BDE resource dispenser - это объект, устанавливаемый вместе с Delphi 4 и регистрируемый программой установки Delphi в среде MTS. Он управляет коллективным использованием соединений с базами данных, использующих BDE.

Shared property manager - это объект, позволяющий использовать общие свойства для нескольких различных серверных объектов.



| >>



Отладка серверных объектов MTS


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

Для отладки серверного объекта следует открыть его в среде разработки, установить необходимые точки прерывания в его исходном тексте и выбрать из меню Delphi опцию Run/Parameters. Затем в строке Host application нужно указать местоположение исполняемого файла mtx.exe, например:

c:\winnt\system32\mtx.exe

(на Вашем компьютере это местоположение может быть другим). В строке Parameters следует указать имя "пакета", в котором установлен данный объект:

/p:"Our Stock Package"

Обратите внимание: между двоеточием и кавычками не должно быть пробела (рис. 16):

Рис. 16. Натройка параметров запуска для отладки серверного объекта

Далее следует выбрать из меню опцию Run/Run. После этого можно запустить на выполнение клиентское приложение и вызывать из него отлаживаемые методы.

При попытке запуска сервера таким образом могут возникнуть следующие проблемы. Во-первых, может оказаться, что какие-то экземпляры объекта уже созданы в адресном пространстве MTS, и в этом случае, скорее всего, просто не получится запустить его в среде Delphi (или даже просто создать выходной файл).. В этом случае следует в MTS Explorer найти соответствующий "пакет" и из его контекстного меню выбрать опцию Shut down.

Иногда отладочные операции не выполняются, если отлаживаемый серверный объект должен выполняться в адресном пространстве клиента (опция Library Package страницы Activation диалоговой панели Properties "пакета"). В этом случае в поле Host application диалоговой панели Run/Parameters можно попытаться указать имя клиентского приложения.

Отметим, что, если клиентское приложение после старта серверного объекта не будет запущено, MTS может уничтожить созданный экземпляр серверного объекта по истечении времени существования объектов, определенных для данного "пакета". По умолчанию оно равно 3 минутам, и может быть изменено с помощью опции Shut down after being idle for… страницы Advanced диалоговой панели Properties "пакета".

<< | | >>



создание простейшего серверного объекта


Предварительная подготовка

Прежде чем приступить к созданию серверных объектов, предназначенных для работы под управлением MTS, следует убедиться в том, что сам MTS и Delphi 4 установлены корректно. Во-первых, NT Option Pack, содержащий MTS, следует обязательно установить до установки Delphi. Тогда в процессе установки Delphi при обнаружении инсталляционной программой установленной копии MTS в него будет добавлен специальный "пакет" (package) BDE-MTS, содержащий объект BdeMTSDispenser (его можно обнаружить с помощью MTS Explorer, рис. 2).

Рис. 2. BdeMTSDispenser, зарегистрированный в Microsoft Transaction Server

Следует также убедиться, что данный объект поддерживает транзакции. C этой целью нужно из контекстного меню объекта BDEDispenser выбрать опцию Properties и в появившейся диалоговой панели выбрать закладку Transaction (рис. 3):

Рис. 3. Установка поддержки транзакций объектом MTS.

Далее следует запустить BDE Administrator, открыть страницу Configuration, выбрать раздел System/Init и установить значение параметра MTS POOLING равным TRUE. Только при этом значении данного параметра возможна поддержка транзакций и коллективное использование соединений с базами данных, доступных с помощью BDE (рис. 4).

Рис. 4. Установка опции MTS POOLING для коллективного использования соединений с базами данных

Для выполнения описанных ниже примеров следует создать три таблицы в трех разных базах данных. Первая из них требует наличия сервера IB Database (он входит в комплект поставки Delphi 4) и должна быть создана в базе данных IBLOCAL с помощью следующего скрипта:

CREATE TABLE STOCKTABLE ( GOODSNAME CHAR(30), PRICE FLOAT, GOODSNUMBER INTEGER NOT NULL)

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

Для генерации первичных ключей в этой таблице создадим также генератор:

CREATE GENERATOR GEN1; SET GENERATOR GEN1 TO 5

Можно ввести в таблицу какие-либо данные (рис. 5):



Рис. 5. Таблица STOCKTABLE, созданная на сервере IB Database
Следующую таблицу создадим в формате Paradox (например, с помощью Database Desktop) и поместим в базу данных DBDEMOS, поставляемую вместе с Delphi. Структура этой таблицы приведена на рис. 6.

Рис. 6. Структура таблицы delivery.db
В этой таблице будут храниться данные о заказах на доставку товаров со склада (номер позиции на складе, название товара, адрес доставки).
И, наконец, третья таблица формата dBase должна быть создана в произвольном каталоге, и этот каталог должен быть описан как псевдоним PAYDB (рис. 7):

Рис. 7. структура таблицы ord.dbf
В этой таблице будут содержаться сведения об оплате за заказы (номер позиции на складе, стоимость товара, адрес для высылки счета).
Распределенные транзакции при использовании этих таблиц будут описывать выбор товара на складе и оформление заказа на доставку. При этом в таблице заказов выбранная запись удаляется, и при этом в двух других таблицах появляются по одной записи с тем же значением первичного ключа.

Проблемы эксплуатации COM-серверов и COM+


Разработчики COM-серверов нередко сталкиваются с различными проблемами при их создании и эксплуатации. В частности, при разработке COM-серверов для доступа к данным, обслуживающих нескольких клиентов, следует позаботиться о поддержке нескольких соединений с базой данных и о работе с несколькими потоками. Создание подобного кода с помощью удаленных модулей данных Delphi или C++Builder, содержащих компоненты TDatabase и TSession, не представляет особых сложностей. Однако при большом числе обслуживаемых клиентов наличие подобного многопользовательского сервиса предъявляет серьезные требования к аппаратному обеспечению компьютера, на котором этот сервис функционирует. Поэтому нередко разработчики пытаются создать дополнительный код для осуществления совместного доступа многих клиентов к нескольким соединениям с базой данных, при этом число последних должно быть по возможности минимальным (обычно для такого разделения ресурсов используется термин "database connection pooling", и в комплекте поставки Delphi 4 Client/Server Suite имеется соответствующий пример).

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

Еще одна проблема, с которой сталкиваются разработчики приложений, предназначенных для работы с серверными СУБД - обработка транзакций, представляющих собой изменение данных в нескольких таблицах, которые либо все вместе выполняются, либо все вместе отменяются.
Нередко код, описывающий транзакцию в стандартной двухзвенной клиент серверной системе, содержится в клиентском приложении, а не в серверной части, просто потому, что в случае отката транзакции клиентское приложение должно быть уведомлено об этом. Что касается распределенных транзакций, использующих синхронные изменения в нескольких разных базах данных, их обработка практически всегда производится только в клиентском приложении. Подобные требования усложняют написание клиентских приложений, особенно если в информационной системе их несколько, и повышают соответствующие требования к аппаратной части рабочих станций. Нередко с целью изъятия кода обработки транзакций из клиентского приложения разработчики создают специализированные сервисы, ответственные за обработку транзакций (так называемые мониторы транзакций; есть и специальные продукты, предназначенные для управления распределенными транзакциями).

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

Таким образом, имеется потребность в расширении COM-технологии за счет сервиса, обеспечивающего создание COM-объектов для совместного использования многими клиентами, авторизованный доступ к этим объектам, а также при необходимости обработку транзакций этими объектами. Расширенная таким образом технология COM получила название COM+, а сам сервис, реализующий это расширение, получил название Microsoft Transaction Server (MTS).

Итак, Microsoft Transaction Server представляет собой сервис, обеспечивающий централизацию использования серверов автоматизации, а также управление транзакциями и совместное использование несколькими клиентами соединений с базой данных независимо от реализации сервера. Версия 2.0 этого сервиса входит в состав NT Option Pack (его можно получить на web-сайте Microsoft) и доступна для Windows NT и Windows 95/98.Однако некоторые возможности MTS (например, управление удаленными объектами) реализованы только в версии NT Option Pack для Windows NT.

Отметим, что, помимо создания COM-объектов для коллективного пользования, предоставления сервисов авторизации пользователя при доступе к объектам и обработки транзакций, MTS предоставляет средства мониторинга объектов и транзакций, что упрощает их реализацию.

>>



Создание клиентского приложения


Зарегистрировав созданный серверный объект в MTS, можно приступить к созданию клиентского приложения. Добавим в имеющуюся программную группу новый проект (или просто создадим новый проект). На главную форму будущего приложения поместим компоненты TDCOMConnection, TClientDataSet, TDataSourse, TDBGrid, два компонента TEdit, два компонента TLabel и три кнопки (рис. 14)

Рис. 14. Клиентское приложение для тестирования серверного объекта

В качестве свойства ServerName компонента TDCOMConnection выберем имя только что созданного нами серверного объекта (оно будет доступно, если объект зарегистрирован в MTS, и при его выборе свойство GUID будет установлено автоматически). Если же клиент разрабатывается на удаленном компьютере, следует заполнить свойства GUID и ComputerName, причем в качестве свойства GUID следует выбирать не идентификатор сервера, а идентификатор соответствующего класса объектов - так называемый CoClass GUID. Причина этого очевидна - в общем случае динамически загружаемая библиотека может содержать несколько классов серверных объектов.

Свяжем компонент TClientDataSet с компонентом TDCOMConnection, выбрав его свойство RemoteServer из единственной позиции выпадающего списка. Свойство ProviderName оставим пустым - ведь при создании сервера мы не экспортировали никаких объектов. Далее свяжем компонент TDataSource с компонентом TClientDataSet, и, наконец, свяжем компонент TDBGrid с компонентом TDataSource. Убедимся, что все невизуальные компоненты неактивны - до возникновения реальной необходимости получить какие-либо данные серверные объекты не должны быть созданы, поэтому установка свойств Active или Connected должна быть произведена на этапе выполнения.

Закончив проектирование формы, создадим обработчики событий, связанных с нажатием на кнопки:

unit stscl1; //Client of Simple MTS server

//By N.Elmanova

//01.12.1998

interface

uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, Db, DBClient, MConnect, Grids, DBGrids, StdCtrls;


type TForm1 = class(TForm) Button1: TButton; DBGrid1: TDBGrid; DCOMConnection1: TDCOMConnection; ClientDataSet1: TClientDataSet; DataSource1: TDataSource; Button2: TButton; Button3: TButton; Label2: TLabel; Label1: TLabel; Edit1: TEdit; Edit2: TEdit; procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); procedure Button3Click(Sender: TObject); private { Private declarations } public { Public declarations } end;

var Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.Button1Click(Sender: TObject);

begin DCOMConnection1.Connected:=True;

ClientDataSet1.Data:=DCOMConnection1.AppServer.GetGoods;

end; procedure TForm1.Button2Click(Sender: TObject); begin try DCOMConnection1.AppServer.AddGoods(Edit1.Text, StrToInt(Edit2.Text)); Except ShowMessage('Не могу добавить запись'); end; end;

procedure TForm1.Button3Click(Sender: TObject); var recnum:integer; begin recnum:=ClientDataSet1.FieldByName('GOODSNUMBER').Value; try DCOMConnection1.AppServer.DeleteGoods(recnum); except ShowMessage('Не могу удалить запись'); end;


end;

end.

Запустив клиентское приложение, протестируем сервер, попытавшись добавить или удалить записи (заодно проверим правильность текста созданных нами SQL-запросов). Обратите внимание: для контроля изменений в базе данных следует нажимать на кнопку Connect & Refresh - обработчик соответствующего события вызывает серверный метод, выполняющий выгрузку данных из таблицы и передачу их клиентскому приложению (рис. 15):



Рис. 15. Тестирование серверного объекта

<< | | >>



Создание клиентского приложения, использующего распределенные транзакции


Для тестирования созданного ранее сервера и инициации распределенных транзакций создадим клиентское приложение, имитирующее процесс оформления заказов. На главной форме приложения поместим кнопку с надписью "Connect", три компонента TDCOMConnection, связанные с соответствующими серверами, три компонента TClientDataSet, связанные с соответствующими компонентами TDCOMConnection, три компонента TDataSource, связанные с компонентами TClientDataSet и блокнот из двух страниц. На одной из страниц блокнота разместим компонент TDBGrid, отображающий данные из таблицы со списком товаров на складе, компонент TEdit для ввода пользователем адреса доставки, и кнопку для инициирования транзакции - принятия заказа. На второй странице поместим два компонента TDBGrid для отображения данных из двух других таблиц и компонент TSplitter между ними (рис. 25):

Рис. 25. Клиентское приложение для тестирования распределенных транзакций

Создадим обработчики событий, связанных с нажатием на кнопки:

unit allcl1; //Client application for using distributed transactions

//By N.Elmanova

//05.12.1998

interface

uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, Grids, DBGrids, Db, DBClient, StdCtrls, MConnect, ExtCtrls, ComCtrls;

type TForm1 = class(TForm) PageControl1: TPageControl; TabSheet1: TTabSheet; TabSheet2: TTabSheet; DCOMConnection1: TDCOMConnection; ClientDataSet1: TClientDataSet; DataSource1: TDataSource; DBGrid1: TDBGrid; Edit1: TEdit; Label1: TLabel; Button1: TButton; DBGrid2: TDBGrid; DBGrid3: TDBGrid; Splitter1: TSplitter; DCOMConnection2: TDCOMConnection; ClientDataSet2: TClientDataSet; DataSource2: TDataSource; DCOMConnection3: TDCOMConnection; ClientDataSet3: TClientDataSet; DataSource3: TDataSource; Button2: TButton; procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); private { Private declarations } public { Public declarations } end;

var Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.Button1Click(Sender: TObject); var n:integer;val:double;gnam,addr:widestring;


begin try n:= ClientDataSet1.FieldByName('GOODSNUMBER').Value; val:= ClientDataSet1.FieldByName('PRICE').Value; gnam:= ClientDataSet1.FieldByName('GOODSNAME').Value; addr:=Edit1.Text; DcomConnection2.Connected:=true; DCOMConnection2.AppServer.DoTrans(n,val,addr,gnam); ShowMessage('Заказ принят'); except ShowMessage('Заказ не принят '); end; DcomConnection2.Connected:=false;

end;

procedure TForm1.Button2Click(Sender: TObject); begin DCOMConnection1.Connected:=true; DCOMConnection2.Connected:=true; DCOMConnection3.Connected:=true; CLientdataset1.data:=Dcomconnection1.Appserver.GetGoods; CLientdataset2.data:=Dcomconnection2.Appserver.GetPays; CLientdataset3.data:=Dcomconnection3.Appserver.GetDelivery; DCOMCOnnection1.Connected:=false; DCOMCOnnection2.Connected:=false; DCOMCOnnection3.Connected:=false;

end; end.

Для тестирования распределенных транзакций запустим приложение. Введем адрес в компонент TEdit, выберем строку в списке товаров и нажмем на кнопку "Заказать" (рис. 26).



Рис. 26. Тестирование распределенной транзакции

В результате получим сообщение о том, что заказ принят

Нажав на кнопку Connect, обновим данные в компонентах TDBGrid. При этом запись, выбранная ранее, исчезнет, а в двух других компонентах TDBGrid появятся две новых (рис. 27):



Рис. 27. Результат выполнения распределенной транзакции

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



Сведения о завершенных и отмененных транзакциях можно получить, выбрав в MTS Explorer опцию Transaction Statistics (рис. 28):



Рис. 28. Просмотр статистики выполнения и отката распределенных транзакций

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

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

procedure TForm1.Button1Click(Sender: TObject); var n:integer;val:double;gnam,addr:widestring;

begin try n:= ClientDataSet1.FieldByName('GOODSNUMBER').Value; val:= ClientDataSet1.FieldByName('PRICE').Value; gnam:= ClientDataSet1.FieldByName('GOODSNAME').Value; addr:=Edit1.Text; DcomConnection2.Connected:=true; DCOMConnection2.AppServer.DoTrans(n,val,addr,gnam); ShowMessage('Заказ принят'); Button2Click(self); except ShowMessage('Заказ не принят '); end; DcomConnection2.Connected:=false;

end;

Отметим также, что при нажатии на кнопку "Заказать" в случае отсутствия данных в компонентах TDBGrid в клиентском приложении возникнет исключение, связанное с отсутствием нужного поля в компоненте TClientDataSet. Следовательно, данная кнопка в такой ситуации должна быть невыбираемой. Поэтому установим значение ее свойства Enabled равным False и перепишем обработчик события,связанного с нажатием на кнопку Connect::

procedure TForm1.Button2Click(Sender: TObject); begin try DCOMCOnnection1.Connected:=true; DCOMCOnnection2.Connected:=true; DCOMCOnnection3.Connected:=true; CLientdataset1.data:=Dcomconnection1.Appserver.GetGoods; CLientdataset2.data:=Dcomconnection2.Appserver.GetPays; CLientdataset3.data:=Dcomconnection3.Appserver.GetDelivery; Button1.Enabled:=true; except Button1.Enabled:=false; ShowMessage('Один из серверных объектов недоступен'); end; DCOMCOnnection1.Connected:=false; DCOMCOnnection2.Connected:=false; DCOMCOnnection3.Connected:=false;

end;

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

Итак, на примере Delphi 4 и Microsoft Transaction Server мы рассмотрели возможность и способы использования технологий COM и COM+ для организации распределенных вычислений и управления распределенными транзакциями.

Следующая статья данного цикла будет посвящена другой технологии, используемой при организации распределенных вычислений - DCE (Distributed Computing Environment), и ее реализации в многоплатформенном сервере приложений Inprise Entera.

<< |



Создание серверного объекта


Для создания серверного объекта следует со страницы Multitier репозитария объектов выбрать пиктограмму MTS Data Module (рис. 8).

Рис. 8. Выбор MTS Data Module из репозитария объектов.

Далее в появившейся диалоговой панели MTS Data Module Wizard следует ввести имя класса и выбрать способ работы с транзакциями (рис. 9).

Рис. 9. MTS Data Module Wizard

После этого будет сгенерирована стандартная библиотека типов, связанная с созданным модулем данных.

В созданный модуль данных поместим один компонент TSession, один компонент TDatabase, один компонент TProvider, один компонент TTable, два компонента TQuery (рис. 10):

Рис. 10. Модуль данных серверного объекта StockDM1, управляющего таблицей STOCKTABLE

Свойство AutoSessionName компонента TSession установим равным True. Свойство SessionName компонента TDatabase установим равным имени компонента TSession (это делается для того, чтобы не было конфликтов между именами различных пользовательских сессий внутри процесса MTS при создании нескольких однотипных объектов). Свяжем компонент TDatabase с псевдонимом IBLOCAL, установив его свойство LoginPrompt равным False (вполне очевидно, что в серверном объекте диалог ввода пароля появляться не должен - ведь клиентское приложение, использующее его, может находиться на удаленном компьютере, рис. 11).

Рис. 11. Параметры компонента TDatabase серверного объекта StockDM1

Свяжем компоненты TTable и TQuery с компонентом TDatabase, и в качестве значения свойства TableName выберем имя вновь созданной таблицы STOCKTABLE. Свяжем компонент TProvider с компонентом TTable.

Далее установим значения свойств SQL компонентов TQuery:

insert into STOCKTABLE values(:a,:b,GEN_ID(GEN1,1))

и

delete from STOCKTABLE where GOODSNUMBER=:C

Первое из SQL-предложений добавляет запись в таблицу STOCKTABLE с автоматической генерацией первичного ключа. Второе удаляет запись на основе значения первичного ключа.

Обратите внимание: ни компонент TTable, ни компонент TProvider не следует экспортировать из модуля данных.
Причина этого заключается в том, что подобные экспортированные объекты хранят состояние данных, с которыми работает конкретное клиентское приложение, поэтому при коллективном использовании таких объектов могут возникнуть коллизии. По этой причине сведения о состоянии данных для конкретных клиентов хранятся менеджером разделяемых свойств MTS (MTS shared property manager), а в модулях данных между вызовами методов эти сведения присутствовать не должны. Поэтому вместо экспорта объектов из модуля данных мы создадим метод GetGoods, предоставляющий эти данные клиентскому приложению по его запросу.

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



Рис. 12. Библиотека типов серверного объекта

Реализация созданных методов приведена ниже:

unit st1; //Simple MTS server

//By N.Elmanova

//01.12.1998

interface

uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ComServ, ComObj, VCLCom, StdVcl, BdeProv, BdeMts, DataBkr, DBClient, MtsRdm, Mtx, st_TLB, DBTables, Provider, Db;

type TStockDM1 = class(TMtsDataModule, IStockDM1) stable: TTable; StProvider: TProvider; Database1: TDatabase; Query1: TQuery; Query2: TQuery; Session3: TSession; private { Private declarations } public { Public declarations } protected function GetGoods: OleVariant; safecall; procedure AddGoods(const Gname: WideString; Gprice: Double); safecall; procedure DeleteGoods(Gnumber: Integer); safecall; end;

var StockDM1: TStockDM1;

implementation

{$R *.DFM}

function TStockDM1.GetGoods: OleVariant; begin Result:=StProvider.Data; SetComplete; end; procedure TStockDM1.AddGoods(const Gname: WideString; Gprice: Double); begin try Database1.Open; Query1.Params[0].Value:=Gname; Query1.Params[1].Value:=Gprice; Query1.Prepare; Query1.ExecSQL; Database1.Сlose; SetComplete; except SetAbort; raise; end;



end;

procedure TStockDM1.DeleteGoods(Gnumber: Integer); begin try Database1.Open; Stable.open; Stable.SetRangeStart; Stable.Fields[2].AsInteger:=Gnumber; Stable.SetRangeEnd; Stable.Fields[2].AsInteger:=Gnumber; Stable.ApplyRange; Stable.Delete; Database1.Close; SetComplete; except SetAbort; raise; end;

end;

initialization TComponentFactory.Create(ComServer, TStockDM1, Class_StockDM1, ciMultiInstance, tmApartment); end.

Прокомментируем приведенный выше код. Напомним, что мы не экспортировали компонент TProvider или компонент TTable из модуля данных, а вместо этого создали метод GetGoods, предоставляющий клиентскому приложению данные из таблицы динамически, позволяя не хранить сведения о состоянии данных в серверном объекте. Метод GetGoods представляет собой так называемый "stateless code", а сам модуль данных в этом случае представляет собой так называемый "stateless object" (об этом было рассказано выше). Именно отсутствие статических данных, связанных с конкретным клиентом, позволит в дальнейшем сделать этот модуль данных разделяемым ресурсом.

Вызов метода SetComplete внутри метода GetGoods означает, что модуль данных более не нуждается в хранении информации о состоянии и может быть деактивирован. Если модуль данных представляет собой одну из частей распределенной транзакции (пока это не так, но чуть позже он станет одной из таких частей), этот метод означает, что данная часть транзакции может быть завершена (естественно, при условии, что все другие части этой транзакции также могут быть завершены; в противном случае произойдет откат транзакции, в том числе и данной части). Если же MTS начинает транзакцию автоматически при создании модуля данных (опция Requires a transaction), вызов метода SetComplete приведет к попытке ее завершения.

Перед компиляцией проекта рекомендуется убедиться, что компоненты TDatabase, TSession, TTable неактивны.

Далее следует выбрать из меню Delphi опцию Run/Install MTS Objects. После этого следует выбрать или ввести имя "пакета" MTS (MTS package).


После этого объект окажется зарегистрированным в MTS (рис. 13):



Рис. 13. Серверный объект StockDM1, зарегистрированный в MTS

Следует обратить внимание на то, что регистрировать серверный объект как обычный COM-сервер не следует - в роли сервера с точки зрения реестра для клиента в данном случае выступает MTS, а не созданная библиотека.

При попытках внесения неоднократных изменений в код серверного объекта и запуска сервера с помощью собственно MTS или обращающихся к нему клиентов могут возникнуть проблемы. В частности, может оказаться, что при попытке компиляции библиотеки появляется сообщение о невозможности создания выходного файла. Это может быть связано с тем, что какие-то экземпляры объекта уже созданы в адресном пространстве MTS, поэтому файл оказался заблокированным. В этом случае следует в MTS Explorer найти соответствующий "пакет" и из его контекстного меню выбрать опцию Shut down. Можно также выбрать в MTS Explorer раздел My Computer и из его контекстного меню выбрать опцию Shut down server processes, прекратив таким образом существование всех серверных объектов. Кроме того, можно уменьшить время существования серверного объекта в неактивном состоянии. Это делается с помощью выбора пункта контекстного меню Properties соответствующего пакета и установкой необходимого значения свойства Shut down after being idle for… на странице Advanced появившейся диалоговой панели.


Создание серверных объектов для реализации распределенной транзакции


Теперь создадим второй объект для управления созданной ранее в базе данных DBDEMOS таблицей delivery.db. Закроем все открытые проекты и создадим новый серверный объект, такой же, как и предыдущий (рис. 17):

Рис. 17. Серверный объект DelDM для управления таблицей delivery.db

В отличие от предыдущего случая компонент TDatabase свяжем с базой данных DBDEMOS (рис. 18):

Рис. 18. Свойства компонента TDatabase серверного объекта delDM

В качестве свойства SQL компонента TQuery используем следующее SQL-предложение:

delete from delivery where OrdNum=:d

Затем отредактируем библиотеку типов, добавив три метода GetDelivery, AddDeliery и DelDelivery для получения данных из таблицы, добавления и удаления записи (рис. 19):

Рис. 19. Библиотека типов серверного объекта, управляющего таблицей delivery.db

Реализация этих методов имеет следующий вид:

unit del1; //Another simple MTS server

//By N.Elmanova

//01.12.1998

interface

uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ComServ, ComObj, VCLCom, StdVcl, BdeProv, BdeMts, DataBkr, DBClient, MtsRdm, Mtx, dels_TLB, DBTables, Provider, Db;

type TdelDM = class(TMtsDataModule, IdelDM) deltable: TTable; DelProvider: TProvider; Database2: TDatabase; Query4: TQuery; Session2: TSession; private { Private declarations } public { Public declarations } protected function GetDelivery: OleVariant; safecall; procedure AddDelivery(OrdNum: Integer; const OrdName: WideString; const OrdAddr: WideString); safecall; procedure DelDelivery(OrdNum: Integer); safecall; end;

var delDM: TdelDM;

implementation

{$R *.DFM}

function TdelDM.GetDelivery: OleVariant; begin Result:=DelProvider.Data;

SetComplete;

end;

procedure TdelDM.AddDelivery(OrdNum: Integer; const OrdName: WideString; const OrdAddr: WideString); begin try deltable.open; deltable.append; deltable.fieldbyname('OrdNum').Value:=OrdNum; deltable.fieldbyname('GoodsName').Value:=OrdName; deltable.fieldbyname('Address').Value:=OrdAddr; deltable.post; deltable.close; SetComplete; except SetAbort; raise; end;


end;

procedure TdelDM.DelDelivery(OrdNum: Integer); begin try database2.open; deltable.open; deltable.SetRangeStart; deltable.FieldByName('OrdNum').AsInteger:=OrdNum; deltable.SetRangeEnd; deltable.FieldByName('OrdNum').AsInteger:=OrdNum; deltable.ApplyRange; deltable.Delete; deltable.close; database2.close; except SetAbort; raise; end;

end; initialization TComponentFactory.Create(ComServer, TdelDM, Class_delDM, ciMultiInstance, tmApartment); end.

Скомпилируем и установим данный объект в тот же "пакет", что и предыдущий.

Рекомендуется протестировать данный объект, создав клиентское приложение, более или менее аналогичное предыдущему. При тестировании следует помнить, что в этой таблице есть уникальный первичный ключ, поэтому при вводе записей с одинаковым значением поля OrdNum транзакции завершаться не будут.

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

delete from ord where ordnum=:d



Рис. 20. Модуль данных серверного объекта pays для управления таблицей ord.dbf

Теперь компонент TDatabase свяжем с созданной нами базой данных dbpay, содержащей таблицу ord.dbf (с ней мы свяжем компонент TTable, рис. 21):



Рис. 21. Свойства компонента TDatabase серверного объекта, управляющего распределенными транзакциями

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

delete from ord where ordnum=:d

Теперь добавим в проект библиотеки типов двух созданных ранее серверов. Для этого следует выбрать из меню Delphi опцию Project/Import type library, нажать кнопку Add и выбрать соответствующий файл с расширением *.tlb (рис. 22):



Рис. 22. Импорт библиотек типов серверных объектов - участников распределенной транзакции

Далее отредактируем библиотеку типов данного серверного объекта, создав методы GetPays. AddPay, DelPay для доставки данных клиентскому приложению, добавления и удаления записей, а также метод DoTrans, реализующий распределенную транзакцию (удаление записи о выбранном товаре из таблицы STOCKTABLE в базе данных IBLOCAL и добавление по одной записи в таблицу заказов на доставку delivery.db в базе данных DBDEMOS и в таблицу счетов за заказы ord.dbf в базе данных paydb, рис. 23).





Рис. 23. Библиотека типов серверного объекта, управляющего распределенными транзакциями

Реализация этих методов имеет следующий вид:

unit pay1; //MTS server for managing distributed transactions

//By N.Elmanova

//04.12.1998

interface

uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ComServ, ComObj, VCLCom, StdVcl, BdeProv, BdeMts, DataBkr, DBClient, MtsRdm, Mtx, paysrv_TLB, dels_TLB, st_TLB, DBTables, Provider, Db;

type Tpays = class(TMtsDataModule, Ipays) paytable: TTable; PayProvider: TProvider; Database3: TDatabase; Query6: TQuery; Session3: TSession; Query5: TQuery; private FStockDM1: IStockDM1; FDelDM: IDelDM; { Private declarations } public { Public declarations } protected function GetPays: OleVariant; safecall; procedure AddPay(Pnum: Integer; Pval: Double; const Address: WideString); safecall; procedure DelPay(Pnum: Integer); safecall; procedure DoTrans(Num: Integer; Val: Double; const Addr, Gname: WideString); safecall; end;

var pays: Tpays;

implementation

{$R *.DFM}

function Tpays.GetPays: OleVariant; begin Result:=PayProvider.Data;

SetComplete;

end;

procedure Tpays.AddPay(Pnum: Integer; Pval: Double; const Address: WideString); begin try paytable.open; paytable.append; paytable.fieldbyname('OrdNum').Value:=PNum; paytable.fieldbyname('Payment').Value:=Pval; paytable.fieldbyname('Address').Value:=Address; paytable.post; paytable.close; SetComplete; except SetAbort; end; end;

procedure Tpays.DelPay(Pnum: Integer); begin try Database3.Open; paytable.Open; paytable.SetRangeStart; paytable.FieldByName('ordnum').AsInteger:=Pnum; paytable.SetRangeEnd; paytable.FieldByName('ordnum').AsInteger:=Pnum; paytable.ApplyRange; paytable.Delete; paytable.Close; Database3.Close; SetComplete; except SetAbort; raise; end; end;

procedure Tpays.DoTrans(Num: Integer; Val: Double; const Addr, Gname: WideString); begin try OleCheck(ObjectContext.CreateInstance(CLASS_StockDM1, IStockDM1, FStockDM1)); OleCheck(ObjectContext.CreateInstance(CLASS_DelDM, IDelDM, FDelDM)); FStockDM1.DeleteGoods(Num); FDelDM.AddDelivery(Num,Gname,Addr); AddPay(Num,Val,Addr); except DisableCommit; raise; end; EnableCommit;



end; initialization TComponentFactory.Create(ComServer, Tpays, Class_pays, ciMultiInstance, tmApartment); end.

Прокомментируем приведенный выше код для метода DoTrans. Этот код реализует распределенную транзакцию, вызывая методы двух порожденных ей серверных объектов и выполняя собственные манипуляции с таблицей счетов. При вызове метода DoTrans клиентским приложением все три серверных объекта функционируют согласованно.

В начале выполнения создается так называемый контекст транзакции - интерфейс ITransactionContextEx. Этот интерфейс контролирует выполнение транзакции и обладает методами CreateInstance (создание экземпляра порожденного объекта), Commit (завершение транзакции) и Abort (откат транзакции). Отметим, что если для порождения серверного объекта MTS клиентским приложением используется компонент TDCOMConnection, то для порождения серверного объекта другим серверным объектом используется вызов метода CreateInstance интерфейса ITransactionContextEx. Параметрами этого метода являются CLSID объекта, интерфейс объекта и указатель на объект (возвращаемый параметр).

Далее следуют вызовы методов порожденных серверных объектов и собственные манипуляции с данными. Если все операции были успешны, транзакция завершена, и может быть выполнен метод Commit интерфейса ITransactionContextEx. Если же операции были неуспешны, и в одном или обоих порожденных серверах либо во время собственных манипуляций с данными возникнут исключения (например, другой пользователь уже удалил запись из списка товаров, сделав заказ, или какая-то из таблиц заблокирована), будет вызван метод Abort.

Для тестирования распределенных транзакций установим все три серверных объекта в один и тот же "пакет" (рис. 24):



Рис. 24. Серверные объекты, участвующие в распределенной транзакции


Требования к объектам MTS


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

Все компоненты MTS поддерживают специфический для них интерфейс IObjectControl, содержащий методы для активации и деактивации объекта MTS и управления ресурсами (в том числе соединениями с базами данных, рис. 1).

Рис. 1. Интерфейсы объекта MTS

Как было сказано ранее, компоненты MTS могут функционировать как COM-объекты вне адресного пространства клиента (out-of-process server). В этом случае клиентское приложение взаимодействует с созданным внутри его адресного пространства proxy-объектом, передающим запросы клиента с помощью вызовов удаленных процедур содержащемуся внутри адресного пространства сервера stub-объекту, взаимодействующему с компонентом MTS посредством его интерфейса. Информация о соединении содержится в proxy-объекте. Последний может активизировать и деактивировать MTS-объект, отдавая ресурсы другим клиентам, нуждающимся в данном сервисе, незаметно для клиента.

Компоненты MTS могут также функционировать как внутренние серверы автоматизации (in-process-сервер). В этом случае их удаленный запуск исключен.

Серверный объект MTS должен иметь стандартную фабрику классов и библиотеку типов (они автоматически создаются при использовании MTS Object wizard). Можно редактировать библиотеку типов, добавляя свойства и методы (в дальнейшем они будут использованы MTS Explorer для получения сведений о его объектах на этапе выполнения). Помимо этого, компонент должен экспортировать функцию DllRegisterServer и осуществлять саморегистрацию его CLSID (идентификаторов класса сервиса), ProgID (идентификаторов сервера), интерфейсов, библиотеки типов (MTS Object wizard автоматически генерирует соответствующий код).

Если предполагается коллективное использование серверного объекта, он не должен хранить внутри себя сведения о состоянии данных, связанных с конкретным клиентом (для этого используются термины "stateless code" и "stateless object"). Пример создания такого кода будет рассмотрен ниже.

<< | | >>



Управление транзакциями


Для управления распределенными транзакциями используется специальный сервис - Microsoft Distributed Transaction Coordinator (MS DTC), который может быть активизирован или с помощью Windows Control Panel, или непосредственно из MTS Explorer.

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

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



Вопросы безопасности


MTS позволяет использовать список пользователей и групп пользователей Windows NT в качестве списка пользователей своих объектов. При этом для каждого объекта можно установить правила его эксплуатации различными пользователями и группами пользователей. Помимо этого, MTS поддерживает также механизм ролей, примерно аналогичный ролям некоторых серверных СУБД (роль представляет собой совокупность пользовательских прав и привилегий на использование тех или иных объектов, и может быть присвоена пользователю или группе пользователей). Соответственно, при использовании MTS можно не включать код реализации правил безопасности в серверные объекты.



Генерация кода для сервера и клиента


Для создания серверной и клиентской части распределенной системы на базе Entera следует сгенерировать stub-код ("заглушку"), превращающую простые вызовы функций в вызовы удаленных процедур. В частности, при использовании удаленной функции следует сообщить компилятору о том, что ее реализация находится не в клиентском приложении, а на удаленном сервере. "Заглушка" нужна для того, чтобы компилятор мог найти нужную функцию, а ее реализация представляет собой код, осуществляющий вызов удаленных процедур.

Генерация stub-кода для сервера и клиента осуществляется автоматически с помощью утилиты rcmake.exe. Ee параметры в случае любой 32-разрядной версии Delphi выглядят так:

rpcmake -d myserv.def -c delphi2.0 -s delphi2.0

В случае С или С++ параметры rpcmake выглядят следующим образом:

rpcmake -d myserv.def -c c -s c

Здесь параметр -d - имя DEF-файла, -c - язык, для которого должен быть сгенерирован клиентский stub-код, -s - язык, для которого должен быть сгенерирован серверный stub-код (eстественно, эти языки могут быть разными).

В каталоге Entera\TCP\BIN имеется также утилита rpcmgui.exe, представляющая собой GUI-оболочку для rpcmake.exe.

Рис. 11. Утилита RPCMGUI.EXE из комплекта поставки Entera 3.2 для Windows NT.

Наиболее близким описанному выше процессу генерации кода из знакомых Windows-программистам процедур является, пожалуй, генерация stub- и proxy- кода Microsoft Visual C++ для динамически загружаемых библиотек, используемых в COM-сервере и COM-клиенте, с помощью компилятора MIDL на основании IDL-описания интерфейсов сервера. Отметим, что автоматическая генерация stub-кода на основании описания интерфейсов является сейчас общепринятым процессом при организации распределенных вычислений; создание такого кода "вручную" сейчас используется довольно редко.



Использование AppCenter


Порядок запуска приложений AppCenter должен быть следующим:

Broker

Репозитарий (Database)

Агенты

Viewer

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

Рис. 16.Главное окно AppCenter Viewer

С помощью AppCenter Viewer можно создавать так называемые конфигурации объектов, содержащие набор связанных между собой серверов функциональности или любых других приложений, описывая правила их функционирования.



Конфигурация и запуск Entera Broker


Для создания сервера следует в первую очередь запустить Entera Broker. Типичный командный файл для запуска брокера выглядит следующим образом: start "Entera Broker" broker -e broker.env

Команда start предназначена для создания процесса, в котором будет выполняться брокер. "Entera Broker" - заголовок окна, созданного командой start. Третий параметр - имя исполняемого файла broker.exe из каталога Entera\TCP. Параметр -e означает использование файла конфигурации, имя которого указано в последнем параметре команды. Этот файл имеет примерно следующий вид:

DCE_BROKER=elmanova, 16000 DCE_LOG=CLIENT.LOG>

DCE_DEBUGLEVEL=D,D

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

Для успешного запуска брокера следует иметь в одном каталоге файлы broker.env, broker.exe, clistart.exe (надстройка над ODBC, обеспечивающая доступ к данным и фактически представляющая собой собственно сам сервер доступа к данным), и odet30.dll (библиотека, содержащая функции Entera API для Windows NT). Иногда бывает нужно, чтобы переменная окружения ODEDIR указывала на каталог Entera\TCP:

Set ODEDIR=D:\OpenEnv\Entera\Tcp

Окно запущенного брокера представляет собой стандартное окно 32-разрядного консольного приложения.

Отметим, что, если используется Entera 3.2 для Windows NT, брокер должен выполняться под управлением именно Windows NT; если используется Entera 3.2 для другой платформы - все выполненные выше действия следует выполнить для этой платформы (отличия здесь невелики, так как в основном здесь используются обычные команды операционной системы для установки значений переменных среды и запуска приложений). Например, в случае UNIX все команды можно выполнить в терминальном режиме или внутри терминального окна, если используется какая-нибудь графическая среда.



Конфигурация источника данных и сервера


Теперь можно приступить к созданию сервера доступа к данным. Сразу же отметим, что этот сервер не обязан находиться на том же самом компьютере, где запущен брокер; кроме того, данный сервер может выполняться под управлением Windows 95/98, что неоднократно проверено экспериментально.

В качестве источника данных используем каталог DBDEMOS или BCDEMOS, описав его в Control Panel как ODBC-источник типа dBase 5 с именем edemo.

Если такого каталога данный компьютер не содержит, может подойти любой другой ODBC-источник. Отметим, что компьютер, содержащий сервер доступа к данным, вовсе не обязан содержать Delphi, С++Builder или иное средство разработки. В принципе он может управляться операционной системой, отличной от Windows, и в этом случае может потребоваться по-другому конфигурировать доступ к данным (например, настроив соответствующим образом клиентскую часть какой-либо серверной СУБД).

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

Создадим простейший пример такого скрипта для таблицы Animals.dbf из каталога DBDEMOS:

select_animals: SELECT NAME,SIZE,WEIGHT,AREA FROM ANIMALS;

insert_animals: INSERT INTO ANIMALS (NAME,SIZE,WEIGHT,AREA) VALUES($NAME[CHAR],$SIZE[DOUBLE],$WEIGHT[DOUBLE],$AREA[CHAR]); delete_animals:

DELETE FROM ANIMALS WHERE NAME='$NAME';

update_animals: UPDATE ANIMALS SET NAME= '$NAME', SIZE='$SIZE', WEIGHT='$WEIGHT', AREA= '$AREA' WHERE NAME='$NAME';

Теперь можно создать командный файл для превращения SQL-скрипта в сервер функциональности и загрузить последний в оперативную память.

SET DB_LOGIN= SET DB_PWD= start "Demo server" clistart -d edemo -s demo -e server.env -q anim.sql

Первые две строки устанавливают переменные среды, проверяемые сервером баз данных. В третьей строке с помощью команды start в окне с именем "Demo server" запускается утилита clistart - сервер доступа к данным, использующий ODBC-сервисы.
Параметр этой утилиты -d указывает на имя ODBC-исчточника, созданного ранее. Параметр - s присваивает серверу имя demo. Параметр -e указывает на имя файла конфигурации. Параметр -q указывает на имя SQL-скрипта.

Файл конфигурации может выглядеть следующим образом:

DCE_BROKER=elmanova, 16000 DCE_LOG=SERVER.LOG DCE_DEBUGLEVEL=D,D

В первой строке этого файла указывается, где выполняется брокер.

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

Окно сервера представляет также собой стандартное окно запущенного 32-разрядного консольного приложения.

Отметим, что при использовании другого способа доступа к данным следует для создания сервера использовать соответствующую надстройку над клиентской частью серверной СУБД. Например, в случае доступа к Oracle такая надстройка называется orastart.exe, и в качестве параметра -d в этом случае указывается имя TNS-псевдонима соответствующей базы данных.

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


Мониторинг объектов


Существует несколько режимов работы AppCenter Viewer. Режим редактирования объектов был рассмотрен нами ранее. Помимо него, имеется также режим мониторинга объектов:

Рис. 25. Режим мониторинга управляемых объектов

Еще один режим (Icon Mode) позволяет отображать Viewer в виде пиктограммы, которая меняет вид и становится движущейся, если произошел сбой в работе какого-либо из управляемых объектов:

Рис. 26. Отображение AppCenter Viewer в виде пиктограммы в штатном режиме и в случае сбоя одного из управляемых объектов

Последний из возможных режимов - Cockpit Mode - позволяет различными способами отображать параметры функционирования объектов (такие, как число обращений клиентских приложений, время отклика и др.), а также вести журнал событий:

Рис. 27. Cockpit mode - режим отображения параметров функционирования наблюдаемых объектов

Cockpit (в переводе это слово означает кабину самолета с приборными панелями) представляет собой набор графических объектов для отображения свойств наблюдаемых объектов. Создается он в режиме редактирования с помощью опции меню Cockpit/New. Методом drag-and-drop на появившуюся пустую форму можно перетаскивать пиктограммы наблюдаемых объектов. Далее из появившейся диалоговой панели можно выбрать тип графика и отображаемое свойство объекта, а также характеристики, специфические для данного способа отображения (высоту графика, цвет, масштаб и др.).

Рис. 28. Установка свойств графика, помещаемого в cockpit

Типичный cockpit выглядит примерно так:

Рис. 29. Типичный cockpit на этапе выполнения

При этом диаграммы, содержащие динамические параметры, меняются в соответствии с установленным при их создании значением частоты обновления. В заключение отметим, что с помощью Inprise AppCenter можно управлять не только серверами или брокерами Entera, но и любыми другими приложениями, если таковые зарегистрированы в его репозитарии.

Российское представительство Inprise:

Тел. 7(095)238-36-11

e-mail:

|



Назначение и состав Inprise Entera


Основное назначение серверов функциональности, созданных на базе Entera - предоставлять содержащим только интерфейс пользователя "тонким" клиентским приложениям те или иные услуги, например, проведение сложных расчетов или доступ к данным, содержащимся на серверах баз данных. Будучи сервером доступа к данным (или иным сервером функциональности) для клиентского приложения, серверы на базе Entera могут быть, в свою очередь, клиентами серверных СУБД. Таким образом, с использованием Entera возможно построение трехзвенной системы, где в среднем звене содержатся средства доступа к данным, а также, при необходимости, бизнес-правила, в том числе бизнес-правила, оформленные в виде исполняемых файлов, выполнение которых инициируется при необходимости. При этом нередко среднее звено состоит из комплекса серверов приложений, функционирующих на нескольких компьютерах, нередко под управлением различных операционных систем (рис..1)

Рис. 1. Архитектура многозвенной системы с использованием Entera

Entera поддерживает как стандарт распределенных вычислений DCE (Distributed Computing Environment), так и обмен данными между клиентом и сервером функциональности непосредственно с помощью протокола TCP/IP (только версия 3.2), позволяя при этом создавать клиенnские приложения с помощью Delphi, Visual Basic, PowerBuilder, Smalltalk, Visual C++, Java, COBOL, C, C++, а также средств разработки 4-го поколения (в состав Entera входят соответствующие генераторы клиентского и серверного stub-кода). Поддерживается также широкий спектр серверных СУБД: Oracle, Sybase, Informix, Ingres, IBM DB2, Microsoft SQL Server (пользователям Entera доступны соответствующие сервисы доступа к данным, представляющие собой надстройки над клиентскими частями этих серверных СУБД).

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

Entera Broker предоставляет клиентскому приложению список доступных в сети сервисов, оформленных в виде объектов, и находит для обратившегося клиента из этого списка один из них.
Entera Broker представляет собой, по существу, так называемый Directory Service.

Сервисы безопасности (Security Services) обеспечивают доступ пользователей к этим объектам в соответствии с их правами (их обсуждение выходит за рамки данной статьи).

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

Помимо стандартного комплекта Entera Developers Package, разработчикам доступен ряд дополнительных продуктов: DCE Adapter - средство, предоставляющее возможность использования DCE клиентскими приложениями; Entera/Fx - набор утилит, включающих дополнительны средства повышения безопасности, средства равномерного распределения загрузки серверов приложений, и др. Подробности о комплектах поставки и дополнительных продуктах можно узнать на Web-сайте .

|



Обзор AppCenter


Inprise AppCenter представляет собой средство управления распределенными приложениями, позволяя осуществлять мониторинг процессов и приложений промежуточного слоя, наблюдать за производительностью компонентов распределенной системы и устанавливать правила их функционирования (такие, как баланс загрузки, связи между приложениями, создание резервных сервисов).

AppCenter состоит из пяти компонентов - утилиты просмотра (AppCenter Viewer), агента, монитора, репозитария и брокера.

Рис. 15. Архитектура Inprise AppCenter

Таблица 12.1 Компоненты AppCenter

Компонент

Описание

Viewer Пользовательская утилита для конфигурации, контроля и управления приложениями.
Agent Компонент, управляющий приложениями, содержащимися на данном компьютере. Должен присутствовать на всех компьютерах, входящих в распределенную систему и быть запущен прежде, чем начнется управление распределенной системой.
Monitor Средство контроля объектов, подлежащих управлению.
Repository Централизованное хранилище данных о конфигурациях приложений и состоянии объектов, подлежащих управлению.
Broker Сервис, способствующий поиску компонентов AppCenter.

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

AppCenter Broker также существует в одном экземпляре и предназначен, так же как и Entera Broker, для поиска в сети сервиса, необходимого для обратившегося клиента (то есть представляет собой Directory Service).

Что каается AppCenter Viewer, это пользовательская утилита (написанная на Java и, соответственно, выполнимая под управлением разных платформ), позволяющая описывать правила функционирования управляемых сервисов. В сети можно иметь несколько компьютеров, содержащих AppCenter Viewer.

Каким образом происходит взаимодействие сервисов AppCenter c управляемыми сервисами? Главным образом с помощью генерации брокером AppCenter IP-вызовов с определенной частотой c целью поиска агентов. Замеченные изменения регистрируются в базе данных. Отметим, что брокеры Entera являются по отношению к AppCenter Broker управляемыми приложениями.



Передача параметров на сервер


Предположим, нам нужно выполнить параметризованный запрос:

select_animals: SELECT NAME,SIZE,WEIGHT,AREA FROM ANIMALS WHERE NAME=$NAME[CHARACTER];

Внесем соответствующие изменения в SQL-скрипт и перезапустим сервер.

Если теперь в клиентском приложении раскрыть список подсвойств SelectRPC компонента TEnteraProvider и выбрать подсвойство Params, можно просмотреть список всех параметров запроса и обнаружить, что параметр NAME изменил значение своего свойства ParamType на ptInput.

Рис. 5. Вложенные подсвойства свойства SelectRPC компонента TEnteraProvider.

Рис. 6. Свойства параметра NAME запроса SelectRPC.

Модифицируем клиентское приложение, добавив компонент TEdit и кнопку.

Добавим обработчик события, связанный с нажатием на эту кнопку: Код для С++Builder при этом будет выглядеть так:

void __fastcall TForm1::Button1Click(TObject *Sender ) { ClientDataSet1->Close(); EnteraProvider1->SelectRPC> Params>Items[0]>Value=Edit1>Text; ClientDataSet1>Open();

}

Соответствующий код для Delphi выглядит так:

procedure TForm1.Button1Click(Sender: TObject); begin ClientDataSet1.Close; EnteraProvider1.SelectRPC.Params[0].Value:=Edit1.Text; ClientDataSet1.Open; < end;

Теперь можно сохранить, скомпилировать и запустить клиентское приложение:

Рис. 7. Клиентское приложение после выполнения параметризованного запроса

Отметим, что создание сервера доступа к данным с использованием стандарта DCE (Distributed Computing Environment), предоставляющего доступ к данным, и клиентского приложения с помощью Delphi/C++Builder Enterprise мало чем отличается от создания сервера и клиента с использованием непосредственного доступа с помощью TCP/IP.

Для создания DCE-сервера следует иметь DCE-версии файлов clistart.exe (или orastart.exe и т.д.) и odet30.dll. Следует также позаботиться о корректных значениях переменных окружения ODEDIR и PATH: SET ODEDIR=C:\OPENENV\ENTERA\DCE PATH=%ODEDIR%\BIN;%PATH%

Файл конфигурации в данном случае имеет расширение DAP (Distributed Application Profile) и имеет примерно следующий вид: [DCEApp] Broker=ncan_in_tcp:elmanova[] LogFile=client.log LogLevel=3


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

SET ODEDIR=C:\OPENENV\ENTERA\DCE PATH=%ODEDIR%\BIN;%PATH% start "Entera Broker" brk

Для запуска сервера следует выполнить команду: start clistart -d edemo -e server.dap -q anim.sql

(в случае использования ODBC-источника данных) или start orastart -d TEST1 -e server.dap -q ora1.sql

(в случае использования ORACLE)

Создание DCE-клиента с помощью Delphi/C++Builder Enterprise практически не отличается от создания TCP-клиента. Основные отличия заключаются в другом значении свойства TransportMode компонента TEnteraConnection - оно должно быть равно tmDCE, и в выборе другого имени конфигурационного файла (сlient.dap), имеющего примерно следующий вид: [DCEApp] Broker=ncan_in_tcp:ws13[] LogFile=dbserv.log LogLevel=3

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

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


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



Рис. 8. Использование Entera Broker

В заключение отметим, что создание клиентских приложений может быть осуществлено не только с помощью Delphi/C++Builder Enterprise, но и с помощью многих других средств разработки, в том числе 16-разрядных (например, SQL Windows, Visual Basic, Delphi 1.0 или Delphi 2.0 Desktop). В этом случае следует использовать для соответствующего языка программирования генератор stub-кода, содержащего вызовы удаленных процедур, связанные с выполнением SQL-запросов (он входит в комплект поставки Entera и называется Object Interface Generator), и затем встраивать сгенерированный код в клиентское приложение, то есть использовать стандартные методы создания клиентских приложений, рассмотренные в следующем разделе. Компоненты TEnteraConnection и TEnetraProvider фактически просто реализуют функциональность, которая в ином случае содержалась бы в сгенерированном stub-коде.



Реализация вызовов удаленных процедур


Помимо функциональности, связанной с доступом к базам данных, можно использовать Entera для реализации любой другой функциональности, то есть использовать RPC (Remote Procedure Calls - вызовы удаленных процедур) для удаленного доступа к функциям, осуществляющим любые другие действия (например, расчеты).

Отметим, что RPC являются основой механизма обмена данными между клиентом и сервером в рассмотренных выше примерах. Генерация этих вызовов осуществляется неявно при использовании компонента TEnteraProvider.

Ниже будут рассмотрены примеры явного вызова RPC (так называемые Simple RPC Calls). В этом случае следует создать stub-код для сервера и клиента; напомним, что вызовы удаленных процедур осуществляются за счет обмена пакетами данных (эта процедура иногда называется маршалингом или маршрутизацией) между двумя stub-объектами в адресных пространствах сервера и клиента (подробнее об этом рассказано в первой статье данного цикла).

Рис. 9. Осуществление вызовов удаленных процедур

С целью иллюстрации этой процедуры создадим простейший TCP-сервер с помощью Entera 3.2.



Создание DEF-файла


Рассмотрим какую-либо функцию, написанную на Pascal, например, для вычисления синуса путем разложения в ряд:

function sin1(x: Double): Double; VAR I:INTEGER; R:DOUBLE;SL:DOUBLE;XX:DOUBLE; CONST DELTA=0.0001 ; begin SL:=X; I:=1; R:=0; XX:=X*X; WHILE (ABS(SL)>DELTA) DO BEGIN R:=R+SL; SL:=-(SL*XX/(2*I))/(2*I+1); inc(i); END; result:=R; end;

Реализация ее с помощью C++ выглядит так:

double sin1(double x) { int ii; double xx,r,sl,f,delta=0.0001; sl=x; ii=1; r=0; xx=x*x; f= fabs(sl); while (f>delta) { r=r+sl; sl=-(sl*xx/(2*ii))/(2*ii+1); f=fabs(sl); ii=ii+1 ; } return(r); }

Создадим простейшее приложение для тестирования этой функции:

Рис. 10. Приложение, подлежащее разбиению на клиента и сервер функциональности

unit SINTST;

interface

uses Windows, Messages, SysUtils, Classes,Graphics, Controls, Forms, Dialogs, StdCtrls, Buttons, TeEngine, Series, ExtCtrls, TeeProcs, Chart,a1;

type TForm1 = class(TForm) Chart1: TChart; Series1: TFastLineSeries; BitBtn1: TBitBtn; procedure BitBtn1Click(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation

{$R *.DFM}

function sin1(x: Double): Double; VAR I:INTEGER; R:DOUBLE;SL:DOUBLE;XX:DOUBLE; CONST DELTA=0.0001 ;

begin SL:=X; I:=1; R:=0; XX:=X*X; WHILE (ABS(SL)>DELTA) DO BEGIN R:=R+SL; SL:=-(SL*XX/(2*I))/(2*I+1); inc(i); END; result:=R;

end; {sin1}

procedure TForm1.BitBtn1Click(Sender: TObject); VAR I:INTEGER;X:DOUBLE;Y:DOUBLE;

begin FOR I:=1 TO 270 DO BEGIN X:=0.1*I; //Y:=SIN1(I); Y:=sin1(X); CHART1.SERIES[0].ADDXY(X,Y,FLOATTOSTR(X),clwHITE); END

end;

end.

Предположим, нам нужно разделить это приложение на сервер функциональности, вычисляющий синус, и "тонкого" клиента, рисующего график этой функции. В этом случае создание сервера функциональности следует начать с определения его интерфейса, для чего следует создать DEF-файл с описанием интерфейса этой функции на языке IDL (Interface Definition Language); вспомним, что IDL фактически представляет собой стандарт описания интерфейсов, и все его диалекты похожи друг на друга.


[ uuid(cdf19a00-22c8-11d2-a36c-008048eb72de), version(1.0) ] interface a1{ double sin1( [in] double x); }

Этот код присваивает серверу уникальный код UUID (Universal Unique Identifier), который можно сгенерировать с помощью алгоритма, определенного Open Software Foundation и реализованного в ряде утилит и функций (например, есть функция Windows API CoCreateGUID, реализующая этот алгоритм). В случае TCP-сервера в действительности он не используется, так же как и номер версии сервера, но используется в случае DCE-сервера. Затем объявляется интерфейс сервера, в котором можно описать несколько функций (в нашем случае она одна).

При написании DEF-файлов следует учитывать соответствие типов данных в DEF-файлах и используемых для создания серверных и клиентских частей языков программирования:

Объявления в DEF-файле Входные параметры Pascal Выходные параметры Pascal
Short x x: Smallint var x: Smallint
long x x: Longint var x: Longint
int x x: Integer var x: Integer
float x x: Single var x: Single
double x x: Double var x: Double
char x x: Char var x: Char

Создание и редактирование конфигураций


В качестве простейшего примера создадим конфигурацию, устойчивую к сбоям. С этой целью щелкнем правой клавишей мыши по папке "Configurations" и из контекстного меню выберем пункт Wizards|New Entera Configuration. Введем для конфигурации имя Sinus.

Рис. 17. Создание конфигурации Entera-серверов

Далее появится несколько диалоговых окон, в которых следует ввести данные в интерфейсные элементы. В частности, следует зарегистрировать используемые в данной конфигурации компьютеры, указать имена компьютеров, на которых функционируют серверы и брокеры, каталоги, в которых находятся исполняемые файлы серверов функциональности, указать номера TCP-портов для обращения к брокерам, и в результате будут автоматически сгенерированы env-файлы (ранее мы создавали их вручную).

Рис. 18. Регистрация управляемых компьютеров

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

Рис. 19. Иерархия объектов в созданной группе

Выбрав из контекстного меню опцию Properties, можно изменить свойства управляемых объектов (например, число попыток повторного запуска после остановки сервера).

Рис. 20. Установка значений свойств управляемого объекта

Выбрав закладку Dependencies, можно установить связи между объектами (например, указать, какой брокер Entera обслуживает данный набор серверов).

Рис. 21. Установка связей между объектами

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

Рис. 22. Связи между сервисами и компьютерами

Теперь можно запустить конфигурацию и пронаблюдать, как ппочередно запускаются объекты (в нашем случае исполняемые файлы a1_s.exe). Если это приложение принудительно закрыть, AppCenter будет снова пытаться запустить его, пока не будет достигнуто число попыток, указанное в параметре Weighting.

Можно создать конфигурацию и без использования экспертов, выбирая пункт New из соответствующих контекстных меню и определяя свойства вновь созданных объектов.
Есть также возможность создания так называемых DUMMY-конфигураций, то есть конфигураций, не соответствующих никаким реальным объектам, с целью моделирования их поведения. Для имитации их поведения можно создавать макросы AppCenter.

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

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

Отметим, что объекты AppCenter обычно создаются на основе шаблонов с заранее предопределенными свойствами. Однако у серверов функциональности могут быть и собственные характеристики, за которыми необходимо осуществлять наблюдение, например, количество или интенсивность RPC-вызовов, объем передаваемых данных. Чтобы наблюдаемые объекты могли предоставлять такие характеристики, они должны содержать экспортирующие их вызовы функций Enterprise Management Interface (EMI) API (обсуждение которого выходит за рамки данной статьи). В этом случае следует создать собственный шаблон управляемого объекта (например, на основе уже имеющегося).



Рис. 23. Собственный шаблон TCP1, созданный на основе готового шаблона Entera TCP Process

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



Рис. 24. Добавление новых свойств к шаблону объекта


Создание клиентского приложения (C/C++)


Код, сгенерированный утилитой rpcmake для клиента C/C++ (a1_c.c), имеет следующий вид:

/*######################## # Client Proxy Procedure Code # generated by rpcmake version 3.0 # on Thursday, December 31, 1998 at 19:17:39 # # interface: a1 # */

#include <stdio.h> #if defined __mpexl defined _MACINTOSH_ #include "dceinc.h" #else #include <dceinc.h> #endif

double sin1(double x) { double rv = 0; int Socket; struct table *dce_table = NULL;

dce_checkver(2, 0); if ((Socket = dce_findserver("a1")) >= 0) { dce_push_double(Socket,"x",x); dce_table = dce_submit("a1", "sin1", Socket); } rv = dce_pop_double(dce_table,"dce_result"); dce_table_destroy(dce_table); return(rv); }

H-файл для него имеет следующий вид:

**************************************/ * * Client Header for a1 * Generated by rpcmake version 3.0 * on Thursday, December 31, 1998 at 19:17:39 * **************************************/ #ifdef __cplusplus extern "C" { #endif

extern double sin1(double ); #ifdef __cplusplus } #endif

Код клиентского приложения, аналогичный приведенному выше для Delphi, в случае С++Builder выглядит следующим образом:

//----------------------------------------- #include "dceinc.h" #include "myserv.h" USEUNIT("a1_c.c"); USELIB("odet30.lib"); //----------------------------------------- void __fastcall TForm1::FormCreate(TObject *Sender) { dce_setenv("client.env",NULL,NULL); } //--------------------------------------------------------------------------- void __fastcall TForm1::FormDestroy(TObject *Sender) { dce_release(); } //--------------------------------------------------------------------------- void __fastcall TForm1::BitBtn1Click(TObject *Sender) { int i; double x1,y; for (i=1;i<271;i++) { x1=0.1*float(i); y=sin1(x1); Chart1->Series[0]->AddXY(x1,y,FloatToStr(x1),clWhite); } } //-----------------------------------------



Создание клиентского приложения (Delphi)


Код, сгенерированный утилитой rpcmake для клиента Delphi (a1_c.pas), имеет следующий вид:

unit a1_c;

interface

uses SysUtils, Classes, ODET3020;

function sin1(x: Double): Double;

implementation

function sin1(x: Double): Double; var dce_table : PTable; socket : Integer; rv : Integer; begin dce_table := nil; dce_checkver(2,0); socket := dce_findserver('a1'); if (socket > -1) then begin dce_push_double(socket,'x',x); dce_table := dce_submit('a1','sin1',socket); end; sin1 := dce_pop_double(dce_table,'dce_result'); dce_table_destroy(dce_table); end; end.

Этот код заставляет клиентское приложение обращатьcя к удаленной функции как к локальной. В действительности этот код представляет собой серию вызовов удаленных процедур. Все интерфейсы функций, к которым обращается клиент, содержатся в файле odet30.pas (dceinc.h), а их реализация - в файле odet30.dll.

Создадим клиентское приложение для тестирования созданного сервера. Создадим новый проект, добавим в него сгенерированный модуль a1_c.pas (a1_c.c в случае С++Builder) и сошлемся на него и на odet3020.pas в модуле, связанном с главной формой приложения. На форму поместим интерфейсные элементы, необходимые для тестирования сервера (примерно те же, что и в тестовом примере, расмотренном выше; можно сделать копию этого проекта и внести в нее необходимые изменения).

Создадим обработчики событий, связанных с нажатием на кнопки, а также с созданием и уничтожением формы:

unit sin_cln1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, Buttons, TeEngine, Series, ExtCtrls, TeeProcs, Chart; type TForm1 = class(TForm) Chart1: TChart; Series1: TFastLineSeries; BitBtn1: TBitBtn; procedure BitBtn1Click(Sender: TObject); procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation uses a1_c, odet3020;

{$R *.DFM} procedure TForm1.BitBtn1Click(Sender: TObject); VAR I:INTEGER;X:DOUBLE;Y:DOUBLE;


begin FOR I:= 1 TO 270 DO BEGIN X:=0.1*I; Y:=sin1(X); CHART1.SERIES[0].ADDXY(X,Y,FLOATTOSTR(X),clwHITE); END;

end;

procedure TForm1.FormCreate(Sender: TObject); Var rv : integer; msg : array [0 .. 200] of char; begin rv := dce_setenv ('client.env', nil, nil); if (rv = 0) then begin dce_error (msg); MessageDlg('TCP Error: ' + msg, mtInformation, [mbOK], 0); PostMessage(Handle, WM_QUIT, 0, 0); end;

end;

procedure TForm1.FormDestroy(Sender: TObject); begin dce_close_env;

end;

end.

Отметим, что при создании главной формы приложения следует вызвать процедуру dce_setenv из библиотеки odet3020.dll, указав имя конфигурационного файла client.env в качестве параметра. К моменту запуска клиента этот файл должен существовать и иметь примерно следующий вид:

DCE_BROKER=elmanova, 16000 DCE_LOG=CLIENT.LOG DCE_DEBUGLEVEL=D,D

По окончании работы приложения следует вызвать процедуру dce_close_env, уничтожающую запущенные с помощью dce_setenv сервисы Entera.


Создание клиентского приложения с помощью Delphi или C++Builder Enterprise


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

Запустим Delphi 3 Enterprise (или C++Builder 3 Enterpise) и создадим новое приложение. На главной форме приложения поместим компоненты TEnteraConnection, TEnteraProvider, TClientDataset, TDataSource, TDBGrid и TDBNavigator. Установим свойство SQLFile компонента TEnteraConnection равным имени SQL-скрипта (его следует скопировать на компьютер, где будет функционировать клиент). Установим свойство ConfigFile этого же компонента равным имени файла окружения:

DCE_BROKER=elmanova, 16000 DCE_LOG=client1.LOG DCE_DEBUGLEVEL=D,D

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

Свойство TransportName компонента TEnteraConnection должно быть установлено равным tmTCP. Cвойство ServerName должно содержать имя сервера (значение параметра -s).

Рис. 2. Свойства компонента TEnteraConnection.

Далее свойство Server компонента TEnteraProvider следует установить равным имени компонента TEnteraConnection. После этого следует выбрать из выпадающих списков значения свойств SelectRPC, InsertRPC, DeleteRPC, UpdateRPC.

Рис. 3. Свойства компонента TEnteraProvider.

Далее следует установить свойство Provider компонента TClientDataSet равным имени компонента TEnteraProvider. Свойство RemoteServer следует оставить пустым (в отличие от случая использования этого компонента с серверами, управляемыми MIDAS).

Теперь можно установить свойство Active этого компонента равным true и связать его с источником данных и интерфейсными элементами формы (рис.3.5 )

Рис. 4. Главная форма клиентского приложения.

Теперь можно сохранить проект, скомпилировать и запустить его.



Создание клиентского приложения (Visual Basic)


Отметим, что клиентское приложение может быть создано с помощью любых версий и разновидностей Delphi (начиная с 1.0 и включая Standard-версии), любых версий C++Builder и вообще любых компиляторов С++. Помимо этого, для создания клиентских приложений можно использовать и другие средства разработки. В качестве примера рассмотрим Visual Basic for Applications.

Для начала создадим клиентский stub-код для Visual Basic, создав и выполнив командный файл вида:

set ODEDIR=F:\OPENENV\ENTERA\TCP PATH=%ODEDIR%\BIN;%PATH% rpcmake.EXE -d myserv.def -c bas

В результате получим файл a1_c.vb вида:

Function sin1# (x#) dim dce_table as long, Socket as integer

call dce_checkver(2,0) Socket = dce_findserver("a1") If (Socket > -1) Then Call dce_push_double(Socket,"x",x) dce_table = dce_submit("a1","sin1",Socket) End If sin1 = dce_pop_double(dce_table,"dce_result") Call dce_table_destroy(dce_table) End function

Теперь создадим новый документ MS Word 97 (или MS Excel 97), сделаем видимой панель инструментов Visual Basic, войдем в режим конструктора и выведем на экран панель интерфейсных элементов. Далее поместим в документ кнопку:

Рис. 12. Создание клиента Entera c помощью VBA

Затем дважды щелкнем на созданной кнопке и перейдем в редактор Visual Basic. Добавим к документу форму UserForm1, поместим на ней несколько меток.

Рис. 13. Создание формы клиента Entera

Теперь создадим обработчик события, связанный с нажатием на кнопку CommandButton1 в документе (его прототип уже имеется в редакторе кода):

Private Sub CommandButton1_Click() x = sin1#(0.25) UserForm1.Label1.Caption = x x = sin1#(0.5) UserForm1.Label2.Caption = x x = sin1#(0.75) UserForm1.Label3.Caption = x x = sin1#(1#) UserForm1.Label4.Caption = x x = sin1#(1.25) UserForm1.Label5.Caption = x x = sin1#(1.5) UserForm1.Label6.Caption = x x = sin1#(1.75) UserForm1.Label7.Caption = x x = sin1#(2#) UserForm1.Label8.Caption = x x = sin1#(2.25) UserForm1.Label9.Caption = x x = sin1#(2.5) UserForm1.Label10.Caption = x x = sin1#(2.75) UserForm1.Label11.Caption = x x = sin1#(3#) UserForm1.Label12.Caption = x UserForm1.Show


End Sub

После обработчика события добавим stub-код, содержащийся в сгенерированном файле a1_c.vb.

И, наконец, экспортируем в проект c помощью пункта меню Файл/Экспорт файла модуль odet30.bas из комплекта поставки Entera.

Создадим файл client.env в каталоге, содержащем документ. Возможно, потребуется отредактировать присоединенный к проекту модуль, изменив параметры процедуры dce_setenv, указав путь к файлу client.env:

rv = dce_setenv("client.env", "", "")

Отметим, что библиотека odet30.dll должна быть доступна нашему приложению, так как из нее им производятся вызовы функций.

Теперь можно вернуться в документ из среды разработки Visual Basic for Applications, выйти из режима конструктора и нажать кнопку в документе. На экране появится форма с результатами вызова удаленных процедур примерно следующего вида:



Рис. 14. Клиент Entera на этапе выполнения

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



Создание серверной части (C/C++)


В результате использования утилиты rpcmake.exe (или rpcmgui.exe) с парамерами, соответствующими выбору C или C++ в качестве языка для сервера, получим следующие файлы: a1_c.c (stub-код, который можно встраивать в клиентское приложение), a1_s.c (исходный текст консольного приложения для сервера функциональности), a1_s.h - h-файл для myserv_s.c, a1.h - h-файл, содержащий объявления функций без их реализации (используется в клиентском приложении). Реализацию этих функций (a1.c) следует создать разработчику.

Код для сервера a1.с, сгенерированный этой утилитой, выглядит следующим образом:

/*######################## # Server Proxy Procedure Code # generated by rpcmake version2.0 # on Thursday, December 31, 1998 at 19:17:39 # # interface: a1 #

######################## # server stub routines # ########################*/ #ifdef __mpexl #include "dceinc.h" #else #include <dceinc.h> #endif

#include <stdio.h> #include <stdlib.h> #include <string.h>

#ifdef __cplusplus extern "C" { #endif

/* RPC stub and stub handle definitions */ void rpc_handle (char *, struct table *, int); void rpc_sin1 (struct table *, int); #ifdef __cplusplus } #endif void rpc_sin1 (struct table *dce_table,int Socket) { double x; int _i; double sin1(double);

x = dce_pop_double(dce_table,"x"); dce_push_double(Socket,"dce_result", sin1(x)); }

int main(int argc,char **argv) { char *ode_file = NULL,*ode_server = NULL; char dce_func[VARLEN]; struct table *dce_table; int called_init_func = 0; int socket,rsocket;

if (!parse_args(&argc, argv,&ode_file)) { printf ("Env flag (-e) not set\n"); ode_file = argc > 1 ? argv[argc-1] : (char *) NULL; } if (dce_setenv(ode_file,NULL,NULL) == 0) { fprintf(stderr,"Set env %s failed\n", ode_file); fprintf (stderr,"Reason: %s\n", dce_errstr()); exit(1); } ode_server = dce_servername("a1");

dce_checkver(2, 0);

if ((socket = dce_init_server( ode_file,ode_server)) <= 0) { fprintf (stderr,"setup server failed\n"); fprintf (stderr,"Reason: %s\n", dce_errstr()); dce_set_exit(); }


while(1) { dce_table = dce_waitfor_call(socket,dce_func); if (dce_should_exit() dce_err_is_fatal() ) {break; } else{ if (dce_server_is_ded()) { dce_spawn(socket,argc,argv,ode_file,ode_server); socket = dce_retsocket(); /* save for future */ } rsocket = dce_retsocket(); /* (old socket closed) */ rpc_handle(dce_func,dce_table,rsocket); dce_send(rsocket,dce_func); dce_recv_conf(rsocket); dce_release(); dce_table_destroy(dce_table); if (!dce_server_is_ded()) { dce_close_socket(rsocket); } } } dce_close_socket(rsocket); dce_table_destroy(dce_table); return(0); }

void rpc_handle(char *func,struct table *dce_table, int Socket) { if (strcmp(func,"sin1")==0) (void)rpc_sin1(dce_table,Socket); else (void)dce_unknown_func(func, dce_table, Socket); }

H-файл a1_s.h выглядит следующим образом:

/************************************* * * Server Header for a1 * Generated by rpcmake version 3.0 * on Thursday, December 31, 1998 at 19:17:39 * **************************************/ #ifdef __cplusplus extern "C" { #endif

extern double sin1(double ); #ifdef __cplusplus } #endif

Код реализации функции следует создать разработчику. Он должен иметь примерно следующий вид:

USEUNIT("A1_s.c"); USELIB("odet30.lib"); //----------------------------------------------------------------- double sin1(double x) { int ii; double xx,r,sl,f,delta=0.0001; sl=x; ii=1; r=0; xx=x*x; f= fabs(sl); while (f>delta) { r=r+sl; sl=-(sl*xx/(2*ii))/(2*ii+1); f=fabs(sl); ii=ii+1 ; } return(r); }

Отметим, что все функции, связанные с выделением памяти в обычном С-коде, следует заменить на соответствующие функции c префиксом dce_ (например, dce_malloc) из Entera API.


Создание серверной части (Delphi)


В результате использования утилиты rpcmake.exe с парамерами, соответствующими выбору Delphi в качестве языка для сервера, получим следующие файлы: a1_c.pas (stub-код, который можно встраивать в клиентское приложение), a1_s.dpr (исходный текст консольного приложения для сервера функциональности), a1.pas - файл-заготовка, используемая при создании сервера, в который разработчику следует добавить код реализации функций (примерно так, как пишутся обработчики событий), a1.inc - код, содержащий объявления функций без их реализации (секция интерфейса).

Код для сервера a1_s.dpr, сгенерированный этой утилитой, выглядит следующим образом:

{$APPTYPE CONSOLE} program a1_s;

uses SysUtils, ODET3020, a1; procedure rpc_sin1(dce_table: PTable; socket: Integer); var rv : Integer; x : Double; begin x := dce_pop_double(dce_table,'x'); dce_push_double(socket,'dce_result',sin1(x)); end; procedure rpc_handle(func: PChar; table: PTable; socket: Integer); begin if (StrComp(func,'sin1')=0) then rpc_sin1(table,socket) else dce_unknown_func(func, table, socket); end;

{Constants and global variables follow} const VARLEN = 100; var ode_file : PChar; ode_server : PChar; dce_func : PChar; argarr : array[0..5] of array[0..VARLEN] of Char; argptrs : array[0..5] of PChar; argv : PChar; argc : Integer; dce_table : PTable; called_init_func : Integer; socket, rsocket, i, rv : Integer; msgstr : String; begin {main} GetMem(ode_file,VARLEN); GetMem(ode_server,VARLEN); GetMem(dce_func,VARLEN); called_init_func := 0;

FillChar(argarr,SizeOf(argarr),#0); FillChar(argv,SizeOf(argv),#0);

for i := 0 to ParamCount + 1 do begin StrCopy(argarr[i],PChar(ParamStr(i))); argptrs[i] := @argarr[i]; end; {for}

argc := ParamCount + 1; argv := @argptrs;

rv := parse_args(argc, argv, ode_file);

if (rv=0) then begin Writeln('Env flag (-e) not set'); if ParamCount > 1 then StrCopy(ode_file,PChar(ParamStr(ParamCount))); end;

if (dce_setenv(ode_file,NIL,NIL) = 0) then begin msgstr := 'Set env '+ode_file^+' failed'; WriteLn(msgstr); msgstr := 'Reason: '+dce_errstr; Writeln(msgstr); Halt(1); end;


ode_server := dce_servername('a1'); dce_checkver(2, 0); socket := dce_init_server(ode_file,ode_server); if (socket <= 0) then begin Writeln('setup server failed'); Writeln('Reason: '+dce_errstr); dce_set_exit; end;

while(True=True) do begin dce_table := dce_waitfor_call(socket,dce_func); if (Boolean(dce_should_exit) or Boolean(dce_err_is_fatal)) then Exit else begin if (Boolean(dce_server_is_ded)) then begin dce_spawn(socket,argc,argv,ode_file,ode_server); socket := dce_retsocket; (* save for future *) end; rsocket := dce_retsocket(); (* (old socket closed) *) rpc_handle(dce_func,dce_table,rsocket); dce_send(rsocket,dce_func); dce_recv_conf(rsocket); dce_release; dce_table_destroy(dce_table); if (dce_server_is_ded=0) then dce_close_socket(rsocket); end; {else} end; {while}

FreeMem(ode_file,VARLEN); FreeMem(ode_server,VARLEN); FreeMem(dce_func,VARLEN);

dce_close_socket(rsocket); dce_table_destroy(dce_table); end.

Секция интерфейса a1.inc выглядит следующим образом:

function sin1(x: Double): Double;

Код реализации функций a1.pas имеет следующий вид (жирным шрифтом выделены строки, которые следует добавить разработчику):

unit a1; interface uses SysUtils, ODET3020; {$include a1.inc}

implementation

function sin1(x: Double): Double;

VAR I:INTEGER; R:DOUBLE;SL:DOUBLE;XX:DOUBLE; CONST DELTA=0.0001 ;

begin SL:=X; I:=1; R:=0; XX:=X*X; WHILE (ABS(SL)>DELTA) DO BEGIN R:=R+SL; SL:=-(SL*XX/(2*I))/(2*I+1); inc(i); END; result:=R;

end; {sin1} end. {unit}

Cервер можно скомпилировать из среды разработки или из командной строки, вызвав компилятор Pascal:

Dcc32 -b a1_s.dpr

Перед компиляцией проекта файл ODET3020.pas (интерфейс к ODET3020.DLL - библиотеке, содержащей Entera API) следует поместить в тот же каталог, что и компилируемый проект. Исполняемый файл также требует наличия этой библиотеки в каком-либо доступном каталоге.

Полученный сервер функциональности, как обычно, представляет собой консольное приложение.


Создание серверов доступа к данным


В типичных информационных системах с использованием Entera обычно имеется три звена:

Сервер баз данных

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

Презентационный слой - та часть информационной системы, которая создается с помощью Delphi или C++Builder. Приложение презентационного слоя запрашивает данные у приложения среднего звена и представляет их пользователю в виде, удобном для просмота и редактирования.

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

Наряду с версией Delphi и C++Builder Client/Server Suite имеются версии Delphi и C++Builder Enterprise, позволяющие создавать "тонкие" клиентские приложения для сервера приложений Inprise Entera c помощью компонентов TEnteraConnection и TEnteraProvider. Использование этих средств разработки будет рассмотрено далее.

В качестве примера рассмотрим использование Entera для Windows NT (использование Entera для Unix требует примерно тех же приемов).



Тестирование сервера функциональности


Для тестирования сервера следует создать для него env-файл с описанием переменных окружения:

DCE_BROKER=elmanova,16000 DCE_DEBUGLEVEL=DEBUG,DEBUG DCE_LOG=server.log

Далее следует запустить Entera Broker (если он еще не запущен), создав предварительно конфигурационный файл broker.env:

start broker -e broker.env

Затем следует создать и запустить командный файл для запуска сервера:

set odedir = c:\OpenEnv\Entera\TCP start "IT IS A SERVER" a1_s -e server.env

Только после этого можно запускать или отлаживать клиентское приложение.



Управление функционированием информационной системы предприятия с помощью Inprise AppCenter


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



Многопользовательская работа в трехзвенных информационных системах


Отметим, что организация многопользовательской работы в трехзвенных системах с CORBA-сервером практически не отличается от организации многопользовательской работы в системах с COM-серверами. В обоих случаях при коллизиях следует создавать обработчик события OnReconcileError компонента TClientDataSet.

Рассмотрим часто встречающийся при многопользовательской работе случай, когда два пользователя пытаются отредактировать одну и ту же запись. При работе с сетевыми версиями настольных СУБД и при работе с серверными СУБД в такой ситуации обычно используется блокировка записи (или нескольких записей, занимающих общую единицу хранения информации, называемую страницей), не позволяющая пользователю редактировать запись, уже редактируемую другим пользователем, пока последний ее не освободит.

В случае трехзвенной системы механизм блокировок, используемый в традиционной двухзвенной модели "клиент/сервер", может оказаться неприемлемым, так как при использовании briefcase model промежуток времени между редактированием записи и сохранением ее в базе данных может быть весьма длительным. Поэтому организация многопользовательской работы в трехзвенных системах отличается от привычной.

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

Так как данные, предоставляемые компонентом TClientDataSet, представляют собой содержимое кэша, вполне возможно, что несколько пользователей создадут свои локальные копии исходных записей, полученных с сервера баз данных, и каждый начнет их редактировать.
Предположим, два пользователя отредактировали одну и ту же запись и пытаются сохранить ее на сервере. В этом случае тот из пользователей, кто пытался первым сохранить в базе данных свой вариант отредактированной записи, сумеет это сделать, тогда как второй пользователь должен быть уведомлен об ошибке, связанной с тем, что сохраняемая им запись уже изменена другим пользователем.

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

Откроем проект клиентского приложения, созданного ранее. Далее на странице страницы Dialogs репозитория объектов выберем пиктограмму Reconcile Error Dialog. В результате к проекту будет добавлена диалоговая панель следующего вида (рис. 11):



Рис. 11. Reconcile Error Dialog из репозитария объектов.

Далее включим ссылку на модуль вновь созданного диалога в секцию uses модуля, связанного с главной формой проекта. Следует также перенести вновь созданную диалоговую панель в список Available Forms на странице Forms диалоговой панели опций проекта. В противном случае этот диалог будет появляться при запуске приложения.

Затем создадим обработчик события OnReconcileError компонента ClientDataSet1:

procedure TForm1.ClientDataSet1ReconcileError(DataSet: TClientDataSet; E: EReconcileError; UpdateKind: TUpdateKind; var Action: TReconcileAction); begin Action := HandleReconcileError(DataSet, UpdateKind, E);

end;

Отметим, что в модуле, связанной с вновь созданным диалогом, уже содержится функция HandleReconcileError, ссылка на которую содержится в обработчике событий.

Скомпилируем и сохраним проект, а затем запустим две копии созданного приложения. В каждой из копий приложения внесем разные изменения в одно и то же поле одной и той же записи и попытаемся сохранить изменения в базе данных. При этом первое изменение с успехом сохранится, а в результате попытки внести второе изменение появится вновь созданная диалоговая панель (рис. 12):





Рис. 12. Сообщение о конфликте, получаемое пользователем при попытке сохранения измененных данных.

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

Отметим, что обработка коллизий, возникших при редактировании detail-записей, производится точно так же.

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

<< | | >>



Назначение Smart Agent


Как было сказано выше, Smart Agent (osagent) представляет собой службу, предназначенную для поиска сервера, содержащего реализацию CORBA-объекта, запрошенного клиентом. Если существует несколько серверов, содержащих подобную реализацию, Smart Agent обеспечивает баланс загрузки, равномерно распределяя клиентов между имеющимися серверами.

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

Рис. 14. Шесть "тонких" клиентов и два экземпляра сервера: демонстрация баланса загрузки

Отметим также, что Smart Agent также способствует восстановлению системы после сбоев, пытаясь либо перезапустить сервер при потере клиентом соединения с ним, либо найти другой подходящий сервер из доступных. Отметим, однако, что при создании систем, устойчивых к сбоям, желательно создавать серверные объекты, не содержащие данных, связанных с конкретным клиентским приложением и предоставляющие их с помощью вызова соответствующих методов (пример такого кода содержится в статье, посвященной созданию объектов Microsoft Transaction Server).

Подобно Entera Broker, Smart Agent должен функционировать где-либо в локальной сети (не обязательно на том же компьютере, где функционируют сервер или клиенты). Для взаимодействия Smart Agent и ORB-клиента используется протокол UDP, требующий меньше ресурсов, чем TCP/IP.

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



Понятие о stub- и skeleton-объектах


Итак, мы убедились,что создание CORBA-приложений с помощью Delphi, на первый взгляд, мало отличается от создания COM-приложений. Для обеспечения взаимодействия клиента и сервера, функционирующих в разных адресных пространствах или на разных компьютерах (в том числе и в разных операционных системах), как и в случае других технологий объектно-ориентированных распределенных вычислений, используются объекты, расположенные в адресных пространствах клиента и сервера и обменивающиеся данными между собой. В терминологии CORBA они называются stub и skeleton. Stub - это представитель сервера в адресном пространстве клиента (иногда для его обозначения используют и термин proxy). Skeleton - это представитель клиента в адресном пространстве сервера (рис. 13).

Рис. 13. Взаимодействие CORBA-клиента и CORBA-объекта

Клиентское приложение взаимодействует со stub-объектом, вызывая его методы (названия которых совпадают с названиями методов серверного объекта). В действительности stub-объект обращается к клиентской части Object Request Broker (ORB), обращающейся, в свою очередь, к специализированному сервису middleware - Smart Agent (он может функционировать на каком-либо из компьютеров сети), представляющему собой не что иное, как directory service - службу, обеспечивающую поиск доступного сервера, содержащего реализацию запрашиваемого клиентом объекта.

Когда сервер найден, в его адресном пространстве создается запрошенный серверный объект, содержащий, в свою очередь, skeleton-объект, которому ORB передает запрос клиента с помощью Basic Object Adaptor (BOA). Используя эту службу, skeleton регистрирует созданный серверный CORBA-объект с помощью Smart Agent, а также сообщает о доступности, факте создания и о готовности объекта принимать запросы клиента.

Как и в случае COM, stub-объект и skeleton-объект взаимодействуют между собой с помощью маршалинга, представляющего собой обмен данными (передаваемые данные упаковываются в так называемый marshalling packet и распаковываются после передачи в другое адресное пространство) и передачу указателей на интерфейсы и аргументы функций между этими объектами.


Оба эти объекта (stub и skeleton) в случае использования Delphi 4 создаются автоматически при определении интерфейса CORBA-объектов в редакторе библиотеки типов.

Проиллюстрируем сказанное выше на примере созданнного нами ранее CORBA-сервера. При создании CORBA-объекта (в нашем случае модуля данных) автоматически были сгенерированы два модуля. Первый из них - serv_TLB.pas (так называемый stub-and-skeleton unit) - представляет собой автоматически сгенерированный код классов stub- и skeleton-объектов:

unit serv_TLB; // ************************************************************************ // // WARNING // // ------- // // The types declared in this file were generated from data read from a // // Type Library. If this type library is explicitly or indirectly (via // // another type library referring to this type library) re-imported, or the // // 'Refresh' command of the Type Library Editor activated while editing the // // Type Library, the contents of this file will be regenerated and all // // manual modifications will be lost. // // ************************************************************************ //

// PASTLWTR : $Revision: 1.11.1.75 $ // File generated on 11.01.99 16:12:40 from Type Library described below.

// ************************************************************************ // // Type Lib: E:\Program Files\Borland\Delphi4\Projects\CORBA\serv.tlb // IID\LCID: {42ED5200-A96E-11D2-B185-000000000000}\0 // Helpfile: // HelpString: Project1 Library // Version: 1.0 // ************************************************************************ //

interface uses Windows, ActiveX, Classes, Graphics, OleCtrls, StdVCL, SysUtils, CORBAObj, OrbPas, CorbaStd;

// *********************************************************************// // GUIDS declared in the TypeLibrary. Following prefixes are used: // // Type Libraries : LIBID_xxxx // // CoClasses : CLASS_xxxx // // DISPInterfaces : DIID_xxxx // // Non-DISP interfaces: IID_xxxx // // *********************************************************************//



const LIBID_serv: TGUID = '{42ED5200-A96E-11D2-B185-000000000000}'; IID_Icrb1: TGUID = '{42ED5201-A96E-11D2-B185-000000000000}'; CLASS_crb1: TGUID = '{42ED5203-A96E-11D2-B185-000000000000}'; type

// *********************************************************************// // Forward declaration of interfaces defined in Type Library // // *********************************************************************//

Icrb1 = interface; Icrb1Disp = dispinterface;

// *********************************************************************// // Declaration of CoClasses defined in Type Library // // (NOTE: Here we map each CoClass to its Default Interface) // // *********************************************************************//

crb1 = Icrb1;

// *********************************************************************// // Interface: Icrb1 // Flags: (4416) Dual OleAutomation Dispatchable // GUID: {42ED5201-A96E-11D2-B185-000000000000} // *********************************************************************//

Icrb1 = interface(IDataBroker) ['{42ED5201-A96E-11D2-B185-000000000000}'] function Get_Table1: IProvider; safecall; property Table1: IProvider read Get_Table1; end;

// *********************************************************************// // DispIntf: Icrb1Disp // Flags: (4416) Dual OleAutomation Dispatchable // GUID: {42ED5201-A96E-11D2-B185-000000000000} // *********************************************************************//

Icrb1Disp = dispinterface ['{42ED5201-A96E-11D2-B185-000000000000}'] property Table1: IProvider readonly dispid 1; function GetProviderNames: OleVariant; dispid 22929905; end;

Tcrb1Stub = class(TDataBrokerStub, Icrb1) public function Get_Table1: IProvider; safecall; end;

Tcrb1Skeleton = class(TDataBrokerSkeleton) private FIntf: Icrb1; public constructor Create(const InstanceName: string; const Impl: IUnknown); override; procedure GetImplementation(out Impl: IUnknown); override; stdcall; published procedure Get_Table1(const InBuf: IMarshalInBuffer; Cookie: Pointer); end;



Cocrb1 = class class function Create: Icrb1; class function CreateRemote(const MachineName: string): Icrb1; end;

Tcrb1CorbaFactory = class class function CreateInstance(const InstanceName: string): Icrb1; end;

implementation

uses ComObj;

{ Tcrb1Stub }

function Tcrb1Stub.Get_Table1: IProvider; var OutBuf: IMarshalOutBuffer; InBuf: IMarshalInBuffer; begin FStub.CreateRequest('Get_Table1', True, OutBuf); FStub.Invoke(OutBuf, InBuf); Result := UnmarshalObject(InBuf, IProvider) as IProvider; end;

{ Tcrb1Skeleton }

constructor Tcrb1Skeleton.Create(const InstanceName: string; const Impl: IUnknown); begin inherited; inherited InitSkeleton('crb1', InstanceName, 'IDL:serv/Icrb1:1.0', tmMultiThreaded, True); FIntf := Impl as Icrb1; end;

procedure Tcrb1Skeleton.GetImplementation(out Impl: IUnknown); begin Impl := FIntf; end;

procedure Tcrb1Skeleton.Get_Table1(const InBuf: IMarshalInBuffer; Cookie: Pointer); var OutBuf: IMarshalOutBuffer; Retval: IProvider; begin Retval := FIntf.Get_Table1; FSkeleton.GetReplyBuffer(Cookie, OutBuf); MarshalObject(OutBuf, IProvider, Retval); end;

class function Cocrb1.Create: Icrb1; begin Result := CreateComObject(CLASS_crb1) as Icrb1; end;

class function Cocrb1.CreateRemote(const MachineName: string): Icrb1; begin Result := CreateRemoteComObject(MachineName, CLASS_crb1) as Icrb1; end;

class function Tcrb1CorbaFactory.CreateInstance(const InstanceName: string): Icrb1; begin Result := CorbaFactoryCreateStub('IDL:serv/crb1Factory:1.0', 'crb1', InstanceName, '', Icrb1) as Icrb1; end;

initialization CorbaStubManager.RegisterStub(Icrb1, Tcrb1Stub); CorbaInterfaceIDManager.RegisterInterface(Icrb1, 'IDL:serv/Icrb1:1.0'); CorbaSkeletonManager.RegisterSkeleton(Icrb1, Tcrb1Skeleton);

end.

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


Редактировать этот модуль не рекомендуется.

Второй автоматически сгенерированный модуль (в нашем случае serv2.pas) содержит реализацию методов сервера. Этот файл подлежит редактированию (в нем мы создавали обработчики событий, связанных с созданием и уничтожением экземпляра модуля данных):

unit serv2;

interface

uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ComObj, VCLCom, StdVcl, BdeProv, DataBkr, CorbaRdm, CorbaObj, serv_TLB, Db, DBTables;

type Tcrb1 = class(TCorbaDataModule, Icrb1) Table1: TTable; Table2: TTable; DataSource1: TDataSource; procedure crb1Create(Sender: TObject); procedure crb1Destroy(Sender: TObject); private { Private declarations } public { Public declarations } protected function Get_Table1: IProvider; safecall; end;

var crb1: Tcrb1;

implementation

{$R *.DFM}

uses serv1, CorbInit, CorbaVcl ;

function Tcrb1.Get_Table1: IProvider; begin Result := Table1.Provider; end;

procedure Tcrb1.crb1Create(Sender: TObject); begin Form1.UpdateCount(1);

end;

procedure Tcrb1.crb1Destroy(Sender: TObject); begin Form1.UpdateCount(-1);

end;

initialization TCorbaVclComponentFactory.Create('crb1Factory', 'crb1', 'IDL:serv/crb1Factory:1.0', Icrb1, Tcrb1, iMultiInstance, tmSingleThread); end.

Как и в случае редактирования библиотеки типов COM-сервера, при создании новых свойств и методов CORBA-сервера "заготовки" методов генерируются в этом модуле автоматически при выполнении операции его обновления (кнопка Refresh панели инструментов библиотеки типов). Этот же модуль отвечает и за создание для каждого из доступных клиентам интерфейсов объекта TCorbaFactory, создающего или находящего подходящий экземпляр класса реализации описанных методов и передающего его интерфейс соответствующему классу skeleton-объектов.

<< | | >>



Поставка CORBA-приложений


При поставке CORBA-серверов и клиентов следует соблюдать следующие правила:

Библиотеки ORB, содержащиеся в каталоге Viisibroker\Bin, должны быть установлены как на компьютерах, содержащих серверы, так и на клиентских рабочих станциях; Если клиентское приложение использует динамический интерфейс вызовов, следует запустить сервер репозитария интерфейсов и зарегистрировать CORBA-сервер. Если необходимо, чтобы запуск сервера был инициирован по запросу клиента, нужно запустить Object Activation Daemon (OAD) хотя бы на одном компьютере локальной сети и зарегистрировать в нем CORBA-сервер. Smart Agent (osagent) следует установить хотя бы на один компьютер в локальной сети.

Помимо этого, следует позаботиться о следующих переменных окружения:

PATH - добавить каталог, в котором содержатся библиотеки ORB;

VBROKER_ADM - указать каталог, в котором содержится репозитарий интерфейсов, OAD и Smart Agent;

OSAGENT_ADDR - IP-адрес компьютера, где функционирует Smart Agent;

OSAGENT_PORT - порт, используемый агентом для прослушивания запросов;

OSAGENT_ADDR_FILE - файл с адресами экземпляров Smart Agent (если их несколько);

OSAGENT_LOCAL_FILE - файл с информацией о сети для Smart Agent, если он функционирует на компьютере, имеющем более чем один IP-адрес;.

VBROKER_IMPL_PATH - каталог, содержащий репозитарий реализаций;

VBROKER_IMPL_NAME - имя репозитария реализаций, принятое по умолчанию.

Следующая статья данного цикла будет посвящена созданию CORBA-серверов и CORBA-клиентов с помощью C++Builder. Как уже было сказано ранее, методы работы с CORBA в этих средствах разработки имеют ряд принципиальных различий, достойных подробного рассмотрения.

<< |



Раннее и позднее связывание


Обычно CORBA-клиенты используют раннее, или статическое, связывание при обращении к интерфейсам объектов сервера. В этом случае производительность системы в целом выше, и, кроме того, на этапе компиляции возможен синтаксический контроль имен свойств и методов серверного объекта. Для использования раннего связывания следует добавить модуль, содержащий код классов stub- и skeleton-объектов, к клиентскому приложению.

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

Для реализации позднего связывания обычно нужно либо запустить сервер вручную, либо зарегистрировать интерфейсы данного сервера в репозитарии интерфейсов (Interface Repository). Подобная регистрация позволяет использовать данный сервер клиентам, написанным с помощью любых языков программирования, если таковые поддерживают так называемый dynamic invocation interface (DII) - интерфейс динамических вызовов, а также разрабатывать клиентские приложения, используя зарегистрированные интерфейсы сервера.

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

<< | | >>



Регистрация интерфейсов и реализаций


При запуске сервера последний информирует ORB о том, какие интерфейсы он реализует. Соответствующий код автоматически добавляется к CORBA-серверу, если он создается с помощью экспертов CORBA Data Module или Corba Object репозитария объектов Delphi 4.

Обычно CORBA-серверы запускаются вручную, как в рассмотренном ранее примере. Однако при необходимости можно организовать автоматический запуск сервера или создание серверных объектов только по запросу клиента. С этой целью используется сервис, который называется Object Activation Daemon (OAD).

Автоматический запуск сервера возможен обычно в том случае, когда, во-первых, имеется регистрационная база данных, содержащая сведения о доступных реализациях подобных серверов, о том, какие серверные объекты могут быть ими созданы и какие интерфейсы в них реализованы, и, во-вторых, когда данный сервер в этой базе данных зарегистрирован. Соответственно OAD может запустить сервер, если последний зарегистрирован в репозитарии реализаций (Implementation Repository), содержащем сведения о том, в каких приложениях реализованы те или иные объекты.

Если сведения о затребованном объекте есть в репозитарии реализаций, ORB обращается не к серверу, а к OAD, как если бы он был сервером. Когда клиент запрашивает объект, OAD перенаправляет запрос к настоящему серверу, запуская его, если в этом есть необходимость.



Регистрация в репозитарии интерфейсов


Для регистрации интерфейсов следует создать репозитарий интерфейсов, запустив соответствующий сервер. С этой целью можно использовать сохраненный ранее IDL-файл с описанием интерфейса.

Сервер репозитария интерфейсов запускается с помощью следующей команды:

irep [-console] IRname [file.idl]

Аргументы этой утилиты представлены ниже:

-console - запустить репозитарий интерфейсов как консольное приложение;

IRname - имя репозитария интерфейсов;

file.idl - IDL-файл для описания состава репозитария интерфейсов (необязательный параметр).

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

irep myrepos serv.idl

Когда сервер репозитария интерфейсов запущен, можно добавлять в репозитарий другие интерфейсы, выбирая опцию File|Load и выбирая новый IDL-файл. Можно также сохранить текущее содержимое репозитария интерфейсов в IDL-файле(опция File|Save). В этом случае при перезапуске сервера репозитария интерфейсов не потребуется вносить изменения в исходный IDL-файл (рис. 15).

Рис. 15. Сервер репозитария интерфейсов

Можно также регистрировать дополнительные интерфейсы с помощью утилиты idl2ir:

idl2ir [-ir IRname] {-replace} file.idl

Аргументы этой утилиты представлены ниже:

-ir IRname - имя репозитария интерфейсов (если оно не указано, интерфейс регистрируется во всех доступных репозитариях);

-replace - заменять описания методов новыми версиями;

file.idl - IDL-файл, содержащий описания регистрируемых интерфейсов.

Если сервер репозитария интерфейсов запущен, интерфейсы из него удалить нельзя. Для этого нужно остановить сервер репозитария, создать новый IDL-файл и снова запустить сервер, зарегистрировав созданный IDL-файл при запуске.



Регистрация в репозитарии реализаций с помощью Object Activation Daemon


Для регистрации интерфейсов сервера с помощью Object Activation Daemon (OAD) нужно запустить его где-нибудь в сети с помощью команды

oad [options]

Список опций можно узнать, запустив OAD с ключом /? .

Собственно регистрация интерфейсов произволдится с помощью утилиты oadutil.

oadutil reg [options]

где

-i <interface name> - имя IDL-файла

-r <repository id> - уникальный идентификатор интерфейса (например, 'IDL:serv/crb1Factory:1.0');

-o <object name>- имя серверного объекта, поддерживающего интерфейс;

-cpp <file name> - имя исполняемого файла сервера;

-host <host name> - имя компьютера, где функционирует OAD (необязательный параметр);

-d<reference data> - данные, передаваемые серверу при запуске(необязательный параметр);

-p<policy> - способ многопользовательского доступа (shared, unshared);

-a arg1 - аргументы командной строки сервера (необязательный параметр);

-e env1 - переменные среды, необходимые для функционирования сервера (необязательный параметр).

Пример:

oadutil reg -r IDL:serv/crb1Factory:1.0 -o crb1 -cpp Serv.exe -p unshared

В результате при успешной регистрации получим примерно следующее сообщение:

Completed registration of repository_id = IDL:serv/crb1Factory:1.0 object_name = crb1 reference data = path_name = Serv.exe activation_policy = UNSHARED_SERVER args = NONE env = NONE for OAD on host 127.0.0.1

Можно также ликвидировать сведения о сервере с помощью команды:

oadutil unreg [options]

Здесь

-i <interface name> - имя IDL-файла;

-r <repository id> - уникальный идентификатор интерфейса (например, 'IDL:serv/crb1Factory:1.0');

-o <object name>- имя серверного объекта, поддерживающего интерфейс;

-host <host name> - имя компьютера, где функционирует OAD (необязательный параметр);

После регистрации при попытке запустить клиентское приложение будет инициирован запуск сервера. Если нужно, чтобы сервер выгружался из памяти, когда он становится больше не нужен ни одному клиенту, Object Activation Daemon должен быть запущен с ключом -k (kill process).

<< | | >>


Теперь создадим новый проект - будущее клиентское приложение. Если он разрабатывается на том же компьютере, где создавался сервер приложений, можно добавить его к уже существующей группе проектов. В общем же случае клиентское приложение не обязано разрабатываться на том же компьютере, что и сервер (в общем случае сервер может вообще функционировать на платформе, отличной от Windows).

Создадим главную форму, поместим на нее компонент TCorbaConnection. Установим значения его свойств такими, как на рис. 6:



Рис. 6. Содержимое удаленного модуля данных

Здесь HostName - IP-адрес компьютера, на котором должен выполняться CORBA-сервер, ObjectName - имя удаленного модуля данных (CORBA-объекта; в общем случае их может быть в одном сервере несколько). Свойство RepositoryId - идентификатор конкретной реализации CORBA-объекта в формате <имя исполняемого файла>/<имя объекта>. Возможен и другой формат этой строки:

IDL:<имя исполняемого файла>/<имя объекта>:<версия>.

Теперь свойство Connected компонента CorbaConnection1 можно установить равным True (или произвести установку этого свойства равным True на этапе выполнения в момент создания формы клиентского приложения).

Теперь можно поместить на форму компонент TClientDataSet, установить значение его свойства RemoteServer равным CorbaConnection1, а затем поместить на форму компонент TDataSource и связать его с компонентом TClientDataSet.

Компонент TClientDataSet предназначен для хранения данных, полученных от сервера приложений, в кэше клиента, и, будучи потомком компонента TDataSet, обладает, подобно компонентам TTable и TQuery, как навигационными методами, так и методами, осуществляющими редактирование данных. Кроме того, этот компонент обладает методами SaveToFile и LoadFromFile, позволяющими сохранять данные из кэша в файле и восстанавливать их оттуда, реализуя так называемую "briefcase model" - модель обработки данных, основанную на том, что "тонкий" клиент осуществляет редактирование данных по большей части при отсутствии соединения с сервером, используя лишь кэш или локальные внешние устройства, и лишь иногда соединяется с сервером приложений для передачи ему измененных данных с целью дальнейшей обработки.



Добавим на форму компоненты TDBGrid, TDBImage, TDBNavigator и свяжем их с DataSource1. Свойство DataField компонента DBImage1 установим равным IMAGE. Добавим также две кнопки (рис. 7:).



Рис. 7. Интерфейсные элементы клиентского приложения

Теперь можно установить нужное значение свойства ProviderName компонента TClientDataSet, выбрав его из выпадающего списка объектов, экспортированных из удаленного модуля данных сервера приложений (так как сервер приложений выбран, все его доступные через интерфейс свойства становятся видны в среде разработки). Далее нужно установить значение свойства Active компонента TClientDataSet равными true (рис. 8).



Рис. 8. Главная форма клиентского приложения

Отметим, что если пользователь редактирует данные в клиентском приложении, они не переносятся непосредственно на сервер баз данных, а вместо этого кэшируются. Фактически компонент TClientDataSet осуществляет доступ к содержимому кэша и при необходимости инициирует добавление в него записей и перенос их обратно на сервер. Идея кэширования данных не нова: использовать его можно и в случае использования классической двухзвенной архитектуры (например, установив равным True свойство CachedUpdates компонента TTable или TQuery). Однако для "тонких" клиентов, связь которых с сервером приложений может осуществляться самыми разными способами, в том числе с помощью каналов, не обеспечивающих стабильную постоянную связь (например, с помощью модемного соединения по телефонной линии), этот способ редактирования данных является основным. На нем же основана так называемая briefcase model (модель работы с данными без постоянного соединения), когда пользователи лишь время от времени соединяются с сервером приложений для синхронизации данных, а подавляющую часть времени работают с кэшем и локальными внешними устройствами, периодически сохраняя и восстанавливая свои данные с помощью методов SaveToFile и LoadFromFile компонента TClientDataSet .

Отметим, что в Delphi 4 компонент TСlientDataSet может содержать кэшированные данные не только из одной, но и из нескольких связанных между собой на сервере приложений таблиц.


В нашем случае исходные таблицы CLIENTS и HOLDINGS связаны соотношением Master-Detail на сервере приложений. В результате этого в наборе данных, кэшируемом компонентом ClientDataSet, помимо полей, соответствующих реальным полям таблицы CLIENTS, имеется поле типа TDataSetField, реально соответствующее detail-записям таблицы HOLDINGS, связанным с текущей записью таблицы CLIENTS. Иными словами, компонент TClientDataSet эмулирует так называемые "вложенные" таблицы, характерные для некоторых объектно-ориентированных СУБД.

Для инициации процесса реального сохранения в базе данных используется метод ApplyUpdates компонента TClientDataSet. В качестве параметра этот метод использует целое число, равное максимально допустимому числу ошибок сервера, после которого следует прекратить попытки физического сохранения данных (значение -1 означает, что следует пытаться сохранить все данные независимо от числа ошибок).

Почему предусмотрена возможность ошибок сервера? Ответ очевиден - клиентское приложение может в общем случае ничего не знать о правилах ссылочной целостности и иных ограничениях, содержащихся на сервере баз данных, и только попытка физического сохранения изменений в базе данных может выявить их нарушения (отметим, что на клиентской рабочей станции может даже не быть библиотеки BDE).

Для сохранения данных из кэша на сервере баз данных создадим обработчики событий, связанных с нажатием на кнопки "Apply" и "Cancel":

procedure TForm1.Button1Click(Sender: TObject); begin ClientDataSet1.ApplyUpdates(-1);

end;

procedure TForm1.Button2Click(Sender: TObject); begin ClientDataSet1.CancelUpdates;

end;

Далее можно запустить и протестировать клиентское приложение. Обратите внимание: показания счетчика соединений на главной форме сервера приложений должны измениться, так как теперь у сервера приложений два клиента - среда разработки и запущенное приложение (рис. 9).



Рис. 9. "Тонкий" клиент на этапе выполнения

Если щелкнуть мышью на колонке Table2 компонента TDBGrid, появится дополнительная форма с detail-записями, соответствующими текущей master-записи.


Их также можно редактировать - при выполнении метода ApplyUpdates изменения в них будут сохранены.

При желании можно с помощью утилиты SQL Monitor, запущенной на том же компьютере, что и сервер приложений, проследить, какие именно запросы созданный нами сервер приложений направляет серверу баз данных.

Отметим, что для переноса клиентского приложения на другой компьютер следует вместе с исполняемым файлом скопировать библиотеку dbclient.dll либо в каталог Windows\System, либо в тот же каталог, где находится исполняемый файл. Библиотеку Borland Database Engine на этот компьютер устанавливать не нужно - "тонкий" клиент к ней не обращается.

CORBA-объект, также как и COM-объект, может обладать произвольным набором свойств и методов, которые можно добавить, редактируя библиотеку типов и код реализации. В частности, можно добавить и метод, осуществляющий доступ к базе данных с помощью пароля. Реализация его может, к примеру, иметь следующий вид:

procedure TMyRDM.Login(username, password: OleVariant); begin Database1.Connected:=false; Database1.params.Values['Password']:=password; Database1.params.Values['User Name']:=username; Database1.Connected:=true; Table1.Open; Table2.Open;

end;

Соответствующий клиентский код имеет в этом случае вид:

procedure Tform1.ButtonLoginClick(Sender: TObject); begin Form1.ClientDataSet1.Close; Form1.MIDASConnection1.AppServer.Login(Edit1.Text,Edit2.Text); Form1.ClientDataSet1.ProviderName:='Table1'; Form1.ClientDataSet1.Open;

end;

Отметим также, что создание CORBA-клиентов в виде активных форм возможно точно так же, как и создание COM-клиентов. Наиболее простым способом можно это сделать, выделив все интерфейсные элементы и сохранив их в виде шаблона (Component/Create ComponentTemplate), который позже можно поместить на вновь созданную активную форму. Далее следует в поставку ActiveX включить dbclient.dll, осуществить эту поставку на какой-нибудь web-сервер и попробовать протестировать полученный ActiveX (рис. 10).



Рис. 10. "Тонкий" клиент в виде активной формы на этапе выполнения

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

Отметим, что создание CORBA-серверов и клиентов с помощю C++Builder осуществляется неколько по-другому и напоминает скорее не создание COM-серверов и клиентов, а создание DCE-серверов и клиентов с помощью кодогенераторов Entera, то есть традиционный способ генерации клиентского и серверного stub-кода на основе IDL-описания интерфейсов сервера. Об этом будет более подробно рассказано в последующих статьях данного цикла.

<< | | >>



Для создания сервера приложений, предоставляющего


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

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



Рис. 1. Главная форма сервера доступа к данным

Можно разместить ее где-нибудь в углу экрана. Появление на экране главной формы в общем случае не является обязательным. Тем не менее, коль скоро она создана, поместим на нее компонент TLabel и установим значение его свойства Caption равным "0" (в дальнейшем в этой метке будет отображаться число подключенных к серверу клиентов).

Сразу же создадим связанную с этой формой процедуру изменения значения счетчика подключений:

uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls;

type TForm1 = class(TForm) Label1: TLabel; private FCount: Integer; { Private declarations } public procedure UpdateCount(Incr: Integer); { Public declarations } end;

var Form1: TForm1;

implementation

uses serv2;

{$R *.DFM}

{ TForm1 }

procedure TForm1.UpdateCount(Incr: Integer); begin FCount := FCount+Incr; Label1.Caption:= IntToStr(FCount) ; end;

end.

Далее из репозитария объектов (доступ к которому осуществляется выбором пункта меню File/New…) со страницы New следует выбрать объект CORBA Data Module (рис. 2).



Рис. 2. Выбор удаленного модуля данных из репозитария объектов

CORBA Data Module имеет принципиальное отличие от обычного модуля данных, заключающееся в том, что он представляет собой объект, обладающий CORBA-интерфейсами, доступными извне.

Создание CORBA Data Module начинается с запуска эксперта CORBA Data Module Wizard, в котором определяется, в частности, имя класса, под которым CORBA-объект - экземпляр данного класса - может быть опознан запрашивающими его приложениями (рис. 3):





Рис. 3. Выбор имени класса для удаленного модуля данных

Параметр Instancing указывает, сколько экземпляров данного класса может быть создано CORBA-сервером. Значение Instance-per-Client, используемое по умолчанию и выбранное нами в данном случае, означает, что один запущенный экземпляр сервера может создать несколько экземпляров удаленного модуля данных - по одному на каждого клиента. Значение Shared Instance означает, что один экземпляр удаленного модуля данных обслуживает несколько клиентов (это наиболее часто встречающаяся модель обработки данных в CORBA) . В этом случае следует создавать приложение так, чтобы модуль данных не хранил данные, связанные с конкретным клиентом (пример такого кода содержится в статье, посвященной созданию объектов Microsoft Transaction Server).

Параметр Threading Model указывает на то, поддерживает ли наш модуль данных многопоточность. Значение Single-threaded означает, что каждый экземпляр объекта обрабатывает в данный момент времени только один запрос клиента, поэтому обращение к данным, содержащимся в этом объекте (свойствам и др.) является безопасной операцией. Однако в этом случае следует предусматривать защиту от коллизий, связанных с модификацией глобальных переменных или объектов. Значение Multi-threaded означает, что каждое соединение, инициированное клиентом, обслуживается в отдельном потоке. В этом случае следует заботиться не только о глобальных переменных, но и о возможных коллизиях, связанных с одновременным обращением к данным объекта из разных потоков.

В данном случае можно оставить значение Single Threaded.

Далее в созданный удаленный модуль данных поместим два компонента TTable, свойство DatabaseName которых установим равным DBDEMOS, а имена равными CLIENTS.DBF (Table1) и HOLDINGS.DBF (Table2). Свойство Active этих компонентов TTable не обязательно устанавливать равным True (они будут автоматически открыты при обращении клиента к данному серверу приложений). Добавим также компонент TDataSourse и свяжем его с компонентом Table1 (рис. 4).





Рис. 4. Содержимое CORBA Data Module

В данном случае мы поместили компонент TDatabase на главную форму, а не в модуль данных. Это означает, что в этом случае все пользователи данного сервера будут использовать одно и то же соединение с базой данных и, соответственно, регистрироваться под именем одного и того же пользователя. Если такое решение неприемлемо, можно поместить его и в модуль данных. В этом случае туда же следует поместить и компонент TSession и позаботиться о разных именах пользовательских сессий во избежание конфликтов между ними.

Затем установим связь Master-Detail между таблицами (в данном случае по общему полю ACCT_NBR, для чего следует выбрать соответствующий индекс).

Далее из контекстного меню компонента Table1 нужно выбрать опцию:

Export Table1 from data module

Выбор этой опции приведет к тому, что в интерфейс, предоставляемый удаленным модулем данных, будут добавлены свойства и методы для работы с компонентами доступа к данным. В этом случае мы получим CORBA-объект, хранящий данные, связанные с конкретным клиентом, что вполне допустимо в случае, когда параметр Instancing имеет значение Instance-per-Client. В случае же, если этот параметр имеет значение Shared Instance, выбирать опцию экспорта из меню не следует, так как такой CORBA-объект не должен хранить никаких данных, связанных с конкретным клиентом (такие объекты называются stateless objects). В этом случае обычно создается метод, реализующий содинение с базой данных, открытие нужной таблицы, передачу данных клиенту, закрытие таблицы и разрыв соединения с базой данных (так называемый stateless code).

Проконтролировать наличие в интерфейсе удаленного модуля данных свойств и методов для доступа клиента к компонентам доступа к данным можно, просмотрев соответствующую библиотеку типов (рис. 5):



Рис. 5. Содержимое удаленного модуля данных

Находясь в редакторе библиотеки типов, нажмем кнопку экспорта ее в файл описания интерфейса CORBA IDL - этот файл нам пригодится в дальнейшем.

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



procedure Tcrb1.crb1Create(Sender: TObject); begin Form1.UpdateCount(1); end;

procedure Tcrb1.crb1Destroy(Sender: TObject); begin Form1.UpdateCount(-1); end;

Далее следует сохранить проект и скомпилировать приложение.

Отметим, что для того, чтобы клиентское приложение имело доступ к CORBA-серверу, недостаточно иметь запущенный экземпляр сервера, даже если разработка клиента производится на том же самом компьютере. CORBA, в отличие от COM, не делает различий между локальным и удаленным сервером; и в том, и в другом случае требуется запуск сервиса, предоставляющего доступ к серверу. В случае реализации CORBA, предложенной Visigenic, он называется Visibroker Smart Agent.

Можно легко объяснить, почему в случае CORBA не делается различий между локальным и удаленным сервером. COM есть не только технология распределенных вычислений, но и способ организации разделения функций между приложениями и библиотеками, составляющий в значительной степени основу самой операционной системы Windows. Естественно, что механизмы, осуществляющие поиск локальных COM-объектов и инициирование запуска COM-серверов, эти объекты реализующих, интегрированы в операционную систему и используют регистрационную базу данных самой операционной системы - реестр Windows - для поиска этих реализаций. Вообще говоря, и механизмы удаленного доступа к COM-серверам также содержатся в Windows NT и практически не отличаются от механизмов доступа к серверам локальным, просто они требуют соответствующих настроек, связанных главным образом с соображениями элементарной безопасности.

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

Итак, прежде чем приступить к разработке клиентского приложения, нужно обязательно запустить Smart Agent (он устанавливается одновременно с Delphi 4 Client/Server Suite) и лишь потом запустить скомпилированный сервер (желательно непосредственно из Windows).

| >>



Несколько слов о серверах доступа к данным


Серверы доступа к данным являются одним из наиболее популярных типов серверов middleware. Как создать переносимый сервер доступа к данным?

Наиболее популярный способ создания таких серверов - использование MIDAS и создание удаленных модулей данных как COM- или CORBA-объектов. В этом случае мы получим COM или CORBA-сервер, но это будет Windows-приложение, так как оно использует VCL, основанную на Windows API, и BDE, которая также представляет собой набор Windows-библиотек. Создание таких серверов более подробно будет рассмотрено в следующей статье данного цикла.

Если нам нужен переносимый сервер доступа к данным, он не должен использовать ни VCL, ни BDE. Как быть в этом случае?

Вспомним, что вся функциональность сервера доступа к данным - это набор SQL-запросов, и ничего более. Для MIDAS-серверов генератором запросов является BDE. Но выполнить запрос можно и другим способом! Например, всегда есть возможность использования низкоуровневого API клиентских частей серверных СУБД (например, Oracle Call Interface). Для большинства таких СУБД такие API существуют для многих платформ и содержат одни и те же функции. Использование таких API может показаться несколько утомительным, но в целом оно ненамного сложнее, чем написание других функций.

Российское представительство Inprise:

Тел. 7(095)238-36-11

e-mail:

http://www.inprise.ru



Постановка задачи


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

Пусть наше приложение имеет главную форму следующего вида (рис. 1):

Рис. 1. Интерфейс приложения, подлежащего разбиению на сервер и клиента

В нашем примере компонент TChart содержит одну серию (ее следует добавить вручную при создании формы).

Создадим обработчик события, связанный с нажатием на кнопку с надписью "График", и в этом же модуле создадим реализацию нашей функции:

//--------------------------------------------------------------------------- #include <vcl.h> #pragma hdrstop

#include "Unit1.h" //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1;

//--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //---------------------------------------------------------------------------

void __fastcall TForm1::BitBtn1Click(TObject *Sender) { int i; double x1,y; for (i=1;i<60;i++) { x1=0.1*float(i-13); y=fun1(x1); Chart1->Series[0]->AddXY(x1,y,FloatToStr(x1),clWhite); } }

double fun1(double x) { double r=x*x*x-5*x*x+3*x+5; return(r); }

//---------------------------------------------------------------------------

Результатом работы такого приложения будет график нашего полинома, появляющийся при нажатии на кнопку "График" (рис. 2):

Рис. 2. То же приложение на этапе выполнения

Это обычное Windows-приложение, содержащее в себе и пользовательский интерфейс, и функциональность, связанную с проведением расчетов (вычисление значений функции).

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



Создание клиента с графическим пользовательским интерфейсом


Для начала создадим клиентское приложение, представляющее собой обычное Windows-приложение с интерфейсом, похоржим на представленный на рис.2. Для этого выберем пиктограмму CORBA Client со страницы Multitier репозитария объектов C++Builder.

В этом примере мы будем использовать раннее связывание клиента с сервером (в этом случае можно достичь максимального быстродействия). Для этого выберем из меню среды разработки опцию "Edit/Use CORBA object". В соответствующем эксперте мы должны добавить к проекту тот же самый IDL-файл, что был создан при создании сервера, а также ответить на вопросы об именах объектов и переменных, в частности, об имени нового свойства формы, к которому следует обращаться, если нужно вызвать метод сервера (рис. 8):

Рис. 8. Определение свойств для доступа к CORBA-объекту в клиентском приложении

Далее можно создать интерфейс, сходный с приведенным на рис. 1, и создать обработчик события, связанного с нажатием на кнопку "График":

void __fastcall TForm1::BitBtn1Click(TObject *Sender) { int i; double x1,y; for (i=1;i<60;i++) { x1=0.1*float(i-13); y=b1_1->fun1(x1); Chart1->Series[0]->AddXY(x1,y,FloatToStr(x1),clWhite); }

}

В результате получим тот же график, что и на рис.2.



Создание переносимого клиентского приложения


Теперь создадим клиентское приложение, переносимое на другие платформы. Это должно быть консольное приложение, не использующее VCL. В этом случае при создании клиента мы должны выбрать опцию "Console Application" и отменить опцию "Enable VCL" .

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

//---------------------------------------------------------------------------

#include <corbapch.h> #pragma hdrstop

//--------------------------------------------------------------------------- #include "fun1_c.hh"

#include <corba.h> #include <condefs.h> USEIDL("corba\corba_rus\fun1.idl"); USEUNIT("corba\corba_rus\fun1_c.cpp"); USEUNIT("corba\corba_rus\fun1_s.cpp"); //--------------------------------------------------------------------------- #pragma argsused int main(int argc, char* argv[]) { try { // Initialize the ORB and BOA CORBA::ORB_var orb = CORBA::ORB_init(argc, argv); CORBA::BOA_var boa = orb->BOA_init(argc, argv); a1_var a1_1 = a1::_bind("a1Obj");

cout<<"Our function table \n"; int i; double x1,y; for (i=1;i<271;i++) { x1=0.1*float(i); y=a1_1->fun1(x1); cout<<x1<<" "<<y<<"\n"; }

} catch(const CORBA::Exception& e) { cerr << e << endl; return(1); } return 0; } //---------------------------------------------------------------------------

В этом случае мы получим таблицу с результатами расчетов непосредственно на экране консольного приложения.

Отметим, что данное приложение можно скомпилировать любым компилятором С++, в том числе и компилятором для платформы, отличной от Windows.



Создание переносимого сервера функциональности


Итак, создадим CORBA-сервер, осуществляющий проведение расчетов. Сразу же заметим, что подобного рода серверы, как правило, не нуждаются в пользовательском интерфейсе. Поэтому мы вполне можем создать его как консольное приложение. Если при этом мы откажемся от использования VCL, код полученного сервера может быть скомпилирован любым другим компилятором C++ (в том числе, естественно, и компилятором для другой платформы). Консольные приложения есть единственный тип приложений, существующий для любых платформ, в отличие от приложений с графическим интерфейсом пользователя - последние требуют графических библиотек или операционных систем с GUI, и на все эти графические библиотеки и функции API подобных операционных систем нет никаких стандартов, в отличие от самого языка С++.

Иными словами, сервер может быть создан переносимым на другие платформы, что и будет сделано.

Для создания сервера выберем из репозитария объектов со страницы Multitier пиктограмму CORBA server (рис. 3):

Рис. 3. Выбор пиктограммы CORBA Server из репозитария объектов

После выбора пиктограммы в диалоговой панели CORBA Server Wizard выберем в качетсве типа приложения Console Application и отключим возможность использования VCL. Это позволит нам создать переносимый на другие платформы код (fig. 4):

Рис. 4. CORBA Server Wizard

Первое, с чего следует начинать создание распределенной системы, - это описание интерфейсов сервера. С этой целью используется язык IDL (Interface Definition Language), являющийся, по существу, стандартом для подобного рода описаний, не зависящим от языков программирования и платформ. Отметим, что существует несколько диалектов IDL (COM IDL, CORBA IDL, DCE IDL), имеющих некоторые различия. Естественно, мы будем использовать CORBA IDL. Так как мы еще не создали никакого IDL-описания, выберем опцию Add New IDL File. После нажатия на кнопку OK в окне редактора кода появится пустая страница для ILD-описания, куда можно ввести описание интерфейса нашего сервера. interface b1 { double fun1(in double x); } ;


Далее следует скомпилировать IDL-файл. В действительности это не компиляция, а кодогенерация, в результате которой мы получим два модуля, fun1_s.cpp and fun1_c.cpp, содержащие stub-код и skeleton-код.

Что представляют собой stub-объект и skeleton-объект?

Все способы взаимодействия между серверами функциональности и их клиентами основаны на механизме вызовов удаленных процедур (RPC - Remote Procedure Calls) и маршалинга, представляющего солой обмен пакетами данных между объектом внутри клиента (stub-объектом) и объектом внутри сервера (skeleton-объектом). Skeleton-объект является представителем клиента внутри адресного пространства сервера. Обращаясь к нему, сервер "думает", что он имеет дело с локальным объектом. Stub-объект является представителем сервера внутри адресного пространства клиентского приложения, поэтому последнее также "думает", что вызывает методы локального объекта. В действительности так оно и есть, однако вместо истинной реализации (в нашем случае - вычисления значений функции) методы этого объекта осуществляют вызовы удаленных процедур и обмен пакетами данных с сервером (рис. 5):



Рис. 5. Взаимодействие сервера и клиента

Теперь можно вспомнить о реализации нашей функции. Для этого выберем со страницы Multitier репозитария объектов пиктограмму CORBA Object Implementation. В диалоговой панели CORBA Object Implementation Wizard выберем имя интерфейса (b1), а также определим имя модуля, содержащего реализацию, и имя класса CORBA-объектов. В данном примере мы описшем объект, экземпляр которого создается в момент старта сервера, так что сервер сразу же сможет принимать обращения клиентов (рис. 6):



Рис. 6. CORBA Object Implementation Wizard

Мы можем просмотреть изменения, внесенные в наше приложение этим экспертом. В частности, имеет смысл внести в код реализации нашего метода строки, ответственные за проведение расчетов (рис. 7):



Рис. 7. Добавление кода рализации функции в сгенерированный модуль

Теперь можно скомпилировать проект и закрыть его.

Перед созданием клиента мы должны запустить VisiBroker Smart Agent - службу каталогов CORBA, позволяющую осуществить доступ к CORBA-серверам. Теперь можно запустить наш сервер (желательно отдельно от среды разработки).

Теперь можно создавать клиенские приложения.


Из чего состоит информационная система


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

Если рассматривать многопользовательскую работу с корпоративными данными в сети, основанную на применении какой-либо СУБД (в данном случае неважно, сетевой или серверной), можно заметить, что она подчиняется некоторым общим правилам и состоит, как правило, из стандартного набора программных компонентов и сервисов.

Наиболее важным из таких компонентов является собственно база данных, то есть набор файлов, содержащих данные компании. Этот набор файлов может обслуживаться сервисом, называемым сервером баз данных, если СУБД серверная, или файловыми сервисами операционной системы того компьютера, на котором эти файлы расположены, если СУБД не является серверной.

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

Еще один компонент, без которого работа сетевой информационной системы невозможна - это средства обеспечения доступности данных из СУБД в пользовательском приложении.
Набор этих средств существенно зависит от того, является ли СУБД серверной. Как минимум, во всех случаях он включает средства сетевого доступа, базирующиеся на сетевых средствах операционных систем, используемых для эксплуатации СУБД и пользовательских приложений. Сетевые средства операционных систем включают, как минимум, поддержку сетевых протоколов, обеспечивающих этот доступ.

В случае серверных СУБД к этому набору добавляются средства взаимодействия пользовательского приложения и сервера баз данных, использующие ту же самую поддержку сетевых протоколов операционными системами. Эти средства обычно включают клиентскую часть серверной СУБД, содержащую, как правило, низкоуровневое API (Application Program Interface - прикладной программный интерфейс) взаимодействия с сервером баз данных. Помимо этого, средства обеспечения доступности данных нередко содержат библиотеки, содержащие высокоуровневые функции доступа к данным. Эти функции упрощают использование клиентской части, если СУБД серверная, либо реализуют стандартные операции с данными, если СУБД не является серверной. В случае пользовательских приложений, созданных с помощью средств разработки Inprise, это библиотека Borland Database Engine (BDE) и драйверы SQL Links, в случае использования средств разработки Microsoft и многих других производителей (а иногда и средств разработки Inprise) - ODBC-драйверы.


Как устроено взаимодействие клиентов и серверов


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

В настоящее время существует немало способов реализации подобного взаимодействия.. Отметим, однако, что все эти способы базируются на одной и той же придуманной много лет назад идее осуществления вызовов удаленных процедур путем передачи данных между объектами внутри клиента и внутри сервера (рис.5).

Рис. 5. Осуществление вызовов удаленных процедур

При обращении клиента к удаленному серверу функциональности внутри адресного пространства сервера создается так называемый серверный stub-объект (в терминологии CORBA он называется skeleton, в терминологии COM - stub; некоторые авторы переводят этот термин как "заглушка"), являющийся представителем данного клиента в адресном пространстве сервера (при многопользовательском доступе к одному и тому же экземпляру сервера этих объектов создается несколько).

Внутри адресного пространства клиента создается клиентский stub-объект (в терминологии CORBA он называется stub, в терминологии COM - proxy; иногда этот термин переводят как "заместитель"). Он может обладать таким же списком методов, как соответствующий ему серверный stub-объект, но никогда не содержит их истинной реализации (в лучшем случае вместо истинной реализации могут присутствовать функции API из библиотеки, реализующей вызовы удаленных процедур и передачу данных с помощью того или иного сетевого протокола, как, например, в случае stub-кода, сгенерированного утилитами из комплекта поставки Inprise Entera) Эти два stub-объекта общаются между собой, обмениваясь данными примерно так же, как взаимодействуют между собой сетевые приложения и сервисы.. Из передаваемых данных формируются так называемые пакеты данных, которые с помощью сетевых протоколов передаются другому stub-объекту (этот процесс иногда называется маршалингом или маршрутизацией), где и расшифровываются.


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

Многие инструменты для создания серверов функциональности содержат в комплекте поставки утилиты для автоматической генерации stub-кода. Код генерируется на основании описаний интерфейса сервера. Во многих случаях эти описания создаются на языке IDL (Interface Definition Language).

Отметим, что существует несколько диалектов IDL (для COM, для CORBA и др.). Тем не менее различия между ними невелики. Delphi 4 поддерживает как COM IDL, так и CORBA IDL.

Сервер приложений Inprise Entera содержит такой кодогенератор для большого количества языков программирования; существует компилятор MIDL для генерации stub-кода и proxy-кода на C++ на основе COM IDL (он активно используется при создании серверов и клиентов с помощью Microsoft Visual C++). В случае CORBA также имеются кодогенераторы для С++ и Java. При создании серверов функциональности, реализованных в виде серверов автоматизации, c помощью Delphi и C++Builder, stub-код генерируется автоматически при создании соответствующих классов, и создавать описания с помощью IDL необходимости нет. Однако в этом случае всегда можно сгенерировать описание на IDL (как для COM, так и для CORBA) на основании созданной библиотеки типов сервера.

Зачем нужен IDL? Фактически это стандарт, позволяющий описывать вызываемые методы сервера и их параметры, не вдаваясь в детали и правила реализации серверов и клиентов на том или ином языке программирования. Используя IDL, можно описать интерфейсы сервера, а затем создать его реализацию на любом языке программирования с помощью широкого спектра средств разработки для широкого спектра платформ, равно как и реализацию клиента.В определенном смысле IDL - это стандарт для описания взаимодействия между компонентами распределенной системы, не зависящий от деталей реализации, языков программирования и платформ.


Какими бывают сервисы промежуточного слоя и серверы приложений


Одним из наиболее модных на сегодняшний день типов серверов приложений являются серверы доступа к данным (Data Access Server), реализуемые, как правило, в виде приложений (реже - в виде библиотек). Они содержат функциональность, связанную с доступом к данным (а нередко и какую-либо иную функциональность, например, статистическую обработку этих данных или генерацию отчетов). Как правило, такие приложения-серверы сами являются клиентами серверных СУБД. В любом случае такие серверы используют перечисленные выше библиотеки доступа к данным. При грамотной организации разделения функций между пользовательским приложением и сервером доступа к данным конфигурация программного обеспечения рабочей станции сводится к информированию пользовательского приложения о том, как называется (или идентифицируется иным способом) нужный ему сервис и на каком компьютере сети должен находиться либо непосредственно он сам, либо некий сервис-посредник, чья задача заключается в поиске нужного сервиса для данного клиента (рис. 2).

Рис. 2. Информационная система с сервером доступа к данным

К этой же категории сервисов можно отнести так называемые мониторы транзакций. Мониторы транзакций, как правило, применяются в информационных системах, использующих распределенное хранение данных (то есть хранение данных в нескольких базах данных, возможно, в разных СУБД и на физически различных компьютерах) и содержащих приложения, использующие одновременный доступ к нескольким базам данных, включающий так называемые распределенные транзакции (то есть модификацию данных в нескольких различных БД, рассматриваемую как единое целое и либо выполняющуюся целиком, либо целиком отменяемую). Если обычные транзакции внутри одной базы данных с успехом поддерживаются серверными СУБД, лишь бы были реализованы в виде объектов базы данных (индексов, триггеров, хранимых процедур, серверных ограничений и др.) соответствующие правила их выполнения, то распределенные транзакции требуют отдельной поддержки за пределами СУБД.
Именно эта поддержка и реализуется в мониторах транзакций (рис.3).



Рис. 3. Информационная система с монитором транзакций

Отметим, однако, что в виде отдельного сервиса может быть реализован не только доступ к данным, но и любая другая функциональность пользовательского приложения, например, многомерный анализ и статистическая обработка данных, генерация и печать отчетов, проведение расчетов, обеспечение шифрования данных и многое другое. В этом случае говорят о серверах функциональности (functionality server). Отметим, что сервер функциональности - более общее понятие, чем сервер доступа к данным; последний есть лишь частный случай сервера функциональности. Один сервер функциональности может в общем случае предоставлять несколько сервисов.

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


Регистрационные базы данных и идентификация серверов и сервисов


Каким образом можно инициировать запуск сервера по запросу клиента? Если реализация сервера одна, и клиенту известно, как ее идентифицировать и на каком компьютере сети ее искать, он обращается к сервису, ответственному за предоставление доступа к данной реализации (Service Control Manager). В случае получения разрешения этот сервис обращается к другому сервису, ответственному за запуск сервера функциональности (как было сказано выше, оба сервиса могут и часто бывают реализованы в одном приложении). Этим другим сервисом либо запускается соответствующее приложение (если сервер еще не запущен, или если каждому клиенту нужен свой экземпляр сервера), либо внутри уже запущенного сервера создаются объекты, взаимодействующие с данным клиентом.

Если реализаций сервиса несколько, удаленный запуск одной из них по запросу клиента может быть осуществлен в том случае, если сведения о местоположении этих реализаций доступны либо самому клиенту, либо сервису, который ищет реализацию по его запросу. В простейшем случае список возможных реализаций просто содержится в клиентском приложении. Например, компонент SimpleObjectBroker в Delphi 4 как раз содержит такой список; клиентское приложение, содержащее такой компонент, будет при каждом запуске случайным образом подключаться к одной из реализаций, указанной в списке. Однако в общем случае, особенно когда для поиска реализации используются специализированные сервисы, такой список хранится отдельно от клиентского приложения.

Естественно, для этого в системе должна существовать некая специализированная база данных, в которой содержатся сведения о сервисах и серверах, содержащих их реализацию. Общей спецификации, которой могло бы подчиняться создание такой базы данных, не существует, так как в общем случае такая база данных может содержаться на любой платформе. В случае COM и его расширений (таких как OLEnterprise) роль такой базы данных с успехом выполняет реестр Windows. В случае CORBA эти сведения хранятся в двух репозитариях (в одном регистрируются интерфейсы сервера, в другом - их реализации, то есть конкретные приложения-серверы).
Inprise AppCenter использует свою собственную базу данных (ее реализация существует для нескольких платформ), при этом она управляется специальным приложеним, регистрирующим обращения к ней других сервисов.

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

Всегда ли необходима регистрация серверов и сервисов? Это зависит от конкретной реализации способа удаленного доступа. COM-сервер автоматически регистрирует себя в реестре Windows после первого запуска, но этот первый запуск должен быть осуществлен не удаленно, а локально. При этом запись о COM-сервере обязана содержаться в реестре компьютера, содержащего сервер. При использовании DCOM и OLEnterprise удаленный сервер функциональности должен быть зарегистрирован также и в реестре клиентской рабочей станции, при этом использование DCOM предполагает для регистрации сервера на рабочей станции прото запустить его там. OLEnterprise, в отличие от Microsoft DCOM, предоставляет средства для импорта записи о сервере функциональности из реестра компьютера, содержащего сервер, в реестры рабочих станций. При использовании же Borland Socket Server сведения о сервере обязаны содержаться в клиентском приложении, а регистрация сервера на рабочей станции не требуется.

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

Серверы Entera 3.2 (равно как и другие приложения) можно зарегистрировать в базе данных Inprise AppCenter, а можно не регистрировать нигде.


В этом случае сервер Entera 3.2 может быть найден сервисами и клиентами только в том случае, если он уже запущен.

В базе данных Inprise AppCenter можно регистрировать самые разнообразные сервисы, серверы и приложения, а также создавать тк называемые конфигурации приложений, описывая правила, которым подчиняется их запуск и остановка. Эти правила могут быть весьма сложными, так как ApCenter, по существу, представляет собой средство управления серверами и сервисами в распределенных системах.

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

Как идентифицируются серверы и содержащиеся в них сервисы? Общих правил на все случаи жизни на этот счет не существует. Однако сейчас довольно часто принято присваивать серверам и сервисам уникальные идентификаторы UUID (Universal Unique Identifier), представляющие собой 128-разрядные значения, сгенерированные с помощью алгоритма, определенного OSF (Open System Foundation), на основе IP-адреса компьютера и иных его характеристик, и гарантирующего с высокой вероятностью уникальность сгенерированного значения. Реализация этого алгоритма содержится, например, в функции Windows API CoCreateGUID. В случае COM эти идентификаторы называются GUID (Global Unique Identifier), IID (Interface Identifier), CLSID (Class Identifier). Именно по этим идентификаторам обычно клиенты и другие сервисы в большинстве случаев ищут реализацию нужного сервера (или предоставляемого им сервиса). Тем не менее в некоторых реализациях можно осуществлять поиск и по другим признакам (имя приложения, имя сервиса и др.). Бывают случаи, когда UUID генерируется с целью соответствия стандартам, но в действительности не используется (например, при использовании серверов Entera 3.2 и доступа к ним непосредственно с помощью протокола TCP/IP).


Специализированные сервисы


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

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

Наиболее распространенными из специализированных сервисов являются сервисы, позволяющие на определенных условиях определенным пользователям получить доступ к тому или иному серверу функциональности, содержащемуся на компьютере, где функционирует данный специализированный сервис. Иногда такой сервис может быть выполнен в виде приложения, иногда - в виде сервиса операционной системы. В случае реализации спецификации в продукте Inprise Visibroker он называется Object Activation Daemon, в случае использования доступа к COM-серверам с помощью Inprise OLEnterprise - Object Factory, в случае использования доступа к COM-серверам с помощью протокола TCP/IP - Borland Socket Server, в случае Inprise AppCenter - AppCenter Agent. В случае использования соответствующего сервиса Microsoft DCOM по отношению к нему и другим подобным сервисам иногда употребляется термин Service Control Manager (см., например, Brockschmidt K.
What OLE is really about, www.microsoft.com/oledev/olecom/aboutole.html).

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

Помимо предоставления доступа к серверу функциональности на приложение, содержащее такой сервис, могут возлагаться и другие обязанности (например, запустить сервер функциональности, как это делает Object Activation Daemon, или передавать и принимать данные, как это делает Borland Socket Server).

Еще один распространенный (но в целом не являющийся обязательным) тип специализированных сервисов - это сервисы, занимающиеся поиском серверов функциональности для обратившихся к ним клиентов и выступающие в качестве посредника между клиентом, нуждающимся в том или ином сервисе, и поставщиком сервиса (в данном случае сервером функциональности), сводя их между собой. Иногда такие сервисы называются общим термином Directory Service. Обычно такие сервисы используются в системах, содержащих несколько одинаковых серверов функциональности, и подключают обратившихся к ним клиентов к этим серверам в соответствии с установленными для этой системы правилами (например, случайным образом, чем достигается баланс загрузки серверов клиентскими приложениями; или, например, к какому-либо конкретному серверу, а в случае сбоя - к другому, считающемуся резервным; правила выбора сервера для обратившегося клиента могут быть самыми разнообразными).



Рис. 4. Система, использующая Directory Service

В случае использования Microsoft DCOM наличие такого сервиса не предполагается, равно как и в случае использования Borland Socket Server. В случае расширения COM с помощью Inprise OLEnterprise этот сервис (в данной реализации он называется Business Object Broker) может как использоваться, так и не использоваться.


В случае Inprise Entera 3. 2 этот сервис используется обязательно (в данной реализации он называется Entera Broker). В случае Inprise AppCenter использование подобного сервиса также обязательно (в этом случае он называется AppCenter Broker, при этом Entera Broker для него может являться сервером функциональности, который следует искать). А вот в спецификации CORBA и ее реализациях этот сервис называется Object Agent.

Отметим, однако, что в некоторых источниках термин Broker нередко означает просто некую транспортную службу, обеспечивающую передачу серверу запросов клиента и обмен данными между ними. При этом такая служба может быть реализована внутри какого-либо служебного приложения (как, например, это сделано в Borland Socket Server), а может фактически содержаться внутри самого сервера функциональности и использующего его клиента (так, например, устроены клиенты и серверы Entera; при этом сам термин broker в Entera означает Directory Service). В случае же CORBA этот термин обозначает скорее концепцию, нежели конкретный подлежащий реализации сервис.


Типичные проблемы эксплуатации информационных систем и способы их решения


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

Рис. 1. Классическая информационная система

Следует также отметить, что подобное программное обеспечение требует обычно проведения работ по его настройке и поддержанию этих настроек в рабочем состоянии. Так, пользовательское приложение должно, как минимум, "знать" о том, где расположены используемые им данные, какого они типа (имеется в виду тип серверной СУБД либо формат данных сетевой СУБД), с помощью какого сетевого протокола они доступны, каков поддерживаемый базой данных язык, определяющий порядок алфавитной сортировки и индексирования данных. Подобная работа нередко является весьма трудоемким процессом, особенно при большом количестве и неоднородном парке рабочих станций. Отметим, что далеко не все компоненты подобного программного обеспечения могут быть включены в состав дистрибутива пользовательского приложения, так как многие из них являются предметом лицензирования и продажи.

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

Имеется еще один фактор, напрямую связанный с тем, что многие средства разработки используют одни и те же стандартные библиотеки доступа к данным (в случае Windows это BDE и ODBC).
На сегодняшний день как на российском, так и на мировом рынке имеется немалое количество различных программных продуктов, содержащих какие-либо данные (в особенности энциклопедий и справочников), при установке которых устанавливаются и эти библиотеки (подобные действия разрешены при определенных условиях производителями этих библиотек). В этом случае нет никакой гарантии, что версия любой из подобных библиотек, входящая в комплект поставки такого продукта, окажется новее, чем уже используемая в корпоративной информационной системе, и что программа установки не внесет изменений в настройки, сопровождающие эту библиотеку, таким образом, что работоспособность уже установленной информационной системы окажется нарушенной. Это, конечно, противоречит правилам создания дистрибутивов, но такие случаи время от времени случаются даже с неплохими коммерческими продуктами.

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



Итак, используя стандартные архитектуры создания многопользовательских информационных систем, можно столкнуться как минимум с двумя серьезными проблемами, требующими материальных затрат: все более повышающимися требованиями к аппаратному обеспечению рабочих станций и обеспечением поддержки настроек доступа к данным. Отметим, что список возможных проблем этим не исчерпывается. Мы не рассматривали проблемы, связанные с перегрузкой сети при росте объема данных (частично они могут быть решены при замене сетевых СУБД на серверные, но далеко не всегда такой переход полностью решает эти проблемы), проблемы, связанные с эксплуатацией всеми пользователями каких-либо общих ресурсов (например, высококачественного многопроцессорного сервера, способного быстро обрабатывать данные по сравнению с пользовательскими рабочими станциями), а также проблемы, возникающие при территориальной разбросанности предприятия или низком качестве линий связи.

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

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

Такие сервисы, как правило, являются сервисами промежуточного слоя (middleware services), поскольку занимают промежуточный уровень между данными и сервисами, их обслуживающими, с одной стороны, и пользовательскими приложениями, ориентированными на конкретную предметную область, с другой стороны. Эти сервисы обычно обладают минимальным пользовательским интерфейсом или не имеют его вовсе.


Нередко они могут быть реализованы для нескольких различных платформ, так как являются сервисами более высокого уровня, чем сервисы, специфичные для данной операционной системы или СУБД. Такие сервисы могут быть реализованы внутри приложений или библиотек (такие приложения или библиотеки обычно называются серверами приложений - Application Server), а также в виде служб операционных систем. Пользовательские приложения, использующие сервисы промежуточного слоя, обычно называются клиентами.

Технологии, используемые для реализации таких сервисов, могут быть различными. В частности, их реализация может использовать технологию и стандарты DCE (Distributed Computing Environment), разработанные OSF (Open Software Foundation), как это сделано в Inprise Entera. Можно реализовать такие сервисы с использованием спецификации CORBA (Common Object Request Broker Architecture), разработанной консорциумом OMG (Object Management Group). В обоих случаях набор возможных клиентских и серверных платформ весьма широк и отнюдь не ограничивается различными версиями Windows. Если же речь идет об относительно недорогих решениях на основе Windows, вполне допустимо использовать DCOM (Distributed Component Object Model) либо различные расширения COM (например, технологию Inprise MIDAS) и реализовывать сервисы middleware внутри серверов автоматизации или компонентов Microsoft Transaction Server (о нем пойдет речь в следующей статье данного цикла).