В отличие от других промежуточных сред, рассматриваемых в данном курсе, среда
.
(рис 8.1) Различные сценарии использования среды RemotingКроме использования в качестве самостоятельной промежуточной среды,
.
С точки зрения среды Remoting, все классы объектов среды CLR делятся на три вида.
System.MarshalByRefObject. Объекты этих классов не покидают свой
домен приложения, но на стороне клиента создается посредник, позволяющий
осуществлять удаленный доступ к объекту. Именно экземпляры таких классов могут
использоваться как удаленные объекты при помощи среды Remoting. Remoting
поддерживает все три вида удаленных объектов: объекты единственного вызова,
единственного экземпляра и объекты, активируемые по запросу клиента.Таким образом, среда Remoting является средством поддержки удаленных вызовов
между 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).
Среда Remoting имеет достаточно сложную организацию удаленного вызова, которая позволяет разработчику при необходимости полностью контролировать и модифицировать процесс вызова клиентом Remoting метода объекта сервера. Это, с одной стороны, дает возможность после доработок применять среду Remoting практически с любыми каналами передачи информации (например, поверх MSMQ) или для некоторых специальных целей (например, для взаимодействия CLR и COM+). Открытая и изменяемая архитектура среды также делает ее достаточно привлекательной с педагогической точки зрения при изучении механизмов удаленного вызова. С другой стороны, модификация архитектуры Remoting является достаточно нетривиальной задачей, в результате решения которой могут быть получены сложные, нестандартные и недостаточно проверенные, в том числе с точки зрения безопасности, расширения промежуточной среды. Поэтому вопрос о полезности больших возможностей по расширению промежуточной среды конечным разработчиком остается, видимо, открытым.
(рис 8.2) Выполнение удаленного вызова в .NET Remoting Архитектура среды Remoting (рис. 8.2) включает в
себя следующие основные сущности, реализуемые классами из пространства имен System.Runtime.Remoting и его подпространств.
TransparentProxy и RealProxy.
Первый из них является классическим посредником и реализует интерфейс удаленного
объекта. Второй, "настоящий", посредник получает от
"прозрачного" посредника сообщение об удаленном вызове
(класс IMessage ). "Настоящий" посредник может быть изменен
разработчиком для реализации каких то специфических функций, и поэтому посредник
удаленного объекта в среде Remoting представлен двумя сущностями.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. Асинхронный односторонний вызов
осуществляется для методов с атрибутом BeginInvoke можно асинхронно вызвать любой метод, в данном случае
подразумевается асинхронность с точки зрения промежуточной среды
Одной из проблем при использовании распределенных объектов является определение
момента их уничтожения сервером. Для автоматического удаления из памяти
локальных объектов, используемых удаленным клиентом, недостаточно отслеживания
локальных ссылок на них. В среде Remoting для управлением временем жизни таких
объектов используется система аренды ( lease ), которая при
использовании активируемых клиентом объектов или объектов единственного
экземпляра с некоторым интервалом просит клиента подтвердить необходимость
существования объекта. Если на стороне клиента уже нет активных ссылок на
удаленный объект, то подтверждения не происходит, и при отсутствии хотя бы одного
подтверждения объект на сервере будет освобожден. Для объектов единственного
вызова в такой системе нет нужды, поскольку объекты создаются сервером при
удаленном вызове и освобождаются сервером после завершения любого удаленного
вызова (включая методы set и get свойств). В силу этого с
объектами, активируемыми сервером, может использоваться только конструктор по
умолчанию, не имеющий параметров.
Среда Remoting имеет штатную возможность описания своей конфигурации в XML-файле. К конфигурации среды Remoting относятся:
Разработчик может задать конфигурацию среды Remoting непосредственно в программе, но данный метод рассматриваться не будет. Применение файлов конфигураций позволяет отделить используемый в программе удаленный объект от места его физического размещения, и допускает настройку одного и того же распределенного приложения на использование различных каналов передачи данных без изменения исходного кода программы. Данная возможность должна всегда использоваться разработчиками, поэтому далее будет рассматриваться использование среды Remoting только с файлами конфигурации. После корректного конфигурирования среда Remoting обеспечивает прозрачное использование удаленных объектов (рис. 8.4).
(рис 8.4) Использование конфигурации Remoting Файл конфигурации на стороне, где находится маршализируемый по ссылке объект,
используется приложением, являющимся сервером среды Remoting. В роли такого
приложения может выступать либо IIS, либо пользовательская программа, обычно
реализованная как сервис операционной системы. Для учебных целей можно
воспользоваться следующим сервером .
// 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(config);
// Для .NET Framework 2.0 рекомендуется использовать
// RemotingConfiguration.Configure(config, 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
После конфигурирования среда 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 можно использовать простую идентификацию.
Канал, по которому в среде 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). Поэтому данную модификацию канала
следует рассматривать исключительно как пример по модернизации инфраструктуры .
Для реализации шифрования достаточно добавить в цепочку поставщиков труб потока
дополнительного поставщика, который будет создавать трубу, шифрующую и
дешифрующую проходящие по ней потоки с сериализованными сообщениями. Для
добавления такого поставщика в канал следует использовать файл конфигурации. Для
использовании стандартного канала с дополнительной трубой достаточно указать имя
класса поставщика трубы в разделе <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)
{
}
}
Собственно шифрование выполняется в созданными поставщиками трубах канала на стороне клиента и сервера.
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
Труба шифрования клиента должна зашифровать этот поток перед отправкой на сервер и расшифровать при получении ответа сервера.
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
Вспомогательный класс 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>
Таким образом, архитектура .
Одним из недостатков стандартных каналов среды 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";
}
Класс 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();
}
Метод 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
Для компиляции сборки с классом канала можно использовать 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>
Как видно из приведенного примера, среда .
Отличительной особенностью среды Remoting является возможность отделить объекты
распределенной системы от способа передачи сообщений удаленного вызова. Одно и
тоже распределенное приложение может работать с различными каналами передачи
данных и способами форматирования, включая локальный канал IpсChannel. Для смены
канала достаточно изменить только файлы конфигурации, используемые на клиентах и
серверах системы. Однако, при необходимости использования механизмов
безопасности на уровне промежуточной среды это преимущество исчезает – поскольку
выбор каналов связи ограничен возможностями IIS.
Рассмотрим среду .
Среда Remoting имеет гибкую архитектуру, позволяющую при необходимости
осуществлять полный контроль за процессом удаленного вызова. Однако, полный
контроль за реализацией промежуточный среды, вероятно, важен для достаточно
небольшого числа распределенных приложений. Для большей же их части более
интересным представляется, например, поддержка отсутствующих в .IpcChannel. Для этого достаточно изменить только файлы конфигурации,
используемые на клиента и сервере, и заменить IIS на программу, являющуюся
сервером .
В отличие от других промежуточных сред, рассматриваемых в данном курсе, среда
.
(рис 8.1) Различные сценарии использования среды RemotingКроме использования в качестве самостоятельной промежуточной среды,
.
С точки зрения среды Remoting, все классы объектов среды CLR делятся на три вида.
System.MarshalByRefObject. Объекты этих классов не покидают свой
домен приложения, но на стороне клиента создается посредник, позволяющий
осуществлять удаленный доступ к объекту. Именно экземпляры таких классов могут
использоваться как удаленные объекты при помощи среды Remoting. Remoting
поддерживает все три вида удаленных объектов: объекты единственного вызова,
единственного экземпляра и объекты, активируемые по запросу клиента.Таким образом, среда Remoting является средством поддержки удаленных вызовов
между 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).
Среда Remoting имеет достаточно сложную организацию удаленного вызова, которая позволяет разработчику при необходимости полностью контролировать и модифицировать процесс вызова клиентом Remoting метода объекта сервера. Это, с одной стороны, дает возможность после доработок применять среду Remoting практически с любыми каналами передачи информации (например, поверх MSMQ) или для некоторых специальных целей (например, для взаимодействия CLR и COM+). Открытая и изменяемая архитектура среды также делает ее достаточно привлекательной с педагогической точки зрения при изучении механизмов удаленного вызова. С другой стороны, модификация архитектуры Remoting является достаточно нетривиальной задачей, в результате решения которой могут быть получены сложные, нестандартные и недостаточно проверенные, в том числе с точки зрения безопасности, расширения промежуточной среды. Поэтому вопрос о полезности больших возможностей по расширению промежуточной среды конечным разработчиком остается, видимо, открытым.
(рис 8.2) Выполнение удаленного вызова в .NET RemotingАрхитектура среды Remoting (рис. 8.2) включает в
себя следующие основные сущности, реализуемые классами из пространства имен System.Runtime.Remoting и его подпространств.
TransparentProxy и RealProxy.
Первый из них является классическим посредником и реализует интерфейс удаленного
объекта. Второй, "настоящий", посредник получает от
"прозрачного" посредника сообщение об удаленном вызове
(класс IMessage ). "Настоящий" посредник может быть изменен
разработчиком для реализации каких то специфических функций, и поэтому посредник
удаленного объекта в среде Remoting представлен двумя сущностями.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. Асинхронный односторонний вызов
осуществляется для методов с атрибутом BeginInvoke можно асинхронно вызвать любой метод, в данном случае
подразумевается асинхронность с точки зрения промежуточной среды
Одной из проблем при использовании распределенных объектов является определение
момента их уничтожения сервером. Для автоматического удаления из памяти
локальных объектов, используемых удаленным клиентом, недостаточно отслеживания
локальных ссылок на них. В среде Remoting для управлением временем жизни таких
объектов используется система аренды ( lease ), которая при
использовании активируемых клиентом объектов или объектов единственного
экземпляра с некоторым интервалом просит клиента подтвердить необходимость
существования объекта. Если на стороне клиента уже нет активных ссылок на
удаленный объект, то подтверждения не происходит, и при отсутствии хотя бы одного
подтверждения объект на сервере будет освобожден. Для объектов единственного
вызова в такой системе нет нужды, поскольку объекты создаются сервером при
удаленном вызове и освобождаются сервером после завершения любого удаленного
вызова (включая методы set и get свойств). В силу этого с
объектами, активируемыми сервером, может использоваться только конструктор по
умолчанию, не имеющий параметров.
Среда Remoting имеет штатную возможность описания своей конфигурации в XML-файле. К конфигурации среды Remoting относятся:
Разработчик может задать конфигурацию среды Remoting непосредственно в программе, но данный метод рассматриваться не будет. Применение файлов конфигураций позволяет отделить используемый в программе удаленный объект от места его физического размещения, и допускает настройку одного и того же распределенного приложения на использование различных каналов передачи данных без изменения исходного кода программы. Данная возможность должна всегда использоваться разработчиками, поэтому далее будет рассматриваться использование среды Remoting только с файлами конфигурации. После корректного конфигурирования среда Remoting обеспечивает прозрачное использование удаленных объектов (рис. 8.4).
(рис 8.4) Использование конфигурации RemotingФайл конфигурации на стороне, где находится маршализируемый по ссылке объект,
используется приложением, являющимся сервером среды Remoting. В роли такого
приложения может выступать либо IIS, либо пользовательская программа, обычно
реализованная как сервис операционной системы. Для учебных целей можно
воспользоваться следующим сервером .
// 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(config);
// Для .NET Framework 2.0 рекомендуется использовать
// RemotingConfiguration.Configure(config, 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
После конфигурирования среда 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 можно использовать простую идентификацию.
Канал, по которому в среде 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). Поэтому данную модификацию канала
следует рассматривать исключительно как пример по модернизации инфраструктуры .
Для реализации шифрования достаточно добавить в цепочку поставщиков труб потока
дополнительного поставщика, который будет создавать трубу, шифрующую и
дешифрующую проходящие по ней потоки с сериализованными сообщениями. Для
добавления такого поставщика в канал следует использовать файл конфигурации. Для
использовании стандартного канала с дополнительной трубой достаточно указать имя
класса поставщика трубы в разделе <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)
{
}
}
Собственно шифрование выполняется в созданными поставщиками трубах канала на стороне клиента и сервера.
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
Труба шифрования клиента должна зашифровать этот поток перед отправкой на сервер и расшифровать при получении ответа сервера.
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
Вспомогательный класс 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>
Таким образом, архитектура .
Одним из недостатков стандартных каналов среды 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";
}
Класс 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();
}
Метод 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
Для компиляции сборки с классом канала можно использовать 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>
Как видно из приведенного примера, среда .
Отличительной особенностью среды Remoting является возможность отделить объекты
распределенной системы от способа передачи сообщений удаленного вызова. Одно и
тоже распределенное приложение может работать с различными каналами передачи
данных и способами форматирования, включая локальный канал IpсChannel. Для смены
канала достаточно изменить только файлы конфигурации, используемые на клиентах и
серверах системы. Однако, при необходимости использования механизмов
безопасности на уровне промежуточной среды это преимущество исчезает – поскольку
выбор каналов связи ограничен возможностями IIS.
Рассмотрим среду .
Среда Remoting имеет гибкую архитектуру, позволяющую при необходимости
осуществлять полный контроль за процессом удаленного вызова. Однако, полный
контроль за реализацией промежуточный среды, вероятно, важен для достаточно
небольшого числа распределенных приложений. Для большей же их части более
интересным представляется, например, поддержка отсутствующих в .IpcChannel. Для этого достаточно изменить только файлы конфигурации,
используемые на клиента и сервере, и заменить IIS на программу, являющуюся
сервером .
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.