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

         

Канал среды 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, причем для этого не требуется что либо менять в коде клиента или сервера. Однако даже в простейшем случае реализация собственных поставщиков и труб выглядит достаточно громоздко.


Содержание раздела