Симон Робинсон - C# для профессионалов. Том II
.NET Remoting легко модифицируется: можно заменить реальный прокси, добавить объекты приемника, заменить форматтер и канал. Конечно, можно также использовать все, что уже предоставлено. При этом необходимо отметить, что связанные с прохождением через эти слои накладные расходы можно считать почти отсутствующими. Если вы добавите собственную функциональность, накладные расходы будут зависеть от нее.
Контексты
Прежде чем рассматривать возможности .NET Remoting для создания серверов и клиентов, которые общаются в сети, давайте рассмотрим те случаи, когда внутри домена приложения требуется канал, осуществляющий вызов объектов через контексты.
При создании компонентов COM+ использовались контексты COM+. Контексты в .NET являются очень похожими. Как уже было сказано, один процесс может иметь несколько доменов приложений. Домен приложения является чем-то типа подпроцесса с границами безопасности и может иметь различные контексты. Контекст используется для группирования объектов с аналогичными требованиями выполнения. Контексты состоят из множества свойств и используются для перехватывания: когда к ограниченному контекстом объекту обращаются из другого контекста, перехватчик может сделать некоторую работу, прежде чем вызов достигнет объекта.
Класс, который выводится из MarsnalByRefObject, ограничен доменом приложения. Вне домена приложения требуется прокси для доступа к объекту. Класс, который выводится из ContextBoundObject ограничен контекстом. Вне контекста для доступа к объекту требуется прокси. Ограниченные контекстом объекты могут иметь атрибуты контекста, объект без таких атрибутов создается в контексте создателя. Ограниченный контекстом объект с атрибутами контекста создается в новом контексте или в контексте создателя, если атрибуты являются совместимыми
Чтобы понять контексты, необходимо знать некоторые термины:
□ Создание домена приложения приводит к возникновению контекста по умолчанию в этом домене. Если вы возьмете экземпляр нового объекта, которому требуются другие свойства контекста, в соответствии с ними создастся новый контекст.
□ Атрибуты контекста могут присваиваться классам, производным из ContextBoundObject. Можно создать класс специального атрибута, реализуя интерфейс IContextAttribute. .NET Framework имеет два класса атрибутов контекста: SynchronizationAttribute и ThreadAffinityAttribute.
□ Атрибуты контекста определяют свойства контекста, необходимые объекту. Класс свойства контекста реализует интерфейс IContextProperty. Активные свойства предоставляют приемники сообщений в цепочку вызовов. Класс ContextAttribute, который может использоваться как базовый для специальных атрибутов, реализует как IContextProperty, так и IContextAttribute.
□ Приемник сообщений является перехватчиком вызова метода. При этом свойства помогают работе приемников сообщений.
Активизация
Новый контекст создается, если экземпляру создаваемого класса требуется контекст, отличный от вызывающего контекста. Классы атрибутов, которые ассоциируются с целевым классом, запрашиваются в том случае, если все свойства текущего контекста в порядке. Если один из этих классов атрибутов присылает false, то среда выполнения запрашивает все классы свойств, связанные с классом атрибута, и создает новый контекст. Среда выполнения запрашивает затем у классов свойств о приемниках, которые они хотят установить. Класс свойства может реализовать интерфейсы IContributeXXXSink для содействия объектам приемника.
Атрибуты и свойства
Класс атрибута контекста является прежде всего атрибутом. Более подробно можно прочитать об этом в главе 6. Классы атрибутов контекста должны реализовать интерфейс IContextAttribute. Специальный класс атрибута контекста можно вывести из класса ContextAttribute, так как этот класс уже имеет используемую по умолчанию реализацию данного интерфейса.
В .NET Framework содержатся два класса атрибутов контекста: System.Runtime.Remoting.Contexts.SynchronizationAttribute и System.Runtime.Remoting.Contexts.ThreadAffinityAttribute. С помощью атрибута ThreadAffinity можно задать, что только один поток выполнения получает доступ к полям экземпляра и методам ограниченного контекстом класса. Это полезно для объектов интерфейса пользователя, так как дескрипторы окон определяются относительно потока выполнения. Атрибут Synchronization, с другой стороны, определяет требования синхронизации. Здесь можно задать, что несколько потоков выполнения не вправе получить доступ к объекту одновременно, но поток выполнения, получающий доступ к объекту, может меняться.
С помощью этих атрибутов в конструкторе задаются четыре значения:
□ NOT_SUPPORTED определяет, что экземпляр класса не должен создаваться в контексте, который имеет либо сходство с потоком выполнения, либо с множеством синхронизации.
□ REQUIRED устанавливает, что требуется контекст со сходством с потоком выполнения/синхронизацией.
□ REQUIRES_NEW всегда обеспечивает получение нового контекста.
□ SUPPORTED означает, что независимо от того, какой контекст мы получаем, объект сможет в нем существовать.
Коммуникация между контекстами
Как же происходит коммуникация между контекстами? Клиент использует вместо реального объекта прокси. Оно создает сообщение, которое передается в канал, и приемники могут выполнить перехват. Тот же самый механизм используется для коммуникации между различными доменами приложения или различными системами. Канал TCP или HTTP не требуется для коммуникации между контекстами, но канал здесь, конечно же, есть. Класс CrossContextChannel может использовать одну и ту же виртуальную память на клиентской и на серверной стороне канала, при этом для соединения контекстов не требуются форматтеры.
Удаленные объекты, клиенты и серверы
Прежде чем перейти к рассмотрению деталей архитектуры .NET Remoting, давайте рассмотрим кратко удаленный объект и очень маленькое простое клиентское серверное приложение, которое использует этот удаленный объект. Затем мы обсудим более подробно все необходимые шаги и параметры.
Реализуемый удаленный объект называется Hello. HelloServer является основным классом приложения на сервере, a HelloClient предназначен для клиента:
Удаленные объекты
Удаленные объекты требуются для распределенного вычисления. Объект, вызываемый удаленно с другой системы, выводится из объектов System.MarshalByRefObject.MarshalByRefObject и соответствует домену приложения, в котором они создаются. Это означает, что такие объекты никогда не переходят между доменами приложений; вместо этого используется объект прокси для доступа к удаленному объекту изнутри другого домена приложения. Другой домен приложения может существовать внутри того же процесса, другого процесса, или на другой системе.
Удаленный объект имеет распределенную идентичность. В связи с этим ссылка на объект может передаваться другим клиентам, и они также будут получать доступ к тому же объекту. Прокси знает об идентичности удаленного объекта.
Класс MarshalByRefObject имеет в дополнение к унаследованным методам из класса Object методы для инициализации и получения служб времени жизни. Службы времени жизни определяют, как долго живут удаленные объекты. Службы времени жизни и арендуемые свойства будут рассмотрены позже в этой главе.
Чтобы увидеть .NET Remoting в действии, создается простая библиотека классов для удаленного объекта. Класс Hello выводится из System.MarshalByRefObject. В конструкторе и деструкторе на консоли записывается сообщение, чтобы было известно о времени жизни объекта. Кроме того, имеется только один метод Greeting(), который будет вызываться от клиента.
Для того чтобы легко различать в последующих разделах сборку и класс, дадим им различные имена аргументов, которые используют вызовы метода. Присвоим сборке имя RemoteHello.dll, а классу — Hello. Типом проекта Visual Studio.NET, используемым для этого класса, является Visual C# Class Library:
namespace Wrox.ProfessionalCSharp {
using System;
/// <summary>
/// Краткое описание Class1
/// </summary>
public class Hello: System.MarshalByRefObject {
public Hello() {
Console.WriteLine("Constructor called");
}
~Hello() {
Console.WriteLine("Destructor called");
}
public string Greeting(string name) {
Console.WriteLine("Greeting called");
return "Hello, " + name;
}
}
}