Распределенные вычисления и технологии 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 предоставляет средства мониторинга объектов и транзакций, что упрощает их реализацию.
>>
Создание клиентского приложения, использующего распределенные транзакции
Для тестирования созданного ранее сервера и инициации распределенных транзакций создадим клиентское приложение, имитирующее процесс оформления заказов. На главной форме приложения поместим кнопку с надписью "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.
<< |
Создание клиентского приложения
Зарегистрировав созданный серверный объект в 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. Тестирование серверного объекта
<< | | >>
Создание серверного объекта
Для создания серверного объекта следует со страницы 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 должен быть следующим:После запуска утилиты просмотра на экране появится диалог ввода имени пользователя и пароля. Далее 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
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
#include
#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 обычно имеется три звена:В некоторых случаях эти три звена могут быть представлены в виде трех отдельных приложений, однако в общем случае каждое звено может состоять из нескольких отдельных приложений, что позволяет говорить о многозвенной архитектуре.
Наряду с версией 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-серверов и клиентов следует соблюдать следующие правила:Помимо этого, следует позаботиться о следующих переменных окружения:
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
-r
-o