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

         

Архитектура среды NET Remoting


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


Рис. 8.2.  Выполнения удаленного вызова в .NET Remoting

Архитектура среды Remoting (рис. 8.2) включает в себя следующие основные сущности, реализуемые классами из пространства имен System.Runtime.Remoting и его подпространств.

Посредники удаленного объекта. В среде используется два посредника, принадлежащие классам TransparentProxy и RealProxy. Первый из них является классическим посредником и реализует интерфейс удаленного объекта. Второй, "настоящий", посредник получает от "прозрачного" посредника сообщение об удаленном вызове (класс IMessage). "Настоящий" посредник может быть изменен разработчиком для реализации каких то специфических функций, и поэтому посредник удаленного объекта в среде Remoting представлен двумя сущностями. Сообщение проходит по каналу (channel) среды Remoting. Канал состоит из отдельных труб (sinks) и может иметь различную структуру. Как минимум, клиентская сторона канала включает трубу форматирования, преобразующую сообщение об удаленном вызове в поток ввода- вывода, и транспортную трубу, передающую данный поток в канал передачи данных.
Серверная сторона канала, в свою очередь, состоит как минимум из транспортной трубы, трубы форматирования и трубы диспетчеризации. Трубы форматирования и транспорта присутствуют на каждой стороне не более чем в одном экземпляре. Канал может содержать и другие трубы, которые оперируют с сообщением или полученным из него потоком. В первом случае трубы располагаются до трубы форматирования (на клиенте) и реализуют интерфейс IMessageSink. Такие трубы называют трубами сообщения (message sinks). Во втором случае трубы располагаются между трубами форматирования и транспорта, и называются трубами обработки потока (channel sinks). Они реализуют интерфейс IСlientChannelSink на клиенте или IServerChannelSink на сервере. Стандартная труба форматирования реализует интерфейсы и трубы потока, и трубы сообщения, а транспортная труба относится к трубам потока. Последняя из труб в серверной части канала должна передать сообщение заглушке удаленного вызова, называемой диспетчером (реализуется методом ChannelServices.DispatchMessage) . Конкретный набор труб, образующий канал Remoting, определяется используемым классом канала. Такой класс реализует интерфейсы IChannel, IChannelSender, IChannelReceiver и должен создавать последовательность труб на стороне клиента и/или сервера, как это будет показано далее в примерах. Среда Remoting сразу "из коробки" содержит три класса канала. Классы HttpChannel и TcpChannel работают с протоколами HTTP и TCP соответственно, а добавленный в .NET Framework 2.0 класс IpcChannel предназначен для эффективного локального взаимодействия процессов (IPC: inter process communication) через именованные каналы. В трубе форматирования любого из трех каналов может использоваться один из двух рассмотренных ранее классов форматирования: BinaryFormatter и SoapFormatter.

Для передачи информации об объекте сервера и создания на клиенте "прозрачного" посредника используется особый класс System.Runtime.Remoting.ObjRef. При попытке сериализации и передачи по каналу маршализируемого по ссылке объекта используется рассмотренный ранее механизм сурогатных селекторов.


Благодаря этому механизму при сериализации наследников класса System.MarshalByRefObject по каналу Remoting вместо них передается маршализируемый по значению объект класса ObjRef, который после десериализации становится "прозрачным" посредником на клиенте Remoting (рис. 8.3).


Рис. 8.3.  Маршализация по ссылке в среде Remoting

Таким образом, среда Remoting позволяет организовать синхронный или односторонний удаленный вызов методов объектов, наследованных от класса MarshalByRefObject. Асинхронный односторонний вызов осуществляется для методов с атрибутом OneWayAttribute 1). Такие методы не должны возвращать какие либо результаты, и вызываются по принципу "выстрелил и забыл". Выбрасываемые в ходе его выполнения исключения также не передаются клиенту. Среда Remoting поддерживает все три вида активации объектов, причем для объектов единственного вызова изменение значения свойств и общедоступных полей рассматривается как удаленный вызов, за которым следует освобождение объекта. Среда Remoting не поддерживает пул объектов.

Одной из проблем при использовании распределенных объектов является определение момента их уничтожения сервером. Для автоматического удаления из памяти локальных объектов, используемых удаленным клиентом, недостаточно отслеживания локальных ссылок на них. В среде Remoting для управлением временем жизни таких объектов используется система аренды (lease), которая при использовании активируемых клиентом объектов или объектов единственного экземпляра с некоторым интервалом просит клиента подтвердить необходимость существования объекта. Если на стороне клиента уже нет активных ссылок на удаленный объект, то подтверждения не происходит, и при отсутствии хотя бы одного подтверждения объект на сервере будет освобожден. Для объектов единственного вызова в такой системе нет нужды, поскольку объекты создаются сервером при удаленном вызове и освобождаются сервером после завершения любого удаленного вызова (включая методы set и get свойств).В силу этого с объектами, активируемыми сервером, может использоваться только конструктор по умолчанию, не имеющий параметров.


Канал среды Remoting


Канал, по которому в среде Remoting передается сообщение удаленного вызова, образуется списком труб, который так же называется цепочкой (chain). Каждая труба в цепочке клиентской части канала, за исключением транспортной, содержит ссылку на следующую трубу. Эта ссылка содержится в свойстве NextSink (для интерфейса IMessageSink) или NextChannelSink (для интерфейсов IClientChannelSink и IServerChannelSink).


Рис. 8.5.  Поставщики труб канала



Цепочка труб создается цепочкой так называемых поставщиков труб (sink providers). Каждый поставщик, реализующий интерфейс IClientChannelSinkProvider или IServerChannelSinkProvider, создает цепочку из одной или нескольких труб в методы CreateSink (рис. 8.5). В отличие от труб канала, трубы сообщения (интерфейс IMessageSink) создаются не поставщиками, а самим каналом в методе CreateMessageSink.

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

Обычно каждый поставщик создает цепочку из единственной трубы, что позволяет изменять свойства канала, добавляя или заменяя только часть поставщиков. Поставщики также образуют список, использующий их свойство Next. Стандартные каналы включают поставщика трубы форматирования и поставщика транспортной трубы. Например, цепочка поставщиков канала TcpClientChannel (клиентской части TcpChannel) состоит по умолчанию из поставщиков BinaryClientFormatterSinkProvider и TcpClientTransportSinkProvider. При этом поставщик форматирования может быть задан в конфигурационном файле. В простейшем случае достаточно указать ссылку на зарегистрированный тип в разделе <channel><serverProviders><formatter> в конфигурации сервера и в разделе <channel><clientProviders><formatter> на клиенте. Для стандартных классов форматирования это ссылки binary или soap.


