Среда Remoting имеет достаточно сложную организацию удаленного вызова, которая позволяет разработчику при необходимости полностью контролировать и модифицировать процесс вызова клиентом Remoting метода объекта сервера. Это, с одной стороны, дает возможность после доработок применять среду Remoting практически с любыми каналами передачи информации (например, поверх MSMQ) или для некоторых специальных целей (например, для взаимодействия CLR и COM+). Открытая и изменяемая архитектура среды также делает ее достаточно привлекательной с педагогической точки зрения при изучении механизмов удаленного вызова. С другой стороны, модификация архитектуры Remoting является достаточно нетривиальной задачей, в результате решения которой могут быть получены сложные, нестандартные и недостаточно проверенные, в том числе с точки зрения безопасности, расширения промежуточной среды. Поэтому вопрос о полезности больших возможностей по расширению промежуточной среды конечным разработчиком остается, видимо, открытым.
Архитектура среды Remoting (рис. 8.2) включает в себя следующие основные сущности, реализуемые классами из пространства имен System.Runtime.Remoting и его подпространств.
Посредники удаленного объекта. В среде используется два посредника, принадлежащие классам TransparentProxy и RealProxy. Первый из них является классическим посредником и реализует интерфейс удаленного объекта. Второй, "настоящий", посредник получает от "прозрачного" посредника сообщение об удаленном вызове (класс IMessage). "Настоящий" посредник может быть изменен разработчиком для реализации каких то специфических функций, и поэтому посредник удаленного объекта в среде Remoting представлен двумя сущностями. Сообщение проходит по каналу (channel) среды Remoting. Канал состоит из отдельных труб (sinks) и может иметь различную структуру. Как минимум, клиентская сторона канала включает трубу форматирования, преобразующую сообщение об удаленном вызове в поток ввода- вывода, и транспортную трубу, передающую данный поток в канал передачи данных.Канал, по которому в среде Remoting передается сообщение удаленного вызова, образуется списком труб, который так же называется цепочкой (chain). Каждая труба в цепочке клиентской части канала, за исключением транспортной, содержит ссылку на следующую трубу. Эта ссылка содержится в свойстве NextSink (для интерфейса IMessageSink) или NextChannelSink (для интерфейсов IClientChannelSink и IServerChannelSink).
Канал на стороне сервера устроен подобным образом, за исключением того, что он должен заканчиваться трубой диспетчеризации. Для этого обычно вызывается метод ChannelServices.CreateServerChannelSinkChain, который добавляет к цепочке поставщиков стандартного поставщика трубы диспетчеризации DispatchChannelSinkProvider.
Обычно каждый поставщик создает цепочку из единственной трубы, что позволяет изменять свойства канала, добавляя или заменяя только часть поставщиков. Поставщики также образуют список, использующий их свойство Next. Стандартные каналы включают поставщика трубы форматирования и поставщика транспортной трубы. Например, цепочка поставщиков канала TcpClientChannel (клиентской части TcpChannel) состоит по умолчанию из поставщиков BinaryClientFormatterSinkProvider и TcpClientTransportSinkProvider. При этом поставщик форматирования может быть задан в конфигурационном файле. В простейшем случае достаточно указать ссылку на зарегистрированный тип в разделе <channel><serverProviders><formatter> в конфигурации сервера и в разделе <channel><clientProviders><formatter> на клиенте. Для стандартных классов форматирования это ссылки binary или soap.
Среда Remoting имеет штатную возможность описания своей конфигурации в XML-файле. К конфигурации среды Remoting относятся:
используемый класс канала;параметры канала (например, используемый сервером порт TCP);используемый класс форматирования;адрес и тип используемого удаленного объекта.Разработчик может задать конфигурацию среды Remoting непосредственно в программе, но данный метод рассматриваться не будет. Применение файлов конфигураций позволяет отделить используемый в программе удаленный объект от места его физического размещения, и допускает настройку одного и того же распределенного приложения на использование различных каналов передачи данных без изменения исходного кода программы. Данная возможность должна всегда использоваться разработчиками, поэтому далее будет рассматриваться использование среды Remoting только с файлами конфигурации. После корректного конфигурирования среда Remoting обеспечивает прозрачное использование удаленных объектов (рис. 8.4).
Файл конфигурации на стороне, где находится маршализируемый по ссылке объект, используется приложением, являющимся сервером среды Remoting. В роли такого приложения может выступать либо IIS, либо пользовательская программа, обычно реализованная как сервис операционной системы. Для учебных целей можно воспользоваться следующим сервером .NET Remoting.
// server.cs using System; using System.Runtime.Remoting; using System.Configuration; public class Server { public static void Main(string[] args) { string config = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile; Console.WriteLine(config); RemotingConfiguration.Configure(s); // Для .NET Framework 2.0 рекомендуется использовать // RemotingConfiguration.Configure(s, false); Console.WriteLine("Нажмите ENTER для завершения работы сервера."); Console.ReadLine(); } }
Как видно из приведенного примера, назначение такого сервера – настройка среды Remoting на основе содержимого стандартного конфигурационного файла программы.
Не претендуя на полное изложение всех возможностей конфигурационного файла среды Remoting, рассмотрим следующий пример конфигурации сервера.
<configuration> <system.runtime.remoting> <application> <service> <wellknown mode="SingleCall" type="RemoteTest.TestService, RemoteTestService" objectUri="endpoint"/> </service> <channels> <channel ref="tcp" port="2080" /> </channels> </application> </system.runtime.remoting> </configuration>
В разделе <application> <service> указываются публикуемые сервером объекты. Для каждого из них указывается модель вызова, имя класса (через запятую идет полное или краткое имя сборки, которая содержит класс), и уникальный URI, связываемый с данным классом. Объекты, активируемые сервером, описываются как
<wellknown mode="SingleCall" type=...
для режима единственного вызова или
<wellknown mode="Singletone" type=...
для режима одного экземпляра. Объекты, активируемые по запросу клиента, описываются иначе.
<activated type=...
Раздел <application><channels> сервера в простейшем случай содержит идентификатор канала и номер порта. Стандартные каналы имеют идентификаторы, соответствующие названиям их протоколов. В файле конфигурации могут использоваться модифицированные или вновь созданные каналы, как будут показано далее.
Файл конфигурации клиента обычно выглядит примерно следующим образом.
<configuration> <system.runtime.remoting> <application> <client> <wellknown mode="SingleCall" type="RemoteTest.TestService, RemoteTestService" objectUri="tcp://127.0.0.1:2080/endpoint" /> </client > </application> </system.runtime.remoting> </configuration>
Раздел <application><client> включает описание используемых удаленных объектов. Следует отметить, что один и тот же файл может содержать и разделы <service>, и <client>.
Программа при этом будет настроена как сервер и клиент Remoting одновременно.
Клиент, использующий удаленный объект, сначала должен настроить среду Remoting. После чего при создании объекта любого из перечисленных в файле конфигурации в разделе <client> классов будет создан посредник, связанный с соответствующим удаленным объектом.
using System; using System.Runtime.Remoting;
using RemotingTest;
public class Client { public static void Main(string[] args) { string config = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile; Console.WriteLine(config); RemotingConfiguration.Configure(config);
// Для объекта единственого вызова в следующей строчке будут // созданы посредники, конструктор самого объекта не вызывается TestService service = new RemotingTest.TestService(); // При удаленном вызове на сервере будет сконструирован новый объект Console.WriteLine(service.Sum(2, 2)); } }
Ниже приведен код самого маршализируемого по ссылке объекта. Конструктор и деструктор объекта выводят сообщения в консоль, чтобы продемонстрировать моменты создания и удаления объекта на сервере.
// TestService.cs using System; namespace RemotingTest { public class TestService : MarshalByRefObject { public TestService() { Console.WriteLine("[ctor]"); }
~TestService() { Console.WriteLine("[dtor]"); }
public int Sum(int a, int b) { return a + b; } } }
Нижеследующий make файл позволяет собрать все приведенные выше файлы. Следует отметить, что в некоторых реализациях CLI для компиляции следует использовать ссылку на сборку System.Runtime.Remoting.dll. В .NET Framework 2.0 эта ссылка добавлена в стандартный файл параметров компилятора C# (csc.rsp).
makefile : Server.exe Client.exe
Server.exe : Server.cs csc Server.cs
Client.exe : Client.cs RemoteTestService.dll csc /r:RemoteTestService.dll Client.cs
RemoteTestService.dll: TestService.cs csc /out:RemoteTestService.dll /t:library TestService.cs
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. |
Закрыть окно |
// Метод синхронной обработки сообщения 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. |
Закрыть окно |
// Файл SevaRemotingMsmq.cs using System; using System.IO; using System.Collections; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Messaging; using System.Runtime.Serialization.Formatters.Binary; using System.Messaging; using System.Text.RegularExpressions; // использовать описанное в главе про MSMQ пространство имен // для работы c очередями сообщений using Seva.Msmq; // Набор классов для одностороннего удаленного вызова // на основе Remoting / MSMQ namespace Seva.Remoting.MsmqChannel { // константы с именами свойств сообщения public class MessageProperties { public const string Uri = "__Uri"; public const string ObjectUri = "__ObjectUri"; } |
Листинг 8.4. |
Закрыть окно |
sink = ChannelServices.CreateServerChannelSinkChain(null, this); // создание стека труб stack = new ServerChannelSinkStack(); stack.Push(sink, null); // Начать ожидание собщений в очереди StartListening(null); } // метод интерфейса IChannelReceiver, возваращает все URL для данного URI public virtual string[] GetUrlsForUri(string objectURI) { return new string[] {objectURI}; } // обработка пришедшего сообщения MSMQ private IMessage OnReceive(Object sender, IMessage request, MessageQueue queueResponse) { return ProcessMessage(request); } // методы, управляющие прослушивание канала public void StartListening(Object data) { msmqServer.BeginReceive(); } public void StopListening(Object data) { msmqServer.EndReceive(); } |
Листинг 8.5. |
Закрыть окно |
public static class Utils { // метод выделяет из URL идентификатор объекта и трубы public static string ParseUrl(string url, string channelName, out string objectUri) { objectUri = null; Regex re = new Regex(@"^" + channelName + @"\:\/\/(.+)$"); Match m = re.Match(url); if (!m.Success) return null; string sinkUri = m.Groups[1].Value; Utils.ParseUrl(url, out objectUri); return sinkUri; } // метод выделяет из URL идентификатор объекта и путь к очереди MSMQ public static string ParseUrl(string url, out string objectUri) { string queuePath = null; objectUri = null; Regex r = new Regex(@"^(.*)(\\|\/)(.+)$"); Match m = r.Match(url); if (m.Success) { objectUri = m.Groups[3].Value; queuePath = m.Groups[1].Value; } else { throw new Exception("Не найден идентификатор объекта в " + url); } return queuePath; } } // Seva.Remoting.MsmqChannel.Utils } // Seva.Remoting.MsmqChannel // Файл SevaRemotingMsmq.cs |
Листинг 8.6. |
Закрыть окно |
Одним из недостатков стандартных каналов среды Remoting является плохая поддержка асинхронного взаимодействия. Даже при вызове одностороннего метода удаленного объекта сервер и клиент Remoting должны быть запущены одновременно, поскольку оба канала требуют функционирующего соединения по TCP/IP. Поэтому в качестве примера полезной модификации среды Remoting можно рассмотреть создание собственного канала на основе службы MSMQ. В отличие от стандартных каналов TcpChannel и HttpChannel, данный канал обеспечивает асинхронный обмен между клиентом и сервером. Ограничимся реализацией канала для одностороннего удаленного вызова.
На рисунке 8.6 приведена схема созданного канала. Следует отметить, что поскольку канал полностью базируется на промежуточной среде MSMQ, данное решение является настолько же безопасным, насколько безопасна среда MSMQ.
Данный канал будет позволять асинхронный вызов методов с атрибутом System.Runtime.Remoting.Messaging.OneWayAttribute, причем время работы клиента и сервера может не совпадать.
Вновь создаваемый канал состоит из двух основных классов: клиентcкой части MsmqChannelSender, реализующей интерфейс IChannelSender, и серверной части MsmqChannelReceiver, реализующей интерфейс IChannelReceiver. Указанные классы используют описанные в разделе MSMQ классы MsmqClient и MsmqServer, причем необходимо использовать бинарное форматирование сообщений MSMQ.
// Файл SevaRemotingMsmq.cs using System; using System.IO; using System.Collections; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Messaging; using System.Runtime.Serialization.Formatters.Binary; using System.Messaging; using System.Text.RegularExpressions; // использовать описанное в главе про MSMQ пространство имен // для работы c очередями сообщений using Seva.Msmq; // Набор классов для одностороннего удаленного вызова // на основе Remoting / MSMQ namespace Seva.Remoting.MsmqChannel { // константы с именами свойств сообщения public class MessageProperties { public const string Uri = "__Uri"; public const string ObjectUri = "__ObjectUri"; } Листинг 8.4.
Класс MsmqBase является базовым классом для обеих частей канала.
Он содержит некоторые общие для них свойства и метод Parse, выделяющий из полного URL удаленного объекта идентификатор объекта. URL при использовании данного канала должен иметь следующий вид
msmq://.\Private$\remoting_queue\endpoint
где .\Private$\remoting_queue – имя очереди (в данном случае – частной, локальной), а endpoint – идентификатор удаленного объекта.
// базовый класс для MsmqSender и MsmqReceiver public class MsmqBase: MarshalByRefObject, IChannel { // обязательные для интерфейса IChannel свойства public int ChannelPriority { get { return 1;} } public string ChannelName { get{ return "msmq";} }
// разделяет URL на идентификаторы для объекта и канала public string Parse(string url, out string objectUri) { return Utils.ParseUrl(url, out objectUri); } }
Конструктору класса MsmqChannelSender в параметре clientSinkProvider передается дополнительный поставщик, описанный в файле конфигурации, который следует вставить в цепочку поставщиков. Однако в данном случае клиентский канал может состоять из единственной цепочки сообщения, которая передает сообщение в MSMQ. Хотя в соответствии с идеологией Remoting следовало бы создать отдельную трубу форматирования, и отдельную – транспортную, для простоты примера можно ограничиться одной трубой без поддержки дополнительных поставщиков.
// канал для клиента public class MsmqChannelSender: MsmqBase, IChannelSender {
public MsmqChannelSender(IDictionary properties, IClientChannelSinkProvider clientSinkProvider) { // поскольку в данном примере сообщение сразу направляется в MSMQ, // то дополнительные поставщики не поддерживаются if (clientSinkProvider!=null) { throw new NotSupportedException( "Дополнительные поставщики не поддерживаются."); } }
Метод CreateMessageSink, единственный в интерфейсе IChannelSender, создает трубу сообщения.
public IMessageSink CreateMessageSink(string url, object channelData, out string objectUri) { // выделить из URL идентификатор для удаленного объекта string remoteobjectUrl = Utils.ParseUrl(url, ChannelName, out objectUri); if (remoteobjectUrl == null) return null; // создание трубы return new MsmqClientChannelSink(remoteobjectUrl); } } // Seva.Remoting.MsmqChannel.Msmq.MsmqSender
Класс MsmqClientChannelSink – транспортная труба клиента, передающая сериализованное сообщение в MSMQ.
// клиентская часть трубы канала public class MsmqClientChannelSink: IMessageSink { // клиент MSMQ, передающий объекты с интерфейсом IMessage // ответы данный объект получать не будет, но указать // отличный от void тип ответов необходимо private MsmqClient<IMessage, IMessage> msmqClient;
// путь к очереди MSMQ и идентификатор удаленного объекта (endpoint) private string queuePath = null; private string objectUri = null;
Конструктор клиентской трубы канала должен создать клиента MSMQ для передачи сообщений.
public MsmqClientChannelSink(string remoteObjectUri) { // выделение из URL пути к очереди и адреса объекта queuePath = Utils.ParseUrl(remoteObjectUri, out objectUri);
// создание клиента MSMQ, не ожидающего ответов от сервера // и использующего бинарный класс форматирования msmqClient = new MsmqClient<IMessage, IMessage>(queuePath, null, QueueFormatter.Binary); }
public IMessage SyncProcessMessage(IMessage msg) { throw new NotSupportedException( "Поддерживаются только методы с OneWayAttribute."); }
Метод AsyncProcessMessage используется для реализации асинхронного вызова. В данном примере он должен передать сообщение Remoting клиенту MSMQ.
public IMessageCtrl AsyncProcessMessage(IMessage message, IMessageSink replySink) { // свойство сообщения c адресом удаленного объекта message.Properties[MessageProperties.ObjectUri] = objectUri;
// передача сообщения клиенту MSMQ для его отсылки серверу msmqClient.Send(message); return null; }
// данная труба - первая и последняя в цепочке public IMessageSink NextSink { get { return null; } } } // Seva.Remoting.MsmqChannel.MsmqClientChannelSink
Принимающая сторона состоит из канала MsmqChannelReceiver.
public class MsmqChannelReceiver: MsmqBase, IChannelReceiver { // Класс-сервер MSMQ для приема сообщений Remoting private MsmqServer<IMessage, IMessage> msmqServer;
// Серверная труба канала private IServerChannelSink sink;
// Стек труб на стороне сервера private ServerChannelSinkStack stack;
// необходимое для реализации интерфейса свойство public object ChannelData { get { return null; } }
Десериализация сообщения осуществляется в классе MsmqServer, поэтому цепочка труб сервера состоит только из стандартной трубы диспетчеризации.
Для вызова метода ProcessMessage трубы диспетчеризации создается стек труб. Сервер ожидает сообщения из очереди, указанной в файле конфигурации.
public MsmqChannelReceiver(IDictionary properties, IServerChannelSinkProvider serverSinkProvider) { // создать сервер MSMQ msmqServer = new MsmqServer<IMessage, IMessage>( properties["queue"].ToString(), QueueFormatter.Binary); msmqServer.ProcessMessage += OnReceive;
if (serverSinkProvider!=null) { throw new NotSupportedException( "Поставщики не поддерживаются."); }
Поскольку начальной обработкой пришедших сообщений занимается класс MsmqReceiver, а десериализацией – класс MsmqServer, то в конструкторе достаточно создать стандартную трубу диспетчеризации сообщений на сервере.
sink = ChannelServices.CreateServerChannelSinkChain(null, this);
// создание стека труб stack = new ServerChannelSinkStack(); stack.Push(sink, null); // Начать ожидание собщений в очереди StartListening(null); }
// метод интерфейса IChannelReceiver, возваращает все URL для данного URI public virtual string[] GetUrlsForUri(string objectURI) { return new string[] {objectURI}; }
// обработка пришедшего сообщения MSMQ private IMessage OnReceive(Object sender, IMessage request, MessageQueue queueResponse) { return ProcessMessage(request); }
// методы, управляющие прослушивание канала public void StartListening(Object data) { msmqServer.BeginReceive(); }
public void StopListening(Object data) { msmqServer.EndReceive(); }
Листинг 8.5.
Метод ProcessMessage обрабатывает пришедшее сообщение .NET Remoting. В данном классе достаточно передать его дальше в трубу канала для передачи, в итоге, диспетчеру сообщений Remoting.
private IMessage ProcessMessage(IMessage request) { IMessage response = null; // если в сообщении не указан URI объекта, то ничего не делать object uri = request.Properties[MessageProperties.ObjectUri]; if (uri == null) return null;
string url = uri.ToString();
// Необходимо заполнить свойство сообщения __URI request.Properties[MessageProperties.Uri] = uri;
// Передача сообщения в трубу канала Stream responseStream = null; ITransportHeaders responseHeaders = null; sink.ProcessMessage(stack, request, null, null, out response, out responseHeaders, out responseStream); return response; } } // Seva.Remoting.MsmqChannel.MsmqReceiver
Класс Utils содержит вспомогательные статические методы для разбора URL.
public static class Utils { // метод выделяет из URL идентификатор объекта и трубы public static string ParseUrl(string url, string channelName, out string objectUri) { objectUri = null;
Regex re = new Regex(@"^" + channelName + @"\:\/\/(.+)$"); Match m = re.Match(url); if (!m.Success) return null;
string sinkUri = m.Groups[1].Value; Utils.ParseUrl(url, out objectUri); return sinkUri; }
// метод выделяет из URL идентификатор объекта и путь к очереди MSMQ public static string ParseUrl(string url, out string objectUri) { string queuePath = null; objectUri = null;
Regex r = new Regex(@"^(.*)(\\|\/)(.+)$"); Match m = r.Match(url);
if (m.Success) { objectUri = m.Groups[3].Value; queuePath = m.Groups[1].Value; } else { throw new Exception("Не найден идентификатор объекта в " + url); } return queuePath; } } // Seva.Remoting.MsmqChannel.Utils } // Seva.Remoting.MsmqChannel // Файл SevaRemotingMsmq.cs
Листинг 8.6.
Для компиляции сборки с классом канала можно использовать make файл следующего содержания.
makefile : SevaRemotingMsmq.dll common = SevaRemotingMsmq.cs SevaMsmq.cs
SevaRemotingMsmq.dll: $(common) csc /out:SevaRemotingMsmq.dll /t:library $(common)
Для использования созданного канала необходимо описать его в файле конфигурации, например, указав его полное имя в разделе <channel>.
<configuration> <system.runtime.remoting> <application> <client> <wellknown type="RemoteService.RemoteServiceOneWay, RemoteService" url="msmq://.\Private$\remoting_queue\service" /> </client> <channels> <channel type="Seva.Remoting.MsmqChannel.MsmqChannelSender, SevaRemotingMsmq"/> </channels> </application> </system.runtime.remoting> </configuration>
Пример файла конфигурации сервера.
<configuration> <system.runtime.remoting> <application name="JobServer"> <service> <wellknown mode="SingleCall" type="RemoteService.RemoteServiceOneWay, RemoteService" objectUri="service" /> </service> <channels> <channel type="Seva.Remoting.MsmqChannel.MsmqChannelReceiver, SevaRemotingMsmq" queue = ".\Private$\remoting_queue"/> </channels> </application> </system.runtime.remoting> </configuration>
Как видно из приведенного примера, среда .NET Remoting имеет уникальные возможности по модификации своей структуры разработчиком.Но даже в рассмотренном простейшем случае такие изменения требуют как минимум глубокого понимания структуры .NET Remoting.
После конфигурирования среда Remoting обеспечивает прозрачное использование удаленных объектов. Однако, для такого прозрачного использования клиент должен иметь доступ к сборке с самим объектом, сборка с его интерфейсом недостаточна. Для решения данной проблемы можно либо пожертвовать прозрачностью, воспользовавшись классом System.Activator, или организовать веб службу Remoting на основе SOAP, что позволяет создать посредника на клиенте по спецификации веб службы в формате WSDL. Вариант с веб службой представляется более разумным – если клиент не имеет доступа к сборке удаленного объекта, то, вероятно, клиент и сервер находятся на разных предприятиях, и видимо речь идет о взаимодействии вне доверенной сети.
При использовании класса форматирования SoapFormatter, канала HttpChannel и активируемых сервером объектов среда Remoting позволяет организовать веб службу на основе SOAP, WSDL и кодирования SOAP-RPC (а не SOAP Document, в отличие от служб ASP.NET). В этом случае нет необходимости иметь сборку на клиенте, поскольку, используя методы класса System.Runtime.Remoting.MetadataServices.MetaData, можно получить сборку из описания веб-службы. Для упрощения данной задачи существует утилита soapsuds.exe. Нижеследующая команда записывает в текущий каталог файл с классом, который является наследником System.Runtime.Remoting.Services.RemotingClientProxy, имеет интерфейс удаленного объекта и может быть использован для сборки клиента Remoting.
soapsuds.exe -url:http://localhost:2080/endpoint?wsdl -gc
Следует отметить, что в файле конфигурации клиента по прежнему должно быть указано имя сборки на сервере (оно совпадает с именем файла, созданного утилитой soapsuds.exe). Созданный утилитой soapsuds.exe посредник не будет использоваться в ходе выполнения программы, если используется конфигурация Remoting. Если же клиент не использует метод RemotingConfiguration.Configure, то будет использован посредник, который попытается использовать тот же сервис, который был указан при его создании в его конструкторе.
Поскольку служба Remoting создает стандартную веб службу, то можно воспользоваться и описанной ранее утилитой wsdl.exe для создания клиента веб службы.
Важной особенностью среды Remoting является возможность использования в качестве сервера IIS, при этом клиент должен использовать канал HTTP. Конфигурация сервера в этом случае храниться в файле web.config в каталоге виртуального сервера, а в его подкаталоге bin размещаются сборки с маршализируемыми по ссылке объектами. Среда Remoting не содержит штатных средств обеспечения безопасности, и единственным практическим способом решения данной проблемы является именно использование IIS. Это позволяет использовать авторизацию и защиту передаваемых данных с помощью SSL. При использовании SSL, кроме соответствующей настройки сервера, клиент должен использовать префикс объектов https://.
Для идентификации пользователя при использовании встроенной авторизации IIS можно воспользоваться примерно следующим кодом.
RemoteObject object = new RemoteObject();
// Параметры идентификации клиента IDictionary properties = ChannelServices.GetChannelSinkProperties(object); properties["username"] = username; properties["password"] = password;
// Удаленный вызов object.Do();
При использовании IIS можно применять декларативную безопасность с помощью атрибута PrincipalPermissionAttribute, или императивную безопасность, когда метод объекта проверяет, принадлежит ли авторизованный пользователь к той или иной группе пользователей.
IPrincipal principal = System.Threading.Thread.CurrentPrincipal; if (!principal.IsInRole(Environment.MachineName + @"\SomeGroup")) { throw new UnauthorizedAccessException("Нет прав доступа."); }
При использовании в пределах домена или группы дружественных доменов Active Directory служба IIS позволяет использовать авторизацию операционной системы, для чего клиенту достаточно указать в файле конфигурации примерно следующее.
<channel ref="http" useDefaultCredentials="true" />
Таким образом, при использовании внутри Active Directory вместе со службой IIS среда Remoting позволяет легко создать безопасную систему на основе авторизации Windows (протокол Kerberos), сертификатов сервера и ролей пользователей.При использовании вне Active Directory вместо идентификации Windows можно использовать простую идентификацию.
В отличие от других промежуточных сред, рассматриваемых в данном курсе, среда .NET Remoting создавалась специально для платформы .NET. Среда Remoting является универсальным средством доступа к удаленным объектам, которое может быть приспособлено к широкому классу задач взаимодействия компонент распределенного приложения. Благодаря своей расширяемой архитектуре среда Remoting может быть доработана для использования с практически любыми каналами передачи данных. Уже со штатными средствами область применения среды Remoting охватывает как реализацию публичных веб служб в интернете на основе протокола SOAP, так и высокоскоростной обмен в доверенной сети на основе бинарного форматирования (рис. 8.1).
Кроме использования в качестве самостоятельной промежуточной среды, .NET Remoting также используется для организации взаимодействия .NET Framework и среды COM+ в рассмотренной ранее промежуточной среде .NET Enterprise Services.
С точки зрения среды Remoting, все классы объектов среды CLR делятся на три вида.
Классы, маршализируемые по значению. Объекты этих классов могут копироваться между доменами приложений, если для них определены операции сериализации и десериализации. В результате десериализации создается копия объекта, не связанная с его оригиналом. Следует отметить, что сериализация и десериализация некоторых объектов может быть осуществлена не со всеми классами форматирования, как указано в теме о сериализации. Классы, маршализируемые по ссылке. Такие классы наследуются от класса System.MarshalByRefObject. Объекты этих классов не покидают свой домен приложения, но на стороне клиента создается посредник, позволяющий осуществлять удаленный доступ к объекту. Именно экземпляры таких классов могут использоваться как удаленные объекты при помощи среды Remoting. Remoting поддерживает все три вида удаленных объектов: объекты единственного вызова, единственного экземпляра и объекты, активируемые по запросу клиента. Немаршализируемые классы, а так же классы без определенной процедуры сериализации или отмеченные, как несериализуемые.Отличительной особенностью среды Remoting является возможность отделить объекты распределенной системы от способа передачи сообщений удаленного вызова. Одно и тоже распределенное приложение может работать с различными каналами передачи данных и способами форматирования, включая локальный канал IpсChannel. Для смены канала достаточно изменить только файлы конфигурации, используемые на клиентах и серверах системы. Однако, при необходимости использования механизмов безопасности на уровне промежуточной среды это преимущество исчезает – поскольку выбор каналов связи ограничен возможностями IIS.
Рассмотрим среду .NET Remoting с точки зрения требований к распределенным системам. При этом следует рассмотреть два типичных случая использования среды Remoting – как веб службы (далее – Remoting-Soap) и с использованием бинарного форматирования (далее – Remoting-Binary).
Открытость. Среда Remoting-Binary может быть использована только в пределах распределенного приложения, использующего CLI, причем для удобства работы нужно, чтобы клиент имел доступ к сборке с классом удаленного объекта. Среда Remoting Soap является открытой и гетерогенной, основанной на общепринятых стандартах SOAP и WSDL. Масштабируемость. Приложение, использующее .NET Remoting, являются плохо масштабируемым, если используются активируемые клиентом объекты. Для объектов же единственного вызова существует возможность организовать балансировку нагрузок – при ее поддержке сетевой инфраструктурой или при использовании нестандартного посредника. Поддержание логической целостности данных. Среда Remoting не содержит штатных средств поддержки распределенных транзакций. Устойчивость. Среда Remoting не содержит средств повышения устойчивости системы, но для объектов единственного вызова ее можно организовать на основе сетевой инфраструктуры или изменениями в стандартных каналах или посредниках. Безопасность. Remoting Soap может использовать механизмы безопасности IIS. При использовании вне IIS среда Remoting не предоставляет каких-либо встроенных средств обеспечения безопасности, но может использоваться поверх безопасного транспортного протокола при использовании VPN.