Разработка распределенных приложений в Microsoft.NET Framework

         

Балансировка нагрузки


COM+ поддерживает динамическую балансировку нагрузки. Для этого необходимо создать кластер COM+ на базе серверной версии операционной системы Microsoft Windows (Windows 2000 Advanced Server и последующих). Кластер COM+ состоит из нескольких серверов и распределяющим между ними запросы маршрутизатором COM+. Для повышения надежность такой системы могут применяться способы быстрой замены маршрутизатора при его выходе из строя на базе Windows Clustering Services. Таким образом, среда COM+ позволяет при наличии необходимости достаточных финансовых ресурсов создать хорошо масштабируемую систему без уникальной точки сбоя.



Использование исключений в обслуживаемых компонентах


Использование исключений в распределенных системах имеет свои особенности. Если исключение выбрасывается в обслуживаемой компоненте, то оно в общем случае может выйти за границы домена приложения компоненты, то есть подвергнуться сериализации и десериализации в рамках среды .NET Remoting с использованием класса BinaryFormatter. Поэтому все исключения, используемые обслуживаемыми компонентами, должны иметь возможность сериализации наследниками System.Runtime.Serialization.Formatter. Все исключения из библиотеки FCL, наследованные от System.ApplicationException и System.SystemException, имеют такую возможность. Ниже приведен пример создания собственного сериализуемого исключения. Класс исключения имеет конструктор с параметрами, описанными в теме об использовании форматеров SoapFormatter и BinaryFormatter. К счастью, разработчику достаточно вызвать аналогичный конструктор базового класса System.SystemException, который реализует все необходимые действия.

// Файл ComException.cs using System; using System.EnterpriseServices; using System.Runtime.Serialization; [assembly: ApplicationActivation(ActivationOption.Server)] [assembly: ApplicationAccessControl(false)] [Serializable] public class CustomException: System.ApplicationException { public CustomException(SerializationInfo info, StreamingContext context): base(info, context) { } public CustomException(string message): base(message) { } } public class ComSample: ServicedComponent { public ComSample() { } public void Process() { throw new CustomException("случилась неприятность"); } } class MainApp { static public void Main() { ComSample com = new ComSample(); try { com.Process(); } catch(CustomException e) { Console.WriteLine("Исключение: {0}", e.Message); } finally { com.Dispose(); } } } // Файл ComException.cs

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

sn -k ComException.snk csc ComException.cs /keyfile:ComException.snk /r:System.EnterpriseServices.dll regsvcs ComException.exe

Использование слабо связанных событий




Для использования слабо связанных событий необходимо (рис. 6.7):

создать интерфейс, описывающий методы события; создать так называемый класс события (event class), который является особой обслуживаемой компонентой, реализующей указанный интерфейс; создать реализующую этот же интерфейс обслуживаемую компоненту и подписать ее на событие; таких сервисных компонент можно создать несколько и они могут входить в состав различных приложений COM+;написать в издателе код создания события, заключающийся в создании экземпляра класса события и вызова его методов.


Рис. 6.7.  Использование слабо связанных событий

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

// LceEvents.cs using System; using System.Runtime.InteropServices; using System.EnterpriseServices; public static class EventsGuids { public const string interfaceId = "A5105B2C-40BF-46C6-B19C-4286A423DBF9"; public const string eventClassId = "DF64D391-CCE9-4FC6-B5F2-3F4DE3FA48C2"; } [Guid(EventsGuids.interfaceId)] public interface ILceMessage { void TriggerEvent(string message); } // LceEvents.cs

Далее описано приложение, являющееся издателем события.

// LcePublisher.cs using System; using System.Threading; using System.Runtime.InteropServices; using System.EnterpriseServices; [assembly: ApplicationName("LCE publisher")] [assembly: ApplicationActivation(ActivationOption.Server)] [assembly: ApplicationAccessControl(false)]

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

[EventClass] [Guid(EventsGuids.eventClassId)] [Transaction(TransactionOption.Disabled)] public class LceEvents: ServicedComponent, ILceMessage { public void TriggerEvent(string message) { } }

Издатель события может быть как обслуживаемой компонентой, так и не быть связанным с контекстом COM+.
Следует учитывать, что при отсутствии у события подписчиков будет выброшено исключение COM+.

public class LcePublisher: ServicedComponent { public LcePublisher() { } public void DoWork() { ILceMessage lcEvent = (ILceMessage) new LceEvents(); try { while (true) { lcEvent.TriggerEvent("Событие!"); Thread.Sleep(1000); } } catch (System.Runtime.InteropServices.COMException) { Console.WriteLine("Нет подписчиков"); } } } class MainApp { public static void Main() { LcePublisher com = new LcePublisher(); com.DoWork(); } } // LcePublisher.cs Подписчик события является компонентой COM+, реализующей интерфейс события. Метод данного интерфейса вызывается при публикации события. В качестве примера при обработке события подписчик проверяет наличие частной очереди сообщений и посылает в нее сообщение, после чего сообщает об успехе транзакции. Для простоты примера имя очереди указано как константа, реальные приложения должны хранить его в файле конфигурации.

// LceSubscriber.cs using System; using System.IO; using System.Threading; using System.Runtime.InteropServices; using System.EnterpriseServices; using System.Messaging; using Seva.ComUtils; using Seva.Msmq; [assembly: ApplicationName("LCE demo")] [assembly: ApplicationActivation(ActivationOption.Server)] [assembly: ApplicationAccessControl(false)] public static class Consts { public const string testQueue = @".\Private$\sample_queue"; } [JustInTimeActivation] [Transaction(TransactionOption.Required)] public class LceSubscriber : ServicedComponent, ILceMessage { public LceSubscriber() { } В качестве примера при обработке события подписчик проверяет наличие частной очереди сообщений и посылает в нее сообщение, после чего информирует координатор транзакций об успехе транзакции.

public void TriggerEvent(string message) { MessageQueue queue = MsmqTools.CreateQueue(Consts.testQueue); queue.Send(message, MessageQueueTransactionType.Automatic); ContextUtil.SetComplete(); } } class MainApp { public static void Main() { // Для ленивой регистрации приложения создается // служебная компонента LceSubscriber subscriber = new LceSubscriber(); // Создание постоянной подписки LceUtils.PermanentSubscription(Consts.comAppName, typeof(LceSubscriber).ToString(), EventsGuids.eventClassId, EventsGuids.interfaceId); } } // LceSubscriber.cs Make-файл для создания подписчика и издателя.

all: LceSubscriber.exe LcePublisher.exe LceSubscriber.snk: sn -k LceSubscriber.snk LcePublisher.snk: sn -k LcePublisher.snk LceSubscriber.exe: Seva*.cs LceEvents.cs LceSubscriber.cs LceSubscriber.snk csc /out:LceSubscriber.exe Seva*.cs LceEvents.cs LceSubscriber.cs /r:interop.comadmin.dll /keyfile:LceSubscriber.snk LcePublisher.exe: Seva*.cs LceEvents.cs LcePublisher.cs LcePublisher.snk csc /out:LcePublisher.exe Seva*.cs LceEvents.cs LcePublisher.cs /r:interop.comadmin.dll /keyfile:LcePublisher.snk

Just-in-time активация и пул объектов


Среда COM+ поддерживает два вида активации объектов – активация по требованию клиента и активация на время единственного вызова (называемая в среде COM+ JIT активацией) с возможностью использования пула объектов. Поскольку распределенные транзакции поддерживаются только для второго типа активации, в дальнейшем будет рассматриваться только он.



Обеспечение безопасности


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

Роль безопасности – это категории пользователей приложения COM+, имеющих определенные права на доступ к компонентам данного приложения, их интерфейсам и методам. Разработчик компоненты COM+ задает роли как некоторые символьные значения и определяет уровни доступа для всех использующихся ролей. При разворачивании приложения ролям ставятся в соответствие учетные записи пользователей и групп Microsoft Windows. Следует отметить, что роли COM+ не связаны напрямую с ролями CLI, поскольку COM+ существует независимо от .NET Framework.

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



Ожидающие компоненты


Хотя среда MSMQ может использоваться в рамках транзакции COM+, это приводит к одновременному использованию двух технологий удаленного взаимодействия. Вероятно часто было бы удобно скрывать использование MSMQ путем создания компонент COM+, поддерживающих асинхронные коммуникации. Поэтому для реализации асинхронного удаленного вызова в промежуточной среде COM+ существуют так называемые ожидающие компоненты (queued componenets), которые прозрачно используют MSMQ. Использование такой компоненты подобно асинхронному удаленному вызову (рис. 6.4).


Рис. 6.4.  Использование ожидающих компонент COM+

При начале использования ожидающей компоненты на стороне клиента создается посредник, называемый протоколистом (recorder), сохраняющий историю вызовов компоненты. После завершения использования компоненты, если не произошло отката транзакции, протоколист формирует сообщение MSMQ со всеми вызовами компоненты. На стороне сервера сообщение MSMQ ожидается слушателем (listener), который не является COM+ компонентой. При появлении сообщения в очереди он создает специальную COM+ компоненту, называемую помощником слушателя (listener helper), которая считывает сообщение из очереди. После считывания сообщения помощник слушателя создает исполнитель (player), который и создает сам экземпляр отложенной компоненты, воспроизводя затем последовательность ее вызовов в рамках той же транзакции. С точки зрения отложенной компоненты исполнитель является обычным клиентом.

Аналогичным образом происходит получение результата вызова удаленной компоненты: на сервере создается протоколист, а на клиенте – слушатель и исполнитель. Однако в этом случае до начала использования удаленной компоненты клиенту следует создать вызываемый объект (call-back object), который будет принимать ответ от сервера через исполнителя, и передать ссылку на такой объект (точнее, на его исполнителя), серверу (рис. 6.5).


Рис. 6.5.  Взаимодействие с отложенной компоненты



Распределенные транзакции


Одним из основных достоинств среды COM+ является поддержка распределенных транзакций на базе координатора распределенных транзакций. Настройки транзакции компоненты COM+ влияют на настройки синхронизации и активации. Любая включенная настройка транзакций, отличная от Not supported, требует использования JIT активации и ограничивает выбор настройки синхронизации не более чем вариантами Required и Requires new. При активации объекта среда COM+ определяет необходимость использования транзакций в соответствии с таблицей 6.2.

Таблица 6.2. Участие объекта COM+ в транзакции Настройка транзакции компоненты COM+Создатель объекта участвует в транзакции Создатель объекта не участвует в транзакции Не поддерживается (Not Supported) Поддерживается (Supported) Требуется (Required) Требуется новая (Requires new)
Вне транзакцииВне транзакции
Транзакция создателяВне транзакции
Транзакция создателя Новая транзакция
Новая транзакцияНовая транзакция

Если при создании объекта обнаружена потребность в создании новой транзакции, среда COM+ создает ее с помощью координатора транзакций и данный объект считается корнем транзакции (рис. 6.2). Поскольку требующие транзакцию объекты используют активацию одного вызова, то транзакция не может существовать дольше, чем один вызов метода корневого объекта клиентом COM+. В транзакции могут участвовать службы, имеющие свой менеджер ресурсов, в частности MSMQ и MS SQL. Для многих ресурсов при необходимости можно создать свой компенсирующий менеджер ресурсов.


Рис. 6.2.  Транзакция COM+

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


Рис. 6.3.  Успешное завершение транзакции COM+



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


Сборка, содержащая описание одного или нескольких классов компонент, должна быть зарегистрирована как приложение COM+ в каталоге COM+. Для этого сборка должна быть подписанной (strong-named assembly). Неподписанные сборки идентифицируются своим именем, версией и информацией о типе культуры. Подписанная сборка содержит также открытый ключ и цифровую подпись, созданную закрытым ключом. Таким образом, при наличии открытого ключа можно проверить неизменность кода сборки. Для генерации пары из закрытого и открытого ключа служит утилита sn.exe из .NET Framework SDK. Для создания сборки из приведенного выше файла SampleComponent.cs следует выполнить (или внести в make файл) следующие комманды.

sn -k SampleComponent.snk csc /target:library /r:System.EnterpriseServices.dll SampleComponent.cs /keyfile:SampleComponent.snk

После успешного создания подписанной сборки остается зарегистрировать ее в каталоге COM+. В .NET Framework существуют три способа регистрации обслуживаемых компонент:

с использованием класса System.EnterpriseServices.RegistrationHelper; c использованием утилиты regsvcs.exe; автоматическая регистрация сборки в момент создания экземпляра обслуживаемой компоненты.

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

regsvcs SampleComponent.dll

Для удаления сборки из каталога COM+ выполняется аналогичная команда.

regsvcs /u SampleComponent.dll

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

// Файл SampleClient.cs using System; using ServicedComponentSample; class Test { static public void Main() { using(SampleComponent com = new SampleComponent()) { com.Do(); } } } // SampleClient.cs

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

csc /target:exe /r:SampleComponent.dll SampleClient.cs

Для связывания этого приложения с компонентой на удаленном компьютере следует зарегистрировать на нем сборку SampleComponent.dll. Затем, пользуясь оснасткой comexp.msc на удаленном компьютере следует посредника приложения COM+, и установить посредника на компьютере клиента с исполняемым файлом SampleClient.exe. Следует отметить, что сборка может быть либо установлена в каталог COM+ в качестве локального приложения, либо как посредник приложения на удаленном компьютере, но не то и другое одновременно.



Сервисы COM+


Целью создания промежуточной среды COM+ являлось предоставление компонентам COM+ набора сервисов, облегчающему создание надежной и масштабируемой распределенной системы. Начиная с Windows 2003/XP SP2, часть этих сервисов может быть доступна и без создания компонент COM+, путем использования так называемых сервисов без компонент.



Сервисы COM+ без компонент COM+


Сервисы без компонент (SWC) позволяют использовать сервисы среды COM+ без создания приложение COM+ и без наследования от класса ServicedComponent.

Для включения возможностей SWC следует включить сначала доступ по TCP/IP к координатору распределенных транзакций. По умолчанию эта возможность отключена в Windows 2003 и Windows XP SP2, поскольку уменьшает безопасность системы. Если компьютер подключен к интернету через NAT, то ее включение достаточно безопасно. Эту опцию можно включить через оснастку %systemroot%\system32\Com\comexp.msc или импортом следующего файла в системный реестр:

REGEDIT4 [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSDTC\Security] "NetworkDtcAccess"=dword:00000001

Затем следует перезапустить службу координатора распределенных транзакций

net stop MSDTC net start MSDTC

Рассмотрим класс, облегчающий использование SWC. Поскольку использование COM+ связано с использованием неуправляемых ресурсов, класс реализует интерфейс System.IDisposable с единственным методом void Dispose(), осуществляющим выход их домена COM+.

using System; using System.EnterpriseServices; namespace Seva.Swc { public class SwcUtil : IDisposable { public SwcUtil() { ServiceDomain.Enter(GetConfig(TransactionOption.Required)); } public SwcUtil(TransactionOption transactionOption) { ServiceDomain.Enter(GetConfig(transactionOption)); } protected virtual ServiceConfig GetConfig( TransactionOption transactionOption) { ServiceConfig config = new ServiceConfig(); config.Transaction = transactionOption; config.TrackingEnabled = true; config.TrackingAppName = "SwcUtil"; config.TrackingComponentName = this.GetType().FullName; return config; } public void Dispose() { ServiceDomain.Leave(); } } }

Пример использования данного класса приведен ниже. В качестве примера рассмотрена работа с очередями сообщений с использованием транзакций COM+.

using System; using System.EnterpriseServices; using System.Messaging; using Seva.Msmq; using Seva.Swc; public class MainApp { public static void Main() { using (SwcUtil swc = new SwcUtil()) { MessageQueue queue = MsmqTools.CreateQueue(@".\Private$\swc_queue"); queue.Send("SWC Message", MessageQueueTransactionType.Automatic); Console.WriteLine("Нажмите <Enter> для завершения транзакции"); Console.ReadLine(); ContextUtil.SetComplete(); } } }

Использование SWC не требует подписанной сборки, поскольку сборка не регистрируется в качестве приложения COM+.

К сожалению, при использовании SWC невозможно использовать компенсирующий менеджер ресурсов. Во первых, класс Compensator наследован от System.EnterpriseServices.ServicedComponent, то есть является обслуживаемой компонентой, поэтому для его использования должно быть создано приложение COM+. Поэтому при создании секретаря класса Clerk и регистрации компенсатора при использовании сервисов без компонент происходит catastrophic exception, если сборка подписана, иначе – жалоба на отсутствие подписи. Таким образом, сервисы без компонент следует считать полумерой, благодаря которой можно работать с ресурсами, поддерживающими распределенные транзакции, такими как MSMQ или MS SQL.



Синхронизация


Среда COM+ позволяет исключить проблемы с синхронизацией при обслуживании запроса клиента путем так называемых активностей. Активность начинается в момент создания клиентом COM+ объекта и заканчивается при его удалении. В течении активности клиент вызывает методы компоненты COM+, в ходе которых она может создавать другие объекты COM+, в том числе расположенные на удаленных компьютерах, и вызывать их методы. При этом COM+ гарантирует, что в течение одной активности в каждый момент выполняется метод только одного COM+ объекта из всех участвующих в активности. Таким образом, активность представляет собой некий логический поток. Объекты COM+ могут как участвовать в активности (требовать синхронизации), так и не участвовать, в зависимости от атрибутов соответствующей им компоненты COM+ и текущего контекста при их создании, как показано в таблице 6.1.

Таблица 6.1. Синхронизация создаваемого объекта COM+Настройка синхронизации компоненты COM+Создатель объекта участвует в активностиСоздатель объекта не участвует в активностиНе поддерживается (Not Supported)Поддерживается (Supported)Требуется (Required) Требуется новая (Requires new)
Вне активностиВне активности
Активность создателяВне активности
Активность создателяНовая активность
Новая активность Новая активность

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



Слабо связанные события


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



Создание компенсирующего менеджера ресурсов


Компенсирующий менеджер ресурсов позволяет использовать в транзакциях среды COM+ какие либо ресурсы, которые не имеют прямой поддержки транзакций COM+. Этот механизм включает следующие классы из пространства имен System.EnterpriseServices.CompensatingResourceManager:

журнал операций (log), заполняемый операциями над ресурсом; секретарь (класс Clerk), ведущий журнал операций; компенсатор (наследника класса Compensator), который восстанавливает первоначальное состояние ресурса в случае отката транзакции и вносит изменения в ресурс при успехе транзакции.

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

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

открытие документа на чтение;открытие документа на перезапись;закрыть файл с сохранением изменений.

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

Пример компилируется и запускается make файлом, который можно передать как параметр входящей в состав .NET SDK утилите nmake.exe.
Этот файл имеет следующее содержание.

# Файл: makefile all: CrmSample.exe # сборка должна быть подписана CrmSample.key: sn -k CrmSample.key CrmSample.exe: CrmSample.cs CrmSample.Key csc /r:System.EnterpriseServices.dll CrmSample.cs /keyfile:CrmSample.key install: # установить приложение COM+ regsvcs CrmSample.exe uninstall: regsvcs –u CrmSample.exe Поскольку для регистрации сборки как приложения COM+необходимо, чтобы она была подписана, то вызовом утилиты sn.exe из состава .NET SKD создается пара ключей. Затем компилятор языка C# csc.exe создает сборку, используя этот файл ключей. При команде nmake install сборка устанавливается в качестве приложения COM+ вызовом утилиты regsvcs.exe Microsoft Windows.

Далее будет рассмотрено содержание файла CrmSample.cs.

// Файл CrmSample.cs using System; using System.IO; using System.Collections.Generic; using System.EnterpriseServices; using System.EnterpriseServices.CompensatingResourceManager; [assembly: ApplicationActivation(ActivationOption.Server)] [assembly: ApplicationAccessControl(false)] [assembly: ApplicationCrmEnabled] [assembly: ApplicationName("Seva CRM")] [assembly: Description("Пример на использование CRM")] Данные атрибуты сборки из пространства имен System.EnterpriseServices управляют параметрами создаваемого приложения COM+:

ApplicationActivation – задает тип приложения COM+ (серверный или библиотечный); ApplicationCrmEnabled – необходим для использования CRM в приложении COM+; ApplicationAccessControl – управляет контролем доступа к приложению, в данном примере – отключен; Атрибут System.ComponentModel.DescriptionAttribute задает описание сборки. Класс StreamLog содержит статический метод для записи в файл буфера, вызываемый при завершении транзакции.

public static class StreamLog { public static void Save(LogRecord log) { if (log.Record is object[]) { object[] record = (object[])log.Record; string fileName = (string) record[0]; byte[] buffer = (byte[]) record[1]; using (FileStream file = new FileStream(fileName, FileMode.Create, FileAccess.Write)) { file.Write(buffer, 0, buffer.Length); } } } // Save() } // StreamLog Класс StreamCache помогает организовать кеширование содержимого файлов, использующихся в транзакции.



public class StreamCache { private string fileName; private MemoryStream stream; public MemoryStream Stream { get { return stream;} } public string FileName { get { return fileName;} } public StreamCache(string streamFileName) { fileName = streamFileName; stream = new MemoryStream(); } Метод Reopen при необходимости открывает повторно закрытый поток. Поскольку такая операция не поддерживается классом MemoryStream, то сначала в массив записывается все содержимое потока, затем создается новый поток, куда записываются сохраненные данные.

public void Reopen() { if (!stream.CanRead) { stream.Close(); byte[] buffer = stream.ToArray(); stream.Dispose(); stream = new MemoryStream(); stream.Write(buffer, 0, buffer.Length); stream.Seek(0, SeekOrigin.Begin); } } // Reopen() При открытии файла для чтения вызывается метод Read, считывающий все содержимое файла в поток типа MemoryStream.

public void Read() { byte[] buffer = new byte[32*1024]; using (Stream inputStream = new FileStream(fileName, FileMode.Open, FileAccess.Read)) { while (true) { int read = inputStream.Read(buffer, 0, buffer.Length); if (read <= 0) { break; } Stream.Write(buffer, 0, read); } } } // Read() } // StreamCache Класс StreamCrm является менеджером ресурсов, используемым объектами COM+. Он является COM+ объектом, поэтому несколько участвующих в транзакции компонент COM+ могут работать с одним менеджером ресурсов данного типа. Менеджер содержит кеш для реализации отложенной до завершения транзакции записи в файл.

public class StreamCrm: ServicedComponent { private Dictionary<string, StreamCache> cache; public StreamCrm() { cache = new Dictionary<string, StreamCache>(); } Метод StreamCrm.CheckCache проверяет, есть ли в кеше информация о данном файле. При отсутствии ее в кеше в случае открытия файла на чтение происходит считывания всего содержимого файла в кеш, в противном случае связанный с файлом поток открывается повторно вызовом метода StreamCache.Reopen.

private StreamCache CheckCache(string fileName, bool read) { StreamCache streamCache; string key = Path.GetFullPath(fileName).ToLower(); if (!cache.ContainsKey(key)) { streamCache = new StreamCache(fileName); cache.Add(key, streamCache); if (read) { streamCache.Read(); }; } else { streamCache = cache[key]; streamCache.Reopen(); }; return streamCache; } Метод StreamCrm.ReadFromFile вызывается клиентом, желающим читать из файла, метод StreamCrm.WriteToFile – желающим перезаписать файл.



public MemoryStream ReadFromFile(string fileName) { return CheckCache(fileName, true).Stream; } public MemoryStream WriteToFile(String fileName) { return CheckCache(fileName, false).Stream; } Метод StreamCrm.Flush прекращает работу с потоком, открытым для записи и сохраняет сделанные изменения в записи для журнала секретаря, которую возвращает в качестве своего результата.

public object[] Flush(String fileName) { StreamCache streamCache = CheckCache(fileName, false); streamCache.Stream.Close(); object[] record = new object[2]; record[0] = fileName; record[1] = streamCache.Stream.ToArray(); return record; } Статический метод StreamCrm.CreateClerk создает секретаря для ведения журнала операций над ресурсом. Вызов статического метода не приводит к удаленному вызову и смене контекста.

public static Clerk CreateClerk() { return new Clerk(typeof(StreamCompensator), "Compensator", CompensatorOptions.AllPhases); } } Класс StreamCompensator наследуется от класса Compensator и служит для сохранения результатов транзакции в случае успеха или возврате данных в первоначальное состояние в случае отката транзакции.

public class StreamCompensator : System.EnterpriseServices.CompensatingResourceManager.Compensator { private bool prepared = false; Методы c суффиксом Prepare вызываются для проверки записей в журнале перед завершением или откатом транзакции. Метод PrepareRecord должен вернуть false, если запись журнала должна быть использована. Поскольку журнал записей не контролирует типы записей, это должен сделать метод PrepareRecord.

public override void BeginPrepare () { } public override bool PrepareRecord(LogRecord log) { prepared = false; if (!(log.Record is object[])) return true; object[] record = log.Record as object[]; if (record.Length != 2) return true; if (!(record[0] is string) || !(record[1] is byte[])) return true; prepared = true; return false; } Метод EndPrepare возвращает сохраненный в поле объекта результат проверки записи. Если он возвращает true, то возможно успешное завершение транзакции.



public override bool EndPrepare () { return prepared; } Методы c суффиксом Commit вызываются для сохранения результата транзакции. Если метод CommitRecord возвращает истинное значение, то запись можно исключить из журнала операций.

public override void BeginCommit (bool commit) { } public override bool CommitRecord (LogRecord log) { StreamLog.Save(log); return true; } public override void EndCommit () { } Группа методов с суффиксом Abort служит для возвращения ресурсу первоначального состояния при откате транзакции. Поскольку созданный менеджер ресурсов изменяет состояние файлов меняется только при успешном завершении транзакции, то эти методы не содержат никаких действий.

public override void BeginAbort (bool abort) { } public override bool AbortRecord (LogRecord log) { return true; } public override void EndAbort () { } } // StreamCrm Ниже приведен пример двух классов COM+, которые используют созданный менеджер ресурсов. Напомним, что для регистрации в качестве COM+ компоненты классы должен иметь публичный конструктор без параметров. Атрибут TransactionAttribute управляет использованием транзакций COM+. Класс SampleCrmClient2 содержит метод ReadLine, читающий из файла строчку с использованием созданного менеджера ресурсов.

[Transaction(TransactionOption.Required)] public class SampleCrmClient2: ServicedComponent { public SampleCrmClient2() { } public string ReadLine(StreamCrm crm, string fileName) { using (StreamReader reader = new StreamReader(crm.ReadFromFile(fileName))) { return line = reader.ReadLine(); } } } Метод SampleCrmClient.DoSomeWork демонстрирует использование CRM. Смена текущей директории вызвана тем, что по умолчанию для серверных компонент COM+ текущей является директория %systemroot%\system32.

[Transaction(TransactionOption.RequiresNew)] public class SampleCrmClient1: ServicedComponent { public SampleCrmClient1() { } public void DoSomeWork(string dir) { const string fileName1 = "sample1.txt"; const string fileName2 = "sample2.txt"; Environment.CurrentDirectory = dir; StreamCrm crm = new StreamCrm(); Clerk clerk = StreamCrm.CreateClerk(); Метод производит действия с двумя файлами.


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

using (StreamWriter writer = new StreamWriter(crm.WriteToFile(fileName1))) { writer.Write(Environment.CurrentDirectory); } // добавление в журнал записи clerk.WriteLogRecord(crm.Flush(fileName1)); Затем из этого файла строка считывается другим объектом COM+ класса SampleCrmClient2. Таким образом, если объекты в пределах одной транзакции будут обращаться к файлам при помощи одного экземпляра менеджера ресурсов, то изменения, внесенные одним объектом, будут видны другим объектам транзакции, но не видны снаружи до ее завершения.

String tempString = ""; using (SampleCrmClient2 client2 = new SampleCrmClient2()) { tempString = client2.ReadLine(crm, Path.GetFullPath(fileName1)); } Считанные объектом данные записываются во второй файл.

using (StreamWriter writer = new StreamWriter(crm.WriteToFile(fileName2))) { writer.WriteLine(String.Format("Считано из файла [{0}]:", fileName1)); writer.WriteLine(tempString); } // добавление в журнал записи clerk.WriteLogRecord(crm.Flush(fileName2)); // успешное завершение транзакциии, сохранение изменений в файлах ContextUtil.SetComplete(); } } Класс CrmTest содержит метод Main. Вместо конструкции try .. finally … Dispose в C# следует использовать оператор using с тождественным результатом. Однако в данном примере хотелось бы показать, что при использовании объектов среды EnterpriseServices/COM+ для них следует вызывать Dispose в явном или неявном (через оператор using) виде.

class CrmTest { public static void Main() { SampleCrmClient1 client1 = new SampleCrmClient1(); try { client1.DoSomeWork(Environment.CurrentDirectory); } finally { client1.Dispose(); } } } Кроме работы с компенсирующим менеджером ресурсов, на данном примере хотелось бы показать, что хотя сборка мусора упрощает работу программиста, но при использовании объектов с интерфейсом IDisposable всегда следует вызывать их метод Dispose (явно или неявно) для своевременного освобождения ресурсов.


Создание обслуживаемых компонент


Обслуживаемая компонента .NET Framework является объектом класса, наследованным от System.EnterpriseServices.ServicedComponent, и отвечающим следующим требованиям:

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

Статические методы в классе компоненты возможны, но они выполняются в контексте клиента, и имеют доступа к контексту COM+ клиента, если таковой существует.

Класс сервисной компоненты должен быть объявлен в сборке, которая может быть зарегистрирована в качестве приложения COM+. Такая сборка не должна иметь классов общего вида со спецификатором public, и должна быть подписана.

Класс обслуживаемой компоненты может иметь атрибуты из пространства имен System.EnterpriseServices, связанные с транзакцией (TransactionAttribute), активацией (JustInTimeActivationAttribute и ObjectPoolingAttribute) и синхронизацией (SynchronizationAttribute). Как упоминалось ранее, поддержка транзакции автоматически означает JIT-активацию и участие в активности, тем не менее следует перечислять атрибут JIT активации явно.

Рассмотрим код простейшей обслуживаемой компоненты.

// Файл SampleComponent.cs using System; using System.EnterpriseServices; [assembly: ApplicationAccessControl(false)] [assembly: ApplicationName("Serviced Component Example 01")] [assembly: Description("Sample of .NET serviced component.")] [assembly: ApplicationActivation(ActivationOption.Server)]

Эти атрибуты задают свойства сборки, которая затем будет зарегистрирована в качестве приложения COM+. Атрибут ApplicationActivation позволяет выбрать серверный или библиотечный тип приложения.

namespace ServicedComponentSample { [JustInTimeActivation] [Transaction(TransactionOption.Required)] [ObjectPooling(Enabled=true, MinPoolSize=5, MaxPoolSize=10)] public class SampleComponent: ServicedComponent {

Объявленный выше класс компоненты, как видно из атрибутов, требует наличия транзакции и использует JIT активацию с пулом объектов


public SampleComponent() { } Для использования в пуле объект должен быть возвращен в свое первоначальное состояние (деактивизирован). Для поддержки режима активации одного вызова компонента не должна иметь состояния, сохраняемого между вызовами ее методов клиентом, и таким образом часто она не требует каких-либо действий для перевода в первоначальное состояние. При необходимости можно перекрыть виртуальные методы Activate и Deactivate, вызываемые при операциях активации и деактивации, и производить в них сброс состояния объекта.

protected override void Activate() { } protected override void Deactivate() { // Здесь при необходимости можно очистить // состояние объекта и освободить ресурсы } Метод CanBePooled возвращает true, если объект может быть возвращен в пул объектов после деактивации, иначе он уничтожается.

protected override bool CanBePooled() { // Подтверждает возможность помещения объекта в пул return true; } Атрибут System.EnterpriseServices.AutoCompleteAttribute применяется для автоматического участия методов в транзакции. Если метод завершается нормально, то считается, что он подтвердил успешное завершение транзакции, при возникновении же в методе не пойманного исключения транзакция будет отменена.

[AutoComplete] public void Do() { } } } // Файл SampleComponent.cs Альтернативный AutoComplete вариант использования транзакций заключается в вызове статических методов класса ContextUtil. Этот класс используется для доступа и изменения контекста COM+, связанного с обслуживаемой компонентой. Метод ContextUtil.SetComplete сообщает COM+ об успешном завершении метода, а ContextUtil.SetAbort предопределяет отмену транзакции. Используются они обычно следующим образом.

try { ... ContextUtil.SetComplete(); } catch() { ContextUtil.SetAbort(); } Постоянное использование методов ContextUtil приводит к коду, который будет сложно перенести из компоненты COM+ в класс, выполняемый вне промежуточной среды COM+. Для решения этой проблемы следует либо использовать методы класса, оборачивающего методы ContextUtil, либо, в крайнем случае, атрибут AutoComplete, который игнорируется для обычных классов.


Введение в промежуточную среду COM+


COM+ – промежуточная среда для создания распределенных систем, действующих в локальной сети. Она разрабатывается фирмой Microsoft с конца 90-х годов и впервые появилась в составе операционной системы Microsoft Windows 2000. Основной целью разработки среды COM+ было создание инфраструктуры для разработки распределенных систем автоматизации предприятия. Основные достоинства среды COM+:

поддержка как синхронного, так и асинхронного взаимодействия программных компонент; совместная работа с координатором распределенных транзакций (distributed transactions coordinator, DTC); поддержка метода доступа единственного вызова с пулом объектов; использование для ограничения доступа к компоненте ролей (roles), связываемых администратором системы с учетными записями пользователей.

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

Приложения COM+ бывают двух видов: библиотечные и серверные. Экземпляры компонент библиотечных приложений выполняются в том же процессе, что и использующий их клиент, компоненты серверного – в отдельном потоке сервера, возможно выполняющимся на удаленном компьютере. Только серверные приложения могут использоваться удаленно путем регистрации в каталоге COM+ на компьютере клиента посредника приложения COM+. После установки посредников использование удаленных серверных компонент COM+ не отличается от использования локальных серверных компонент. Понятие посредник (proxy) используется в COM+ в двух различных смыслах – запись в каталоге COM+, связанная с некоторым приложением COM+ на удаленном компьютере, и в том же смысле, что и посредник при удаленном вызове.

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


На рис. 6. 1 показана архитектура среды COM+ при использовании серверных приложений. Объекты COM+ являются экземплярами компонент COM+, зарегистрированных в каталоге. Заглушка на стороне серверного процесса называется в COM+ перехватчиком (interceptor).

Рис. 6.1.  Архитектура среды COM+
Каталог COM+ каждого компьютера содержит список зарегистрированных на компьютере локальных приложений COM+, а также список установленных посредников для связи с приложениями удаленных компьютеров. Каталог устроен иерархически, в виде дерева. Например, узел с описанием приложения COM+ содержит узел со списком входящих в него компонент COM+ и узел со списком ролей приложений. Управление каталогом COM+ происходит при помощи оснастки comexp.msc или программно, используя классы библиотеки comadmin.dll. Подробнее состав каталога описан в приложении I.
Поскольку среда COM+ реализует свое собственное управлением исполняемым внутри нее кодом, то существует понятие контекста COM+, который представляет собой окружение объекта COM+. Контекст закрепляется за объектом в момент его активации и сохраняется до его деактивации. При создании контекста учитываются атрибуты необходимости транзакции и синхронизации, установленные в активируемой компоненте COM+, а так же текущий контекст в момент активации объекта.

Выводы по использованию среды Enterprise Services / COM+


Хотя COM+ имеет обширные возможности для создания распределенных систем внутри предприятия, ей свойственны и следующие недостатки.

Среда COM+ разработана до .NET Framework, поэтому в среде Enterprise Services существуют ограничения на классы .NET Framework, регистрируемые в качестве обслуживаемых компонент. При использовании компонент Enterprise Services проявляются некоторые особенности работы с ними, являющиеся следствием нетривиального взаимодействия CLR и COM+. Компоненты COM+ не могут использоваться вне доверенной сети, поскольку для их использования должен быть открыт, в частности, доступ к порту RPC (135-ый порт TCP). С данным портом связан большой и, вероятно, еще незаконченный список общеизвестных уязвимостей.

Рассмотрим промежуточную среду COM+ с точки зрения требований к распределенной системе.

Открытость. Служба COM+ является внутренней технологией Microsoft и реализована только в операционной системе Windows 2000 и последующих версиях Windows. Для использования обслуживаемой компоненты нужно иметь как минимум доступ к сборке с ее интерфейсом и иметь установленный посредник компоненты COM+. Таким образом, среда Enterprise Services не является открытой. Масштабируемость. Служба COM+ поддерживает балансировку нагрузок путем создания кластера машин на основе Windows Server. Выбор используемого сервера осуществляется только в момент создания объекта клиентом, даже при использовании JIT активации. Среда COM+ поддерживает модель единственного вызова с пулом объектов, что позволяет добиться баланса времени создания удаленного объекта и используемой сервером памяти. Поддержание логической целостности данных. COM+ использует координатор распределенных транзакций из сервера транзакций Microsoft (MTS) и позволяет создавать менеджеры управления ресурсами. Устойчивость. Посредники приложения COM+ связывают клиентский компьютер с именем компьютера (DNS или NETBIOS), на котором развернуто приложение COM+. Таким образом, этот компьютер является слабым местом системы (в случае использования кластера приложений им является планировщик кластера COM+). Для решения этой проблемы можно использовать сервисы Windows Clustering, позволяющие создать дублера данного компьютера, начинающего функционировать в случае выхода основного планировщика. Таким образом, использующая среду COM+ распределенная система может не иметь уникальной точки сбоя. Эффективность (в узком смысле). Среда COM+ взаимодействует со средой .Net достаточно сложным образом, вероятно приводящим к определенным накладным расходам. Безопасность. COM+ позволяет использовать встроенные механизмы безопасности Microsoft Windows.

Можно сделать вывод, что хотя промежуточная среда EnterpriseServices/COM+ предоставляет обширный набор сервисов для компонент распределенной системы, но ее использование ограничено взаимодействием компонент внутри локальной или виртуальной частной сети, построенной на базе Microsoft Windows в пределах одного предприятия.



Взаимодействие среды COM+ и среды CLR


Среда COM+ была создана до технологии .NET, поэтому она работает с неуправляемым кодом и не является носителем исполняемой среды CLR. Для использовании сервисов COM+ из .NET Framework необходимо получить возможность использовать управляемый код в контексте COM+. Для решения данной проблемы была создана достаточно сложная схема взаимодействия сред CLR и COM+, основанная на понятии компоненты, использующей сервисы COM+ (serviced component), называемой далее обслуживаемой (средой COM+) компонентой. Такая компонента состоит из объекта управляемого кода, принадлежащему наследованному от System.EnterpriseServices.ServicedComponent классу. Благодаря наследованию от ServicedComponent при исполнении методов этого объекта имеется доступ к контексту COM+. Использование таких компонент упрощенно показано на рис. 6.6.


Рис. 6.6.  Взаимодействие COM+ и CLR при использовании серверных приложений

Маршализация параметров методов обслуживаемой компоненты при использовании происходит под управлением службы .NET Remoting (с использованием класса форматирования BinaryFormatter), а среда COM+ используется только для реализации своих сервисов.

Как видно из рис. 6.6, обслуживаемая компонента .NET Framework не является, строго говоря, компонентой COM+. Аналогично и промежуточная среда .NET Enterpsise Services является не новым названием среды COM+, а средством использования сервисов COM+ в управляемом коде. Хотя часто можно упрощенно считать, что обслуживаемые компоненты – это компоненты COM+ на управляемом коде, но при разработке обслуживаемых компонент существуют случаи, когда желательно понимать взаимосвязь CLR и COM+. Например, если при вызове метода управляемой компоненты происходит ошибка в момент выполнения неуправляемого кода среды COM+, то полученная от посредником COM+ информация преобразуется в исключение типа System.Runtime.InteropServices.COMException. В качестве текста сообщения в этом исключении фигурируют ошибки среды COM+, достаточно малопонятные с точки зрения управляемого кода, например такие.

Unhandled Exception: System.Runtime.InteropServices.COMException (0x8000FFFF): Catastrophic failure (Exception from HRESULT: 0x8000FFFF (E_UNEXPECTED)) Unhandled Exception: System.Runtime.InteropServices.COMException (0x8004D082): Exception from HRESULT: 0x8004D082

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