<configuration> <system.runtime.remoting> <application> <service> <wellknown mode="SingleCall" type="RemotingTest.TestService, RemoteTestService" objectUri="endpoint"/> </service> <channels> <channel ref="http" port="2080"> <serverProviders> <formatter ref="binary"/> </serverProviders> </channel> </channels> </application> </system.runtime.remoting> </configuration> В качестве примера модификации рассмотрим добавление шифрования в стандартный канал на основе симметричного шифрования. Симметричное шифрования выбрано в данном примере исключительно из за простоты реализации. На практике следовало бы скорее использовать для данной цели шифрование с открытом ключом, однако действительно правильным решением обеспечения безопасности передачи данных в Remoting заключается либо в использовании IIS, либо в развертывании VPN на основе безопасного решения (например, OpenVPN). Поэтому данную модификацию канала следует рассматривать исключительно как пример по модернизации инфраструктуры .NET Remoting.

Для реализации шифрования достаточно добавить в цепочку поставщиков труб потока дополнительного поставщика, который будет создавать трубу, шифрующую и дешифрующую проходящие по ней потоки с сериализованными сообщениями. Для добавления такого поставщика в канал следует использовать файл конфигурации. Для использовании стандартного канала с дополнительной трубой достаточно указать имя класса поставщика трубы в разделе <channel><clientProviders><provider>.

В нижеследующем файле описаны классы поставщика трубы клиента и самой трубы, а также поставщика трубы сервера и его трубы. Сам механизм шифрования на основе стандартных классов FCL описан в приложении III.

// Файл SevaRemotingEncrypted.cs using System; using System.IO; using System.Collections; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Tcp; using System.Runtime.Remoting.Messaging; using Seva.Security.Encryption; namespace Seva.Remoting.Encryption { class EncryptedClientChannelSinkProvider: IClientChannelSinkProvider { private IClientChannelSinkProvider next; public IClientChannelSinkProvider Next { get { return next; } set { next = value; } } private SymmetricEncryptor encryptor; Конструктор поставщика трубы на стороне клиента получает в качестве аргумента словарь свойств, заполненный свойствами из файла конфигурации.


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

public EncryptedClientChannelSinkProvider(IDictionary properties, ICollection providerData) { string keyFile = (string) properties["key"]; Console.WriteLine("Client key: [{0}]", keyFile); encryptor = new SymmetricEncryptor(keyFile); } Метод CreateSink является основным методом поставщика трубы. В типичном случае сначала вызывается этот же метод для следующего в цепочке поставщика, а затем создается труба, вставляемая в цепочку труб. Следует отметить, что данный поставщик не может быть последним в цепочке, но соответствующие проверки свойства Next и выбросы исключений для экономии места не показаны.

public IClientChannelSink CreateSink(IChannelSender channel, string url, object remoteChannelData) { IClientChannelSink next = Next.CreateSink(channel, url, remoteChannelData); return new EncryptedClientChannelSink(encryptor, next); } } Класс поставщика трубы на стороне сервера устроен аналогичным образом.

class EncryptedServerChannelSinkProvider : IServerChannelSinkProvider { private IServerChannelSinkProvider next; private SymmetricEncryptor encryptor; public IServerChannelSinkProvider Next { get { return next; } set { next = value; } } public EncryptedServerChannelSinkProvider(IDictionary properties, ICollection providerData) { string keyFile = (string) properties["key"]; Console.WriteLine("Server key: [{0}]", keyFile); encryptor = new SymmetricEncryptor(keyFile); } // Создание трубы канала public IServerChannelSink CreateSink(IChannelReceiver channel) { IServerChannelSink nextSink = Next.CreateSink(channel); return new EncryptedServerChannelSink(channel, encryptor, nextSink); } // Обязательный метод интерфейса public void GetChannelData(IChannelDataStore channelData) { } } Листинг 8.1. Собственно шифрование выполняется в созданными поставщиками трубах канала на стороне клиента и сервера.



class EncryptedServerChannelSink : IServerChannelSink { public IDictionary Properties { get { return null; } } private IServerChannelSink nextSink; public IServerChannelSink NextChannelSink { get { return nextSink; } } private SymmetricEncryptor encryptor; public EncryptedServerChannelSink(IChannelReceiver channel, SymmetricEncryptor channelEncryptor, IServerChannelSink next) { encryptor = channelEncryptor; nextSink = next; } Главным методом трубы при синхронном удаленном вызове является метод ProcessMessage. В случае трубы потока данный метод может оперировать как с самим сообщением, так и с полученным из него в ходе сериализации потоком. Труба шифрования сервера должна дешифровать этот поток при получении сообщения от клиента и зашифровать поток при посылке клиенту ответа сервера.

// Метод синхронной обработки сообщения public ServerProcessing ProcessMessage(IServerChannelSinkStack sinkStack, IMessage requestMsg, ITransportHeaders requestHeaders, Stream requestStream, out IMessage responseMsg, out ITransportHeaders responseHeaders, out Stream responseStream) { Stream plainStream = null; MemoryStream decodedStream = new MemoryStream(); requestStream = Utils.ReadAllStream(requestStream); // Расшифровать запрос клиента encryptor.Decrypt(requestStream, decodedStream); decodedStream.Position = 0; ServerProcessing result = nextSink.ProcessMessage(sinkStack, requestMsg, requestHeaders, decodedStream, out responseMsg, out responseHeaders, out plainStream); // Зашифровать ответ сервера responseStream = new MemoryStream(); encryptor.Encrypt(plainStream, responseStream); responseStream.Position = 0; // return result; } public void AsyncProcessResponse(IServerResponseChannelSinkStack sinkStack, Object state, IMessage msg, ITransportHeaders headers, Stream stream) { throw new NotSupportedException(); } public Stream GetResponseStream(IServerResponseChannelSinkStack sinkStack, Object state, IMessage msg, ITransportHeaders headers) { return nextSink.GetResponseStream(sinkStack, state, msg, headers); } } // EncryptedServerChannelSink Листинг 8.2. Труба шифрования клиента должна зашифровать этот поток перед отправкой на сервер и расшифровать при получении ответа сервера.



class EncryptedClientChannelSink: IClientChannelSink { public IDictionary Properties { get { return null; } } private IClientChannelSink nextSink; public IClientChannelSink NextChannelSink { get { return nextSink; } } private SymmetricEncryptor encryptor; public EncryptedClientChannelSink(SymmetricEncryptor channelEncryptor, IClientChannelSink next) { encryptor = channelEncryptor; nextSink = next; } public void ProcessMessage( IMessage message, ITransportHeaders requestHeaders, Stream requestStream, out ITransportHeaders responseHeaders, out Stream responseStream) { MemoryStream encryptedStream = new MemoryStream(); Stream serverStream = null; // Зашифровать запрос клиента encryptor.Encrypt(requestStream, encryptedStream); encryptedStream.Position = 0; nextSink.ProcessMessage(message, requestHeaders, encryptedStream, out responseHeaders, out serverStream); serverStream = Utils.ReadAllStream(serverStream); // Расшифровать ответ сервера responseStream = new MemoryStream(); encryptor.Decrypt(serverStream, responseStream); responseStream.Position = 0; } public Stream GetRequestStream(IMessage msg, ITransportHeaders headers) { return null; } public void AsyncProcessRequest(IClientChannelSinkStack sinkStack, IMessage message, ITransportHeaders headers, Stream stream) { throw new NotSupportedException(); } public void AsyncProcessResponse( IClientResponseChannelSinkStack sinkStack, object state, ITransportHeaders headers, Stream stream) { throw new NotSupportedException(); } } // EncryptedClientChannelSink Листинг 8.3. Вспомогательный класс Utils служит для считывания всего содержимого потока, не поддерживающего свойство Length.

public static class Utils { public static MemoryStream ReadAllStream(Stream input) { byte[] buffer = new byte [1024]; MemoryStream output = new MemoryStream(); while (true) { int read = input.Read(buffer, 0, buffer.Length); output.Write(buffer, 0, read); if (read < buffer.Length) break; } output.Position = 0; return output; } } } // SevaRemotingEncrypted.cs Для компиляции указанных файлов можно использовать следующий make-файл.



makefile : Client.exe SevaRemotingEncrypted.dll common = Seva*.cs Client.exe : Client.cs RemoteService.dll csc /r:RemoteService.dll Client.cs RemoteService.dll : RemoteService.cs csc /t:library RemoteService.cs SevaRemotingEncrypted.dll: $(common) csc /out:SevaRemotingEncrypted.dll /t:library $(common) Как видно из данного файла, при компиляции клиент не ссылается на сборку с модифицированными поставщиками труб. Регистрация данных поставщиков труб происходит с помощью файлов конфигурации.

<configuration> <system.runtime.remoting> <application> <client> <wellknown type="RemoteService.RemoteServiceTest, RemoteService" url="tcp://localhost:10020/endpoint" /> </client> <channels> <channel ref ="tcp"> <clientProviders> <formatter ref="binary"/> <provider type="Seva.Remoting.Encryption. EncryptedClientChannelSinkProvider, SevaRemotingEncrypted" key="secret.key" /> </clientProviders> </channel> </channels> </application> </system.runtime.remoting> </configuration> Таким образом, архитектура .NET Remoting позволяет встраивать собственные трубы в канал Remoting, причем для этого не требуется что либо менять в коде клиента или сервера. Однако даже в простейшем случае реализация собственных поставщиков и труб выглядит достаточно громоздко.


Конфигурирование среды NET Remoting


Среда Remoting имеет штатную возможность описания своей конфигурации в XML-файле. К конфигурации среды Remoting относятся:

используемый класс канала;параметры канала (например, используемый сервером порт TCP);используемый класс форматирования;адрес и тип используемого удаленного объекта.

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


Рис. 8.4.  Использование конфигурации Remoting

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

// server.cs using System; using System.Runtime.Remoting; using System.Configuration; public class Server { public static void Main(string[] args) { string config = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile; Console.WriteLine(config); RemotingConfiguration.Configure(s); // Для .NET Framework 2.0 рекомендуется использовать // RemotingConfiguration.Configure(s, false); Console.WriteLine("Нажмите ENTER для завершения работы сервера."); Console.ReadLine(); } }

Как видно из приведенного примера, назначение такого сервера – настройка среды Remoting на основе содержимого стандартного конфигурационного файла программы.
Не претендуя на полное изложение всех возможностей конфигурационного файла среды Remoting, рассмотрим следующий пример конфигурации сервера.

<configuration> <system.runtime.remoting> <application> <service> <wellknown mode="SingleCall" type="RemoteTest.TestService, RemoteTestService" objectUri="endpoint"/> </service> <channels> <channel ref="tcp" port="2080" /> </channels> </application> </system.runtime.remoting> </configuration> В разделе <application> <service> указываются публикуемые сервером объекты. Для каждого из них указывается модель вызова, имя класса (через запятую идет полное или краткое имя сборки, которая содержит класс), и уникальный URI, связываемый с данным классом. Объекты, активируемые сервером, описываются как

<wellknown mode="SingleCall" type=... для режима единственного вызова или

<wellknown mode="Singletone" type=... для режима одного экземпляра. Объекты, активируемые по запросу клиента, описываются иначе.

<activated type=... Раздел <application><channels> сервера в простейшем случай содержит идентификатор канала и номер порта. Стандартные каналы имеют идентификаторы, соответствующие названиям их протоколов. В файле конфигурации могут использоваться модифицированные или вновь созданные каналы, как будут показано далее.

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

<configuration> <system.runtime.remoting> <application> <client> <wellknown mode="SingleCall" type="RemoteTest.TestService, RemoteTestService" objectUri="tcp://127.0.0.1:2080/endpoint" /> </client > </application> </system.runtime.remoting> </configuration> Раздел <application><client> включает описание используемых удаленных объектов. Следует отметить, что один и тот же файл может содержать и разделы <service>, и <client>.


Программа при этом будет настроена как сервер и клиент Remoting одновременно.

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

using System; using System.Runtime.Remoting; using RemotingTest; public class Client { public static void Main(string[] args) { string config = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile; Console.WriteLine(config); RemotingConfiguration.Configure(config); // Для объекта единственого вызова в следующей строчке будут // созданы посредники, конструктор самого объекта не вызывается TestService service = new RemotingTest.TestService(); // При удаленном вызове на сервере будет сконструирован новый объект Console.WriteLine(service.Sum(2, 2)); } } Ниже приведен код самого маршализируемого по ссылке объекта. Конструктор и деструктор объекта выводят сообщения в консоль, чтобы продемонстрировать моменты создания и удаления объекта на сервере.

// TestService.cs using System; namespace RemotingTest { public class TestService : MarshalByRefObject { public TestService() { Console.WriteLine("[ctor]"); } ~TestService() { Console.WriteLine("[dtor]"); } public int Sum(int a, int b) { return a + b; } } } Нижеследующий make файл позволяет собрать все приведенные выше файлы. Следует отметить, что в некоторых реализациях CLI для компиляции следует использовать ссылку на сборку System.Runtime.Remoting.dll. В .NET Framework 2.0 эта ссылка добавлена в стандартный файл параметров компилятора C# (csc.rsp).

makefile : Server.exe Client.exe Server.exe : Server.cs csc Server.cs Client.exe : Client.cs RemoteTestService.dll csc /r:RemoteTestService.dll Client.cs RemoteTestService.dll: TestService.cs csc /out:RemoteTestService.dll /t:library TestService.cs

private IServerChannelSinkProvider next; private SymmetricEncryptor


class EncryptedServerChannelSinkProvider : IServerChannelSinkProvider { private IServerChannelSinkProvider next; private SymmetricEncryptor encryptor; public IServerChannelSinkProvider Next { get { return next; } set { next = value; } } public EncryptedServerChannelSinkProvider(IDictionary properties, ICollection providerData) { string keyFile = (string) properties["key"]; Console.WriteLine("Server key: [{0}]", keyFile); encryptor = new SymmetricEncryptor(keyFile); } // Создание трубы канала public IServerChannelSink CreateSink(IChannelReceiver channel) { IServerChannelSink nextSink = Next.CreateSink(channel); return new EncryptedServerChannelSink(channel, encryptor, nextSink); } // Обязательный метод интерфейса public void GetChannelData(IChannelDataStore channelData) { } }
Листинг 8.1.
Закрыть окно




class EncryptedServerChannelSinkProvider : IServerChannelSinkProvider
{
private IServerChannelSinkProvider next;
private SymmetricEncryptor encryptor;

public IServerChannelSinkProvider Next
{
get { return next; }
set { next = value; }
}

public EncryptedServerChannelSinkProvider(IDictionary properties,
ICollection providerData)
{
string keyFile = (string) properties["key"];
Console.WriteLine("Server key: [{0}]", keyFile);
encryptor = new SymmetricEncryptor(keyFile);
}

// Создание трубы канала
public IServerChannelSink CreateSink(IChannelReceiver channel)
{
IServerChannelSink nextSink = Next.CreateSink(channel);
return new EncryptedServerChannelSink(channel, encryptor, nextSink);
}

// Обязательный метод интерфейса
public void GetChannelData(IChannelDataStore channelData)
{
}
}

Stream requestStream, out IMessage responseMsg,


// Метод синхронной обработки сообщения
public ServerProcessing ProcessMessage(IServerChannelSinkStack sinkStack,
IMessage requestMsg, ITransportHeaders requestHeaders,
Stream requestStream, out IMessage responseMsg,
out ITransportHeaders responseHeaders, out Stream responseStream)
{
Stream plainStream = null;
MemoryStream decodedStream = new MemoryStream();
requestStream = Utils.ReadAllStream(requestStream);

// Расшифровать запрос клиента
encryptor.Decrypt(requestStream, decodedStream);
decodedStream.Position = 0;

ServerProcessing result = nextSink.ProcessMessage(sinkStack,
requestMsg, requestHeaders, decodedStream, out responseMsg,
out responseHeaders, out plainStream);

// Зашифровать ответ сервера
responseStream = new MemoryStream();
encryptor.Encrypt(plainStream, responseStream);
responseStream.Position = 0;
//
return result;
}

public void AsyncProcessResponse(IServerResponseChannelSinkStack sinkStack,
Object state, IMessage msg, ITransportHeaders headers, Stream stream)
{
throw new NotSupportedException();
}

public Stream GetResponseStream(IServerResponseChannelSinkStack sinkStack,
Object state, IMessage msg, ITransportHeaders headers)
{
return nextSink.GetResponseStream(sinkStack, state, msg, headers);
}

} // EncryptedServerChannelSink


// Метод синхронной обработки сообщения public ServerProcessing ProcessMessage(IServerChannelSinkStack sinkStack, IMessage requestMsg, ITransportHeaders requestHeaders, Stream requestStream, out IMessage responseMsg, out ITransportHeaders responseHeaders, out Stream responseStream) { Stream plainStream = null; MemoryStream decodedStream = new MemoryStream(); requestStream = Utils.ReadAllStream(requestStream); // Расшифровать запрос клиента encryptor.Decrypt(requestStream, decodedStream); decodedStream.Position = 0; ServerProcessing result = nextSink.ProcessMessage(sinkStack, requestMsg, requestHeaders, decodedStream, out responseMsg, out responseHeaders, out plainStream); // Зашифровать ответ сервера responseStream = new MemoryStream(); encryptor.Encrypt(plainStream, responseStream); responseStream.Position = 0; // return result; } public void AsyncProcessResponse(IServerResponseChannelSinkStack sinkStack, Object state, IMessage msg, ITransportHeaders headers, Stream stream) { throw new NotSupportedException(); } public Stream GetResponseStream(IServerResponseChannelSinkStack sinkStack, Object state, IMessage msg, ITransportHeaders headers) { return nextSink.GetResponseStream(sinkStack, state, msg, headers); } } // EncryptedServerChannelSink
Листинг 8.2.
Закрыть окно





out ITransportHeaders responseHeaders, out Stream


class EncryptedClientChannelSink: IClientChannelSink
{
public IDictionary Properties
{
get { return null; }
}

private IClientChannelSink nextSink;

public IClientChannelSink NextChannelSink
{
get { return nextSink; }
}

private SymmetricEncryptor encryptor;

public EncryptedClientChannelSink(SymmetricEncryptor channelEncryptor,
IClientChannelSink next)
{
encryptor = channelEncryptor;
nextSink = next;
}

public void ProcessMessage(IMessage message,
ITransportHeaders requestHeaders, Stream requestStream,
out ITransportHeaders responseHeaders, out Stream responseStream)
{
MemoryStream encryptedStream = new MemoryStream();
Stream serverStream = null;

// Зашифровать запрос клиента
encryptor.Encrypt(requestStream, encryptedStream);
encryptedStream.Position = 0;

nextSink.ProcessMessage(message, requestHeaders, encryptedStream,
out responseHeaders, out serverStream);

serverStream = Utils.ReadAllStream(serverStream);

// Расшифровать ответ сервера
responseStream = new MemoryStream();
encryptor.Decrypt(serverStream, responseStream);
responseStream.Position = 0;
}

public Stream GetRequestStream(IMessage msg, ITransportHeaders headers)
{
return null;
}
public void AsyncProcessRequest(IClientChannelSinkStack sinkStack,
IMessage message, ITransportHeaders headers, Stream stream)
{
throw new NotSupportedException();
}

public void AsyncProcessResponse(
IClientResponseChannelSinkStack sinkStack, object state,
ITransportHeaders headers, Stream stream)
{
throw new NotSupportedException();
}
} // EncryptedClientChannelSink


class EncryptedClientChannelSink: IClientChannelSink { public IDictionary Properties { get { return null; } } private IClientChannelSink nextSink; public IClientChannelSink NextChannelSink { get { return nextSink; } } private SymmetricEncryptor encryptor; public EncryptedClientChannelSink(SymmetricEncryptor channelEncryptor, IClientChannelSink next) { encryptor = channelEncryptor; nextSink = next; } public void ProcessMessage( IMessage message, ITransportHeaders requestHeaders, Stream requestStream, out ITransportHeaders responseHeaders, out Stream responseStream) { MemoryStream encryptedStream = new MemoryStream(); Stream serverStream = null; // Зашифровать запрос клиента encryptor.Encrypt(requestStream, encryptedStream); encryptedStream.Position = 0; nextSink.ProcessMessage(message, requestHeaders, encryptedStream, out responseHeaders, out serverStream); serverStream = Utils.ReadAllStream(serverStream); // Расшифровать ответ сервера responseStream = new MemoryStream(); encryptor.Decrypt(serverStream, responseStream); responseStream.Position = 0; } public Stream GetRequestStream(IMessage msg, ITransportHeaders headers) { return null; } public void AsyncProcessRequest(IClientChannelSinkStack sinkStack, IMessage message, ITransportHeaders headers, Stream stream) { throw new NotSupportedException(); } public void AsyncProcessResponse( IClientResponseChannelSinkStack sinkStack, object state, ITransportHeaders headers, Stream stream) { throw new NotSupportedException(); } } // EncryptedClientChannelSink
Листинг 8.3.
Закрыть окно





в главе про MSMQ пространство


// Файл SevaRemotingMsmq.cs
using System;
using System.IO;
using System.Collections;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Serialization.Formatters.Binary;
using System.Messaging;
using System.Text.RegularExpressions;
// использовать описанное в главе про MSMQ пространство имен
// для работы c очередями сообщений
using Seva.Msmq;
// Набор классов для одностороннего удаленного вызова
// на основе Remoting / MSMQ
namespace Seva.Remoting.MsmqChannel
{
// константы с именами свойств сообщения
public class MessageProperties
{
public const string Uri = "__Uri";
public const string ObjectUri = "__ObjectUri";
}


// Файл SevaRemotingMsmq.cs using System; using System.IO; using System.Collections; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Messaging; using System.Runtime.Serialization.Formatters.Binary; using System.Messaging; using System.Text.RegularExpressions; // использовать описанное в главе про MSMQ пространство имен // для работы c очередями сообщений using Seva.Msmq; // Набор классов для одностороннего удаленного вызова // на основе Remoting / MSMQ namespace Seva.Remoting.MsmqChannel { // константы с именами свойств сообщения public class MessageProperties { public const string Uri = "__Uri"; public const string ObjectUri = "__ObjectUri"; }
Листинг 8.4.
Закрыть окно





метод интерфейса IChannelReceiver, возваращает все


sink = ChannelServices.CreateServerChannelSinkChain(null, this);

// создание стека труб
stack = new ServerChannelSinkStack();
stack.Push(sink, null);
// Начать ожидание собщений в очереди
StartListening(null);
}
// метод интерфейса IChannelReceiver, возваращает все URL для данного URI
public virtual string[] GetUrlsForUri(string objectURI)
{
return new string[] {objectURI};
}

// обработка пришедшего сообщения MSMQ
private IMessage OnReceive(Object sender, IMessage request,
MessageQueue queueResponse)
{
return ProcessMessage(request);
}

// методы, управляющие прослушивание канала
public void StartListening(Object data)
{
msmqServer.BeginReceive();
}

public void StopListening(Object data)
{
msmqServer.EndReceive();
}


sink = ChannelServices.CreateServerChannelSinkChain(null, this); // создание стека труб stack = new ServerChannelSinkStack(); stack.Push(sink, null); // Начать ожидание собщений в очереди StartListening(null); } // метод интерфейса IChannelReceiver, возваращает все URL для данного URI public virtual string[] GetUrlsForUri(string objectURI) { return new string[] {objectURI}; } // обработка пришедшего сообщения MSMQ private IMessage OnReceive(Object sender, IMessage request, MessageQueue queueResponse) { return ProcessMessage(request); } // методы, управляющие прослушивание канала public void StartListening(Object data) { msmqServer.BeginReceive(); } public void StopListening(Object data) { msmqServer.EndReceive(); }
Листинг 8.5.
Закрыть окно





метод выделяет из URL идентификатор


public static class Utils
{
// метод выделяет из URL идентификатор объекта и трубы
public static string ParseUrl(string url, string channelName,
out string objectUri)
{
objectUri = null;

Regex re = new Regex(@"^" + channelName + @"\:\/\/(.+)$");
Match m = re.Match(url);
if (!m.Success) return null;

string sinkUri = m.Groups[1].Value;
Utils.ParseUrl(url, out objectUri);
return sinkUri;
}
// метод выделяет из URL идентификатор объекта и путь к очереди MSMQ
public static string ParseUrl(string url, out string objectUri)
{
string queuePath = null;
objectUri = null;

Regex r = new Regex(@"^(.*)(\\|\/)(.+)$");
Match m = r.Match(url);
if (m.Success)
{
objectUri = m.Groups[3].Value;
queuePath = m.Groups[1].Value;
}
else
{
throw new Exception("Не найден идентификатор объекта в " + url);
}
return queuePath;
}
} // Seva.Remoting.MsmqChannel.Utils
} // Seva.Remoting.MsmqChannel
// Файл SevaRemotingMsmq.cs


public static class Utils { // метод выделяет из URL идентификатор объекта и трубы public static string ParseUrl(string url, string channelName, out string objectUri) { objectUri = null; Regex re = new Regex(@"^" + channelName + @"\:\/\/(.+)$"); Match m = re.Match(url); if (!m.Success) return null; string sinkUri = m.Groups[1].Value; Utils.ParseUrl(url, out objectUri); return sinkUri; } // метод выделяет из URL идентификатор объекта и путь к очереди MSMQ public static string ParseUrl(string url, out string objectUri) { string queuePath = null; objectUri = null; Regex r = new Regex(@"^(.*)(\\|\/)(.+)$"); Match m = r.Match(url); if (m.Success) { objectUri = m.Groups[3].Value; queuePath = m.Groups[1].Value; } else { throw new Exception("Не найден идентификатор объекта в " + url); } return queuePath; } } // Seva.Remoting.MsmqChannel.Utils } // Seva.Remoting.MsmqChannel // Файл SevaRemotingMsmq.cs
Листинг 8.6.
Закрыть окно





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


Одним из недостатков стандартных каналов среды Remoting является плохая поддержка асинхронного взаимодействия. Даже при вызове одностороннего метода удаленного объекта сервер и клиент Remoting должны быть запущены одновременно, поскольку оба канала требуют функционирующего соединения по TCP/IP. Поэтому в качестве примера полезной модификации среды Remoting можно рассмотреть создание собственного канала на основе службы MSMQ. В отличие от стандартных каналов TcpChannel и HttpChannel, данный канал обеспечивает асинхронный обмен между клиентом и сервером. Ограничимся реализацией канала для одностороннего удаленного вызова.

На рисунке 8.6 приведена схема созданного канала. Следует отметить, что поскольку канал полностью базируется на промежуточной среде MSMQ, данное решение является настолько же безопасным, насколько безопасна среда MSMQ.


Рис. 8.6.  Простейший канал Remoting, использующий MSMQ

Данный канал будет позволять асинхронный вызов методов с атрибутом System.Runtime.Remoting.Messaging.OneWayAttribute, причем время работы клиента и сервера может не совпадать.

Вновь создаваемый канал состоит из двух основных классов: клиентcкой части MsmqChannelSender, реализующей интерфейс IChannelSender, и серверной части MsmqChannelReceiver, реализующей интерфейс IChannelReceiver. Указанные классы используют описанные в разделе MSMQ классы MsmqClient и MsmqServer, причем необходимо использовать бинарное форматирование сообщений MSMQ.

// Файл SevaRemotingMsmq.cs using System; using System.IO; using System.Collections; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Messaging; using System.Runtime.Serialization.Formatters.Binary; using System.Messaging; using System.Text.RegularExpressions; // использовать описанное в главе про MSMQ пространство имен // для работы c очередями сообщений using Seva.Msmq; // Набор классов для одностороннего удаленного вызова // на основе Remoting / MSMQ namespace Seva.Remoting.MsmqChannel { // константы с именами свойств сообщения public class MessageProperties { public const string Uri = "__Uri"; public const string ObjectUri = "__ObjectUri"; } Листинг 8.4.

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

msmq://.\Private$\remoting_queue\endpoint где .\Private$\remoting_queue – имя очереди (в данном случае – частной, локальной), а endpoint – идентификатор удаленного объекта.

// базовый класс для MsmqSender и MsmqReceiver public class MsmqBase: MarshalByRefObject, IChannel { // обязательные для интерфейса IChannel свойства public int ChannelPriority { get { return 1;} } public string ChannelName { get{ return "msmq";} } // разделяет URL на идентификаторы для объекта и канала public string Parse(string url, out string objectUri) { return Utils.ParseUrl(url, out objectUri); } } Конструктору класса MsmqChannelSender в параметре clientSinkProvider передается дополнительный поставщик, описанный в файле конфигурации, который следует вставить в цепочку поставщиков. Однако в данном случае клиентский канал может состоять из единственной цепочки сообщения, которая передает сообщение в MSMQ. Хотя в соответствии с идеологией Remoting следовало бы создать отдельную трубу форматирования, и отдельную – транспортную, для простоты примера можно ограничиться одной трубой без поддержки дополнительных поставщиков.

// канал для клиента public class MsmqChannelSender: MsmqBase, IChannelSender { public MsmqChannelSender(IDictionary properties, IClientChannelSinkProvider clientSinkProvider) { // поскольку в данном примере сообщение сразу направляется в MSMQ, // то дополнительные поставщики не поддерживаются if (clientSinkProvider!=null) { throw new NotSupportedException( "Дополнительные поставщики не поддерживаются."); } } Метод CreateMessageSink, единственный в интерфейсе IChannelSender, создает трубу сообщения.

public IMessageSink CreateMessageSink(string url, object channelData, out string objectUri) { // выделить из URL идентификатор для удаленного объекта string remoteobjectUrl = Utils.ParseUrl(url, ChannelName, out objectUri); if (remoteobjectUrl == null) return null; // создание трубы return new MsmqClientChannelSink(remoteobjectUrl); } } // Seva.Remoting.MsmqChannel.Msmq.MsmqSender Класс MsmqClientChannelSink – транспортная труба клиента, передающая сериализованное сообщение в MSMQ.



// клиентская часть трубы канала public class MsmqClientChannelSink: IMessageSink { // клиент MSMQ, передающий объекты с интерфейсом IMessage // ответы данный объект получать не будет, но указать // отличный от void тип ответов необходимо private MsmqClient<IMessage, IMessage> msmqClient; // путь к очереди MSMQ и идентификатор удаленного объекта (endpoint) private string queuePath = null; private string objectUri = null; Конструктор клиентской трубы канала должен создать клиента MSMQ для передачи сообщений.

public MsmqClientChannelSink(string remoteObjectUri) { // выделение из URL пути к очереди и адреса объекта queuePath = Utils.ParseUrl(remoteObjectUri, out objectUri); // создание клиента MSMQ, не ожидающего ответов от сервера // и использующего бинарный класс форматирования msmqClient = new MsmqClient<IMessage, IMessage>(queuePath, null, QueueFormatter.Binary); } public IMessage SyncProcessMessage(IMessage msg) { throw new NotSupportedException( "Поддерживаются только методы с OneWayAttribute."); } Метод AsyncProcessMessage используется для реализации асинхронного вызова. В данном примере он должен передать сообщение Remoting клиенту MSMQ.

public IMessageCtrl AsyncProcessMessage(IMessage message, IMessageSink replySink) { // свойство сообщения c адресом удаленного объекта message.Properties[MessageProperties.ObjectUri] = objectUri; // передача сообщения клиенту MSMQ для его отсылки серверу msmqClient.Send(message); return null; } // данная труба - первая и последняя в цепочке public IMessageSink NextSink { get { return null; } } } // Seva.Remoting.MsmqChannel.MsmqClientChannelSink Принимающая сторона состоит из канала MsmqChannelReceiver.

public class MsmqChannelReceiver: MsmqBase, IChannelReceiver { // Класс-сервер MSMQ для приема сообщений Remoting private MsmqServer<IMessage, IMessage> msmqServer; // Серверная труба канала private IServerChannelSink sink; // Стек труб на стороне сервера private ServerChannelSinkStack stack; // необходимое для реализации интерфейса свойство public object ChannelData { get { return null; } } Десериализация сообщения осуществляется в классе MsmqServer, поэтому цепочка труб сервера состоит только из стандартной трубы диспетчеризации.


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

public MsmqChannelReceiver(IDictionary properties, IServerChannelSinkProvider serverSinkProvider) { // создать сервер MSMQ msmqServer = new MsmqServer<IMessage, IMessage>( properties["queue"].ToString(), QueueFormatter.Binary); msmqServer.ProcessMessage += OnReceive; if (serverSinkProvider!=null) { throw new NotSupportedException( "Поставщики не поддерживаются."); } Поскольку начальной обработкой пришедших сообщений занимается класс MsmqReceiver, а десериализацией – класс MsmqServer, то в конструкторе достаточно создать стандартную трубу диспетчеризации сообщений на сервере.

sink = ChannelServices.CreateServerChannelSinkChain(null, this); // создание стека труб stack = new ServerChannelSinkStack(); stack.Push(sink, null); // Начать ожидание собщений в очереди StartListening(null); } // метод интерфейса IChannelReceiver, возваращает все URL для данного URI public virtual string[] GetUrlsForUri(string objectURI) { return new string[] {objectURI}; } // обработка пришедшего сообщения MSMQ private IMessage OnReceive(Object sender, IMessage request, MessageQueue queueResponse) { return ProcessMessage(request); } // методы, управляющие прослушивание канала public void StartListening(Object data) { msmqServer.BeginReceive(); } public void StopListening(Object data) { msmqServer.EndReceive(); } Листинг 8.5. Метод ProcessMessage обрабатывает пришедшее сообщение .NET Remoting. В данном классе достаточно передать его дальше в трубу канала для передачи, в итоге, диспетчеру сообщений Remoting.

private IMessage ProcessMessage(IMessage request) { IMessage response = null; // если в сообщении не указан URI объекта, то ничего не делать object uri = request.Properties[MessageProperties.ObjectUri]; if (uri == null) return null; string url = uri.ToString(); // Необходимо заполнить свойство сообщения __URI request.Properties[MessageProperties.Uri] = uri; // Передача сообщения в трубу канала Stream responseStream = null; ITransportHeaders responseHeaders = null; sink.ProcessMessage(stack, request, null, null, out response, out responseHeaders, out responseStream); return response; } } // Seva.Remoting.MsmqChannel.MsmqReceiver Класс Utils содержит вспомогательные статические методы для разбора URL.



public static class Utils { // метод выделяет из URL идентификатор объекта и трубы public static string ParseUrl(string url, string channelName, out string objectUri) { objectUri = null; Regex re = new Regex(@"^" + channelName + @"\:\/\/(.+)$"); Match m = re.Match(url); if (!m.Success) return null; string sinkUri = m.Groups[1].Value; Utils.ParseUrl(url, out objectUri); return sinkUri; } // метод выделяет из URL идентификатор объекта и путь к очереди MSMQ public static string ParseUrl(string url, out string objectUri) { string queuePath = null; objectUri = null; Regex r = new Regex(@"^(.*)(\\|\/)(.+)$"); Match m = r.Match(url); if (m.Success) { objectUri = m.Groups[3].Value; queuePath = m.Groups[1].Value; } else { throw new Exception("Не найден идентификатор объекта в " + url); } return queuePath; } } // Seva.Remoting.MsmqChannel.Utils } // Seva.Remoting.MsmqChannel // Файл SevaRemotingMsmq.cs Листинг 8.6. Для компиляции сборки с классом канала можно использовать make файл следующего содержания.

makefile : SevaRemotingMsmq.dll common = SevaRemotingMsmq.cs SevaMsmq.cs SevaRemotingMsmq.dll: $(common) csc /out:SevaRemotingMsmq.dll /t:library $(common) Для использования созданного канала необходимо описать его в файле конфигурации, например, указав его полное имя в разделе <channel>.

<configuration> <system.runtime.remoting> <application> <client> <wellknown type="RemoteService.RemoteServiceOneWay, RemoteService" url="msmq://.\Private$\remoting_queue\service" /> </client> <channels> <channel type="Seva.Remoting.MsmqChannel.MsmqChannelSender, SevaRemotingMsmq"/> </channels> </application> </system.runtime.remoting> </configuration> Пример файла конфигурации сервера.

<configuration> <system.runtime.remoting> <application name="JobServer"> <service> <wellknown mode="SingleCall" type="RemoteService.RemoteServiceOneWay, RemoteService" objectUri="service" /> </service> <channels> <channel type="Seva.Remoting.MsmqChannel.MsmqChannelReceiver, SevaRemotingMsmq" queue = ".\Private$\remoting_queue"/> </channels> </application> </system.runtime.remoting> </configuration> Как видно из приведенного примера, среда .NET Remoting имеет уникальные возможности по модификации своей структуры разработчиком.Но даже в рассмотренном простейшем случае такие изменения требуют как минимум глубокого понимания структуры .NET Remoting.


Веб-службы в NET Remoting


После конфигурирования среда Remoting обеспечивает прозрачное использование удаленных объектов. Однако, для такого прозрачного использования клиент должен иметь доступ к сборке с самим объектом, сборка с его интерфейсом недостаточна. Для решения данной проблемы можно либо пожертвовать прозрачностью, воспользовавшись классом System.Activator, или организовать веб службу Remoting на основе SOAP, что позволяет создать посредника на клиенте по спецификации веб службы в формате WSDL. Вариант с веб службой представляется более разумным – если клиент не имеет доступа к сборке удаленного объекта, то, вероятно, клиент и сервер находятся на разных предприятиях, и видимо речь идет о взаимодействии вне доверенной сети.

При использовании класса форматирования SoapFormatter, канала HttpChannel и активируемых сервером объектов среда Remoting позволяет организовать веб службу на основе SOAP, WSDL и кодирования SOAP-RPC (а не SOAP Document, в отличие от служб ASP.NET). В этом случае нет необходимости иметь сборку на клиенте, поскольку, используя методы класса System.Runtime.Remoting.MetadataServices.MetaData, можно получить сборку из описания веб-службы. Для упрощения данной задачи существует утилита soapsuds.exe. Нижеследующая команда записывает в текущий каталог файл с классом, который является наследником System.Runtime.Remoting.Services.RemotingClientProxy, имеет интерфейс удаленного объекта и может быть использован для сборки клиента Remoting.

soapsuds.exe -url:http://localhost:2080/endpoint?wsdl -gc

Следует отметить, что в файле конфигурации клиента по прежнему должно быть указано имя сборки на сервере (оно совпадает с именем файла, созданного утилитой soapsuds.exe). Созданный утилитой soapsuds.exe посредник не будет использоваться в ходе выполнения программы, если используется конфигурация Remoting. Если же клиент не использует метод RemotingConfiguration.Configure, то будет использован посредник, который попытается использовать тот же сервис, который был указан при его создании в его конструкторе.
Поскольку служба Remoting создает стандартную веб службу, то можно воспользоваться и описанной ранее утилитой wsdl.exe для создания клиента веб службы.

Важной особенностью среды Remoting является возможность использования в качестве сервера IIS, при этом клиент должен использовать канал HTTP. Конфигурация сервера в этом случае храниться в файле web.config в каталоге виртуального сервера, а в его подкаталоге bin размещаются сборки с маршализируемыми по ссылке объектами. Среда Remoting не содержит штатных средств обеспечения безопасности, и единственным практическим способом решения данной проблемы является именно использование IIS. Это позволяет использовать авторизацию и защиту передаваемых данных с помощью SSL. При использовании SSL, кроме соответствующей настройки сервера, клиент должен использовать префикс объектов https://.

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

RemoteObject object = new RemoteObject(); // Параметры идентификации клиента IDictionary properties = ChannelServices.GetChannelSinkProperties(object); properties["username"] = username; properties["password"] = password; // Удаленный вызов object.Do(); При использовании IIS можно применять декларативную безопасность с помощью атрибута PrincipalPermissionAttribute, или императивную безопасность, когда метод объекта проверяет, принадлежит ли авторизованный пользователь к той или иной группе пользователей.

IPrincipal principal = System.Threading.Thread.CurrentPrincipal; if (!principal.IsInRole(Environment.MachineName + @"\SomeGroup")) { throw new UnauthorizedAccessException("Нет прав доступа."); } При использовании в пределах домена или группы дружественных доменов Active Directory служба IIS позволяет использовать авторизацию операционной системы, для чего клиенту достаточно указать в файле конфигурации примерно следующее.

<channel ref="http" useDefaultCredentials="true" /> Таким образом, при использовании внутри Active Directory вместе со службой IIS среда Remoting позволяет легко создать безопасную систему на основе авторизации Windows (протокол Kerberos), сертификатов сервера и ролей пользователей.При использовании вне Active Directory вместо идентификации Windows можно использовать простую идентификацию.


Введение в среду NET Remoting


В отличие от других промежуточных сред, рассматриваемых в данном курсе, среда .NET Remoting создавалась специально для платформы .NET. Среда Remoting является универсальным средством доступа к удаленным объектам, которое может быть приспособлено к широкому классу задач взаимодействия компонент распределенного приложения. Благодаря своей расширяемой архитектуре среда Remoting может быть доработана для использования с практически любыми каналами передачи данных. Уже со штатными средствами область применения среды Remoting охватывает как реализацию публичных веб служб в интернете на основе протокола SOAP, так и высокоскоростной обмен в доверенной сети на основе бинарного форматирования (рис. 8.1).


Рис. 8.1.  Различные сценарии использования среды Remoting

Кроме использования в качестве самостоятельной промежуточной среды, .NET Remoting также используется для организации взаимодействия .NET Framework и среды COM+ в рассмотренной ранее промежуточной среде .NET Enterprise Services.

С точки зрения среды Remoting, все классы объектов среды CLR делятся на три вида.

Классы, маршализируемые по значению. Объекты этих классов могут копироваться между доменами приложений, если для них определены операции сериализации и десериализации. В результате десериализации создается копия объекта, не связанная с его оригиналом. Следует отметить, что сериализация и десериализация некоторых объектов может быть осуществлена не со всеми классами форматирования, как указано в теме о сериализации. Классы, маршализируемые по ссылке. Такие классы наследуются от класса System.MarshalByRefObject. Объекты этих классов не покидают свой домен приложения, но на стороне клиента создается посредник, позволяющий осуществлять удаленный доступ к объекту. Именно экземпляры таких классов могут использоваться как удаленные объекты при помощи среды Remoting. Remoting поддерживает все три вида удаленных объектов: объекты единственного вызова, единственного экземпляра и объекты, активируемые по запросу клиента. Немаршализируемые классы, а так же классы без определенной процедуры сериализации или отмеченные, как несериализуемые.
Объекты этих классов недоступны вне своего домена приложения. Это, в частности, касается и классов исключений – выбрасываемые на сервере Remoting исключения должны маршализироваться по значению для передачи клиенту. Таким образом, среда Remoting является средством поддержки удаленных вызовов между доменами приложений CLR. В отличие от технологий MSMQ и COM+, среда Remoting не привязана жестко к каким либо службам операционной системы Microsoft Windows. Благодаря этому ответственные за реализацию среды Remoting пространства имен являются стандартизированной частью CLI, в отличие от пространств System.EnterpriseServices и System.Messaging. Таким образом, одним из достоинств Remoting является переносимость как между различными реализациями CLI фирмы Microsoft (.NET Framework, .NET Compact Framework), а также независимыми реализациями CLI (в настоящий момент к ним относятся Mono Project и DotGNU Portable.NET).
Реализация промежуточной среды Remoting только силами исполняемой среды CLI привела и к определенным недостаткам. Возможности Remoting как промежуточной среды достаточно скромны по сравнению со средой Enterprise Services. Remoting не имеет аналогов большинства сервисов среды COM+, в первую очередь это касается распределенных транзакций. В самой среде Remoting отсутствуют так же какие либо средства обеспечения безопасности, однако эта проблема может быть решена сервером носителем среды Remoting, в роли которого может выступать служба Internet Information Services (IIS).

Выводы по использованию NET Remoting


Отличительной особенностью среды Remoting является возможность отделить объекты распределенной системы от способа передачи сообщений удаленного вызова. Одно и тоже распределенное приложение может работать с различными каналами передачи данных и способами форматирования, включая локальный канал IpсChannel. Для смены канала достаточно изменить только файлы конфигурации, используемые на клиентах и серверах системы. Однако, при необходимости использования механизмов безопасности на уровне промежуточной среды это преимущество исчезает – поскольку выбор каналов связи ограничен возможностями IIS.

Рассмотрим среду .NET Remoting с точки зрения требований к распределенным системам. При этом следует рассмотреть два типичных случая использования среды Remoting – как веб службы (далее – Remoting-Soap) и с использованием бинарного форматирования (далее – Remoting-Binary).

Открытость. Среда Remoting-Binary может быть использована только в пределах распределенного приложения, использующего CLI, причем для удобства работы нужно, чтобы клиент имел доступ к сборке с классом удаленного объекта. Среда Remoting Soap является открытой и гетерогенной, основанной на общепринятых стандартах SOAP и WSDL. Масштабируемость. Приложение, использующее .NET Remoting, являются плохо масштабируемым, если используются активируемые клиентом объекты. Для объектов же единственного вызова существует возможность организовать балансировку нагрузок – при ее поддержке сетевой инфраструктурой или при использовании нестандартного посредника. Поддержание логической целостности данных. Среда Remoting не содержит штатных средств поддержки распределенных транзакций. Устойчивость. Среда Remoting не содержит средств повышения устойчивости системы, но для объектов единственного вызова ее можно организовать на основе сетевой инфраструктуры или изменениями в стандартных каналах или посредниках. Безопасность. Remoting Soap может использовать механизмы безопасности IIS. При использовании вне IIS среда Remoting не предоставляет каких-либо встроенных средств обеспечения безопасности, но может использоваться поверх безопасного транспортного протокола при использовании VPN.
Кроме того, при создании нестандартного канала Remoting может работать с другими безопасными транспортами, такими как MSMQ. Эффективность (в узком понимании термина). По имеющимся оценкам, Remoting Binary является наиболее быстродействующей промежуточной средой из всех рассмотренных в курсе. Среда Remoting имеет гибкую архитектуру, позволяющую при необходимости осуществлять полный контроль за процессом удаленного вызова. Однако, полный контроль за реализацией промежуточный среды, вероятно, важен для достаточно небольшого числа распределенных приложений. Для большей же их части более интересным представляется, например, поддержка отсутствующих в .NET Remoting распределенных транзакций. С другой стороны, при использовании совместно с IIS среда Remoting не имеет заметных преимуществ перед веб службами, причем для веб служб на основе .NET Remoting нет аналогов WSE. Несомненное преимущество использования .NET Remoting вместе с IIS таким образом заключается в возможности быстрого перехода от веб служб HTTP к, например, локальному взаимодействию на основе канала IpcChannel. Для этого достаточно изменить только файлы конфигурации, используемые на клиента и сервере, и заменить IIS на программу, являющуюся сервером .NET Remoting. Вероятным применение Remoting может быть так же высокоскоростной обмен данными в LAN на основе TCP и двоичного форматирования, а также демонстрация реализации удаленного вызова в учебных целях. Возможным доводом в пользу .NET Remoting так же может являться наличие поддержки этой среды в сторонних реализациях CLI.
  1)   При помощи делегатов и BeginInvoke можно асинхронно вызвать любой метод, в данном случае подразумевается асинхронность с точки зрения промежуточной среды