• Что такое .NET Remoting 
  • Web Services Anywhere
  • CLR Object Remoting
  • Обзор .NET Remoting
  • Контексты
  • Активизация
  • Атрибуты и свойства
  • Коммуникация между контекстами
  • Удаленные объекты, клиенты и серверы
  • Удаленные объекты
  • Простой сервер
  • Простой клиент
  • Архитектура .NET Remoting
  • Каналы 
  • Задание свойств канала
  • Подключаемость канала
  • Форматтеры
  • ChannelServices и RemotingContiguration
  • Сервер для активизированных клиентом объектов
  • Активизация объектов
  • URL-приложения
  • Активация хорошо известных объектов
  • Активизация объектов, активизированных клиентом
  • Объекты прокси
  • Сообщения
  • Приемники сообщений
  • Уполномоченный приемник
  • Приемник серверного контекста
  • Объектный приемник
  • Передача объектов в удаленные методы
  • Направляющие атрибуты
  • Управление временем жизни
  • Обновление аренды
  • Классы, используемые для управления временем жизни
  • Пример: получение информации об аренде
  • Изменение используемых по умолчанию конфигураций аренды
  • Конфигурационные файлы
  • Конфигурация сервера для хорошо известных объектов
  • Конфигурация клиента для хорошо известных объектов
  • Серверная конфигурация для активизированных клиентом объектов
  • Клиентская конфигурация для активизированных клиентом объектов
  • Серверный код, использующий конфигурационные файлы
  • Клиентский код, использующий конфигурационные файлы
  • Службы времени жизни в конфигурационных файлах
  • Инструменты для файлов удаленной конфигурации
  • Приложения хостинга
  • Хостинг удаленных серверов в ASP.NET
  • Классы, интерфейсы и SOAPSuds
  • Интерфейсы
  • SOAPSuds
  • Отслеживание служб
  • Асинхронная удаленная работа
  • Атрибут OneWay
  • Удаленное выполнение и события
  • Удаленный объект
  • Аргументы событий
  • Сервер
  • Приемник событий
  • Клиент
  • Выполнение программы
  • Контексты вызова
  • Заключение
  • Главa 23

    Создание распределенных приложений с помощью .NET Remoting

    В главе 17 были рассмотрены службы Web, которые позволяют вызывать объекты на удаленном сервере. Использование сервера Web и протокола SOAP не всегда достаточно эффективно для приложений интранет. Протокол SOAP означает большие накладные расходы при пересылке большого объема данных. Для быстрых решений интранет можно использовать просто сокеты, как это делалось в предыдущей главе. В "старом мире", как известно, программы писали с использованием DCOM. С помощью DCOM можно вызывать методы на объектах, выполняющихся на сервере. Программная модель всегда является одной и той же, если объекты применяются на сервере или на клиенте.

    Без DCOM мы вынуждены иметь дело с портами и сокетами, уделяя внимание целевым платформам в связи с возможным различным представлением данных, и создавать специальные протоколы, в которых сообщения посылаются сокету, чтобы, в конце концов, вызвать некоторые методы. DCOM обрабатывает все эти вопросы для программиста.

    Заменой DCOM является .NET Remoting. В противоположность DCOM .NET Remoting может использоваться также в решениях Интернета, а для них DCOM недостаточно гибок и эффективен. С помощью .NET Remoting можно адаптировать и расширить любую часть архитектуры, поэтому подходит практически для любого удаленного сценария. В этой главе будут рассмотрены:

    □ Архитектура .NET Remoting

    □ Каналы, сообщения, приемники

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

    □ Удаленные свойства с конфигурационными файлами

    □ Возможности расширения рабочей среды

    □ Использование возможностей удаленного управления в приложениях ASP.NET

    Прежде всего выясним, что такое .NET Remoting.

    Что такое .NET Remoting 

    Два выражения могут описать .NET Remoting: Web Services Anywhere и CLR Object Remoting. Рассмотрим, что это означает. 

    Web Services Anywhere

    Выражение Web Services Anywhere используется в .NET Remoting и означает, что с помощью .NET Remoting службы Web могут применяться в любом приложении с помощью любого транспорта, используя какое угодно кодирование полезной нагрузки. .NET Remoting является предельно гибкой архитектурой.

    Совместное использование SOAP и HTTP — только способ вызова удаленных объектов. Транспортный канал является подключаемым и может заменяться. Мы получаем каналы HTTP и TCP, представленные классами

    HttpChannel
    и
    TcpChannel
    . Мы вправе создать транспортные каналы для использования UDP, IPX или механизма общей памяти — выбор полностью зависит от программиста.

    Кодирование полезной нагрузки также можно заменить. Компания Microsoft предоставляет SOAP и механизмы двоичного кодирования. Можно использовать средство форматирования (форматтер) SOAP с помощью канала НТTР, но и использовать HTTP, применяя двоичный форматтер. Конечно оба эти форматтера можно использоватъ также с каналом TCP.

    .NET Remoting не только делает возможным использование служб Web в каждом приложении .NET, но позволяет также предложить возможности службы Web в каждом приложении. Не имеет значения, создается ли консольное приложение или приложение для Windows, Windows Service или компонент COM+ — службы Web могут использоваться везде.

    Термин подключаемый (pluggable) часто используется в .NET Remoting.

    Подключаемый означает, что определенная часть проекта разработана так, что она может заменяться специальной реализацией.

    CLR Object Remoting

    CLR Object Remoting везде располагается поверх служб Web. CLR Object Remoting облегчает использование служб Web. Все конструкции языка, такие как конструкторы, делегаты, интерфейсы, методы, свойства и поля, могут использоваться с удаленными объектами. CLR Object Remoting имеет дело с активацией, распределенной идентификацией, временем жизни и контекстами вызова.

    Обзор .NET Remoting

    .NET Remoting может использоваться для доступа к объектам в домене другого приложения независимо от того, находятся ли два объекта внутри одного процесса, в разных процессах или на разных системах.

    Удаленные сборки можно сконфигурировать для локальной работы в домене приложения или как часть удаленного приложения. Если сборка является частью удаленного приложения, то клиент получает для общения прокси вместо реального объекта. Прокси посылает сообщение в канал.

    Приложения .NET работают внутри домена приложения. Домен приложения можно рассматривать как подпроцесс внутри процесса, Традиционно процессы используются как изолирующая граница. Приложение, выполняющееся в одном процессе, не может получить доступ и разрушить память в другом процессе. Чтобы приложения общались друг с другом, требуется межпроцессная коммуникация. При использовании .NET домен приложения выступает новой границей безопасности внутри процесса, так как код CIL является проверяемым и обеспечивает безопасность типов данных. Различные приложения могут выполняться внутри одного процесса, но внутри различных доменов приложений. Объекты внутри одного домена приложений могут взаимодействовать напрямую. Чтобы получить доступ к объектам в другом домене приложений, требуется прокси.

    Больше о доменах приложений можно узнать в главе 8.

    Прежде чем перейти к внутренней функциональности .NET Remoting, давайте рассмотрим основные элементы архитектуры:

    □ Удаленный объект является объектом, который выполняется на сервере. Клиент не вызывает методы на этом объекте напрямую, а использует для этого прокси. С помощью .NET легко отличить удаленные объекты от локальных: каждый класс, производный из

    MarshalByValueObject
    , никогда не покидает свой домен приложений. Клиент может вызывать методы на удаленном объекте через прокси.

    □ Канал используется для коммуникации между клиентом и сервером. Существует клиентская и серверная часть канала. С помощью .NET Framework мы получаем два типа каналов, которые общаются через TCP или HTTP. Можно также создать специальный канал, который поддерживает связь с помощью другого протокола.

     Сообщения посылаются в канал. Они создаются для коммуникации между клиентом и сервером и хранят информацию об удаленных объектах, именах вызванных методов и всех аргументах.

    □ Форматтер определяет, как сообщения передаются в канал. Вместе с .NET Framework мы получаем форматтеры SOAP и двоичный. Форматтер SOAP можно использовать для коммуникации со службами Web, которые не основываются на .NET Framework. Двоичные форматтеры действуют значительно быстрее и могут эффективно использоваться в среде интранет. Конечно, имеется возможность создать специальный форматтер.

     Провайдер форматтера используется для соединения форматтера с каналом. Создавая канал, можно выбрать провайдер форматтера, и этот выбор в свою очередь, определяет форматтер, который будет использоваться для передачи данных в канал.

    □ Клиент вызывает методы на прокси, а не на удаленном объекте. Существует два типа прокси: прозрачный прокси и реальный прокси. Прозрачный прокси выглядит для клиента как удаленный объект. Клиент может вызывать методы, реализуемые удаленным объектом на прозрачном прокси. В свою очередь, прозрачный прокси вызывает метод

    Invoke()
    на реальном прокси. Метод
    Invoke()
    использует приемник сообщений для передачи сообщения в канал.

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

    □ Клиент может использовать активатор для создания удаленного объекта на сервере или для получения прокси активированного сервером объекта.

    □ 

    RemotingConfiguration
    является служебным классом для конфигурирования удаленных серверов и клиентов. Этот класс используется для чтения конфигурационных файлов или для динамического конфигурирования удаленных объектов.

    □ 

    ChannelServices
    является служебным классом для регистрации каналов и затем — для отправки сообщений в канал.

    Чтобы получить представление о функциональности, давайте рассмотрим концептуально, как элементы сочетаются друг с другом.

    Когда клиент вызывает методы на удаленном объекте, он на самом деле вызывает вместо этого методы на прозрачном прокси. Прозрачный прокси выглядит как реальный объект, он реализует открытые методы реального объекта. Прозрачный прокси узнает об открытых методах, используя механизм отражения для считывания метаданных из сборки.

    Прозрачный прокси, в свою очередь, вызывает реальный прокси. Реальный прокси отвечает за отправку сообщения в канал. Реальный прокси является подключаемым, можно заменить его с помощью специальной реализации, которая применяется для записи журнала для другого способа поиска канала и т.д. Используемая по умолчанию реализация реального прокси находит совокупность (или цепочку) уполномоченных приемников и передает сообщение в первый уполномоченный приемник. Уполномоченный приемник может перехватить и изменить сообщение. Примерами таких приемников являются приемники отладки, системы безопасности, синхронизации. Последний уполномоченный приемник досылает сообщение в канал. Как сообщение передается по линиям связи, зависит от форматтера. Ранее уже упоминалось, что имеются SOAP и двоичный форматтеры, которые также являются подключаемыми. Канал отвечает либо за соединение с принимающим сокетом на сервере, либо за отправку форматированных данных. Со специальным каналом можно производить различные действия, необходимые для передачи данных на другую сторону.

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

    .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;

      }

     }

    }

    Простой сервер

    Для сервера используется консольное приложение C#. Для применения класса

    TcpServerChannel
    необходимо сослаться на сборку
    System.Runtime.Remoting.dll
    . Также требуется, чтобы мы ссылались на созданную ранее сборку
    RemoteHello.dll
    .

    В методе

    Main()
    создается объект
    System.Runtime.Remoting.Channel.Тcр.TcpServerChannel
    с портом номер 8086. Этот канал регистрируется в классе
    System.Runtime.Remoting.Channels.ChannelServices
    , чтобы сделать его доступным для удаленных объектов. Тип удаленного объекта регистрируется с помощью
    System.Runtime.Remoting.RemotingConfiguration.RegisterWellKnownServiceType
    . Здесь определяется тип класса в удаленном объекте, используемый клиентом URI, и режим. Режим
    WellKnownObject.SingleCall
    означает, что для каждого вызова метода создается новый экземпляр, мы не храним состояние в удаленном объекте.

    После регистрации удаленного объекта продолжим выполнение сервера, пока не будет нажата клавиша:

    using System;

    using System.Runtime.Remoting;

    using System.Runtime.Remoting.Channels;

    using System.Runtime.Remoting.Channels.Tcp;


    namespace Wrox.ProfessionalCSharp {

     /// <summary>

     /// Краткое описание Class1

     /// </summary>

     public class HelloServer {

      public static void Main(string[] args) {

       TcpServerChannel channel = new TcpServerChannel(8086);

       ChannelServices.RegisterChannel(channel);

       RemotingConfiguration.RegisterWellKnownServiceType(

        typeof(Hello), "Hi", WellKnownObjectMode.SingleCall);

       System.Console.WriteLine("hit to exit");

       System.Console.ReadLine();

      }

     }

    }

    Простой клиент

    Клиент также является консольным приложением C#. И здесь делается ссылка на сборку

    System.Runtime.Remoting.dll
    , чтобы можно было использовать класс
    TcpClientChannel
    . Кроме того, имеется ссылка на сборку
    RemoteHello.dll
    . Хотя объект будет создаваться на удаленном сервере, нам понадобится сборка на стороне клиента, чтобы прокси прочитал метаданные во время выполнения.

    В клиентской программе создается объект

    TcpClientChannel
    , который регистрируется в
    ChannelServices
    . Для
    TcpChannel
    используется конструктор по умолчанию, поэтому выбирается свободный порт. Затем используется класс
    Activator
    для возврата прокси удаленному объекту. Прокси является типом
    System.Runtime.Remoting.Proxies._TransparentProxy
    . Этот объект выглядит как реальный. Это делается с помощью механизма отражения, в котором считываются метаданные реального объекта. Прозрачный прокси использует реальный для пересылки сообщений в канал:

    using System;

    using System.Runtime.Remoting.Channels;

    using System.Runtime.Remoting.Channels.Tcp;


    namespace Wrox.ProfessionalCSharp {

     /// <summary>

     /// Краткое описание Class1.

     /// </summary>

     public class HelloClient {

      public static void Main(string[] args) {

       ChannelServices.RegisterChannel(new TcpClientChannel());

       Hello obj = (Hello)Activator.GetObject(typeof(Hello), "tcp://localhost:8086/Hi");

       if (obj == null) {

        Console.WriteLine("could not locate server"); return;

       }

       for (int i = 0; i < 5; i++) {

        Console.WriteLine(obj.Greeting("Christian"));

       }

      }

     }

    }

    Когда запустятся сервер и клиентская программа Hello, Christian появится пять раз на клиентской консоли. В консольном окне серверного приложения можно будет увидеть вывод, аналогичный следующему:

    Первый конструктор вызывается во время регистрации удаленного объекта. Как утверждалось ранее, метод

    RemotingConfiguration.RegisterWellKnownServiceType()
    уже создает один экземпляр. Затем для каждого вызова метода создается новый экземпляр, так как был выбран режим активации
    WellKnownObjectMode.SingleCall
    . В зависимости от синхронизации и необходимых ресурсов, будут наблюдаться также вызовы деструктора. Если запустить клиент несколько раз, то вызовы деструктора будут присутствовать наверняка.

    Архитектура .NET Remoting

    Показав в действии простой клиент, сервер и изучив архитектуру .NET, перейдем к деталям. На основе созданных ранее программ будут рассмотрены механизмы архитектуры и способы расширения.

    Каналы 

    Канал используется для коммуникации между клиентом .NET и сервером. Среда .NET поставляется с классами каналов, которые общаются с помощью TCP или HTTP. Можно создать специальные каналы для других протоколов.

    Канал HTTP применяется большинством служб Web. Он использует для коммуникации протокол HTTP, так как брандмауэры обычно имеют открытым порт 80, чтобы клиенты могли получить доступ к серверам и службам Web. Прием на порту 80 также находится в распоряжении этих клиентов.

    В Интернете используется и канал TCP, но здесь брандмауэры должны специально конфигурироваться, чтобы клиенты получали доступ к указанному порту канала TCP. Канал TCP по сравнению с HTTP может применяется для коммуникации более эффективно в интранет.

    Когда выполняется вызов метода на удаленном объекте, объект клиентского канала посылает сообщение удаленному объекту канала.

    Как серверное, так и клиентское приложения, должны создавать канал. Следующий код показывает, как можно создать

    TcpServerChannel
    на серверной стороне:

    using System.Runtime.Remoting.Channels.Tcp;

    // ...

    TcpServerChannel channel = new TcpServerChannel(8086);

    Порт, который слушает сокет TCP, определяется аргументом конструктора. Серверный канал должен определить общеизвестный порт, а клиент использует его порт при доступе к серверу. Однако для создания

    TcpClientChannel
    на клиенте не требуется определять общеизвестный порт. Конструктор по умолчанию для
    TcpClientChannel
    выберет доступный порт, который передается серверу во время соединения, чтобы сервер мог послать данные назад клиенту.

    Создание нового экземпляра канала немедленно включает сокет на прослушивание состояния, которое можно проверить, вводя в командной строке

    netstat -а
    .

    Каналы HTTP используются аналогично каналам TCP. Определяется порт, где сервер может создать слушающий сокет. У нас также есть конструктор, в котором можно задать, передавая

    Boolean
    , что должен использоваться защищенный протокол HTTP.

    Сервер способен слушать несколько каналов. Здесь создаются каналы как HTTP, так и TCP:

    using System;

    using System.Runtime.Remoting;

    using System.Runtime.Remoting.Channels;

    using System.Runtime.Remoting.Channels.Tcp;

    using System.Runtime.Remoting.Channels.Http;


    namespace Wrox.ProfessionalCSharp {

     /// <summary>

     /// Краткое описание Class1.

     /// </summary>

     public class HelloServer {

      public static void Main(string[] args) {

       TcpServerChannel tcpChannel = new TcpServerChannel(8086);

       HttpServerChannel httpChannel = new HttpServerChannel (8085);

       // ...

    Класс канала должен реализовать интерфейс

    IChannel
    . Интерфейс
    IChannel
    имеет два свойства:

    ChannelIName
    — только для чтения, которое возвращает имя канала. Имя канала зависит от типа, например, канал HTTP называется HTTP.

    ChannelPriority
    — только для чтения с условием, что более одного канала используется для коммуникации между клиентом и сервером. Приоритет определяет порядок каналов. На клиенте канал с более высоким приоритетом выбирается первым для соединения с сервером.

    В зависимости от того, является ли канал клиентским каналом или серверным каналом, реализуются дополнительные интерфейсы. Серверные версии каналов реализуют интерфейс

    IChannelReceiver
    , клиентские версии — интерфейс
    IChannelSender
    .

    Классы

    HttpChannel
    и
    TcpChannel
    используются как для клиентов, так и для серверов. Они реализуют
    IChannelSender
    и
    IChannelReceiver
    . Эти интерфейсы являются производными из
    IChannel
    .

    IChannelSender
    клиентской стороны имеет в дополнение в
    IChannel
    единственный метод, называемый
    CreateMessageSink()
    , который возвращает объект, реализующий
    IMessageSink
    . Интерфейс
    IMessageSink
    применяется для размещения синхронных, а также асинхронных сообщений в канале. С помощью интерфейса серверной стороны
    IChannelReceiver
    канал можно перевести в режим прослушивания с помощью метода
    StartListening()
    и снова остановить с помощью метода
    StopListening()
    . Также имеется свойство для доступа к полученным данным.

    Информацию о конфигурации обоих каналов получают с помощью свойств классов каналов

    ChannelName
    ,
    ChannelPriority
    и
    ChannelData
    . С помощью свойства
    ChannelData
    получают информацию об URI, который хранится в классе
    ChannelDataStore
    . В классе
    HttpChannel
    имеется также свойство
    Scheme
    . Ниже представлен вспомогательный метод
    ShowChannelProperties()
    , демонстрирующий эти данные.

    protected static void ShowChannelProperties(IChannelReceiver channel) {

     Console.WriteLine("Name; " + channel.ChannelName");

     Console.WriteLine("Priority: " + channel.ChannelPriority);

     if (channel is HttpChannel) {

      HttpChannel httpChannel = channel as HttpChannel;

      Console.WriteLine("Scheme: " + httpChannel.ChannelScheme);

     }

     ChannelDataStore data = (ChannelDataStore)channel.ChannelData;

     foreach (string uri in data.ChannelUris) {

      Console.WriteLine("URI: " + uri);

     }

     Console.WriteLine();

    }

    После создания каналов вызывается метод

    ShowChannelProperties()
    :

    TcpServerChannel tcpChannel = new TcpServerChannel(8086);

    ShowChannelProperties(tcpChannel);

    HttpServerChannel httpChannel = new HttpServerChannel(8085);

    ShowChannelProperties(httpChannel);

    С помощью каналов TCP и HTTP будет получена следующая информация:

    Как можно видеть, именем по умолчанию для

    TcpServerChannel
    будет
    tcp
    , а канал HTTP называется
    http
    . Оба канала имеют свойство по умолчанию, равное 1 (в конструкторах заданы порты 8085 и 8086). URI каналов показывает протокол, имя хоста (в данном случае
    CNagel
    ) и номер порта.

    Задание свойств канала

    Можно задать все свойства канала в списке с помощью конструктора

    TcpServerChannel(IDictionary, IServerChannelSinkProvider)
    . Класс
    ListDictionary
    реализует
    IDictionary
    , поэтому свойства
    Name
    ,
    Priority
    и
    Port
    задаются с помощью этого класса.

    Для использования класса

    ListDictionary
    необходимо объявить использование пространства имен
    System.Collections.Specialized
    . В дополнение к параметру
    IDictionary
    передается параметр
    IServerChannelSinkProvider
    , в данном случае
    SoapServerFormatterSinkProvider
    вместо
    BinaryServerFormatterSinkProvider
    , который используется по умолчанию для
    TCPServerChannel
    . Реализация по умолчанию класса
    SoapServerFormatterSinkProvider
    ассоциирует класс
    SoapServerFormatterSink
    с каналом, применяющим
    SoapFormatter
    для преобразования данных передачи:

    ListDictionary properties = new ListDictionary();

    properties.Add("Name", "TCP Channel with a SOAP Formatter");

    properties.Add("Priority", "20");

    properties.Add("Port", "8086");

    SoapServerFormatterSinkProvider sinkProvider =

     new SoapServerFormatterSinkProvider();

    TcpServerChannel tcpChannel =

     new TcpServerChannel(properties.sinkProvider);

    ShowChannelProperties(tcpChannel);

    Вывод, который будет получен из запускаемого на сервере кода, показывает новые свойства канала TCP:

    Подключаемость канала

    Можно создать специальный канал для отправки сообщений с помощью транспортного протокола, отличного от HTTP или TCP, или расширить существующие каналы:

    □ Посылающая часть должна реализовать интерфейс

    IChannelSender
    . Наиболее важным является метод
    CreateMessageSink()
    , где клиент посылает URL, с помощью него создается экземпляр соединения с сервером. Здесь должен быть создан приемник сообщений, затем он используется прокси для отправки сообщений в канал.

    □ Получающая часть должна реализовать интерфейс

    IChannelReceiver
    . Необходимо запустить прослушивание с помощью свойства
    ChannelData
    , затем ожидать отдельного потока выполнения для получения данных от клиента. После демаршализации сообщения метод
    ChannelServices.SyncDispatchMessage()
    может использоваться для отправки сообщения объекту.

    Форматтеры

    .NET Framework предоставляет два класса форматтера:

    System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
    .

    System.Runtime.Serialization.Formatters.Soap.SoapFormatter
    .

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

    Оба эти класса форматтера реализуют интерфейс

    System.Runtime.Remoting.Messaging.IRemotingFormatter
    , который определяет методы
    Serialize()
    и
    Deserialize()
    для передачи и приема данных из канала.

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

    SoapServerFormatterSinkProvider
    , может передаваться как аргумент при создании канала. Провайдер источника форматтера реализует для сервера интерфейс
    IServerChannelSinkProvider
    , а для клиента
    IClientChannelSinkProvider
    . Оба эти интерфейса определяют метод
    CreateSink()
    , возвращающий источник форматтера, —
    SoapServerFormatterSinkProvider
    даст экземпляр класса
    SoapServerFormatterSink
    . На клиентской стороне имеется класс
    SoapClientFormatterSink
    , который использует в методах
    SyncProcessMessage()
    и
    AsyncProcessMessage()
    класс
    SoapFormatter
    для сериализации сообщения.
    SoapServerFormatterSink
    десериализует сообщение снова с помощью
    SoapFormatter
    .

    Все эти классы приемника и провайдера способны расширяться и заменяться собственными реализациями.

    ChannelServices и RemotingContiguration

    Служебный класс

    ChannelServices
    используется для регистрации каналов в среде выполнения .NET Remoting. С помощью этого класса можно также получить доступ ко всем зарегистрированным каналам. Это крайне полезно, если для конфигурирования канала используются конфигурационные файлы, так как в этом случае канал создается неявно (см. ниже).

    Канал регистрируется с помощью статического метода

    ChannelServices.RegisterChannel()
    .

    Здесь представлен серверный код для регистрации каналов HTTP и TCP:

    TcpChannel tcpChannel = new TcpChannel(8086);

    HttpChannel httpChannel = new HttpChannel(8085);

    Channel Services.RegisterChannel(tcpChannel);

    СhannelServices.RegisterChannel(httpChannel);

    Служебный класс

    ChannelServices
    можно теперь использовать для отправки синхронных и асинхронных сообщений и для отмены регистрации определенных каналов. Свойство
    RegisteredChannels
    возвращает массив
    IChannel
    всех зарегистрированных каналов. Возможно также и< пользование метода
    GetChannel()
    для доступа к определенному каналу по его имени. С помощью
    ChannelServices
    пишется специальная административная утилита для управления каналами. Вот небольшой пример показывающий как можно остановить режим прослушивания канала:

    HttpServerChannel channel =

     (HttpServerChannel)ChannelServices.GetChannel("http");

    channel.StorListening(null);

    Класс

    RemotingConfiguration
    — другой служебный класс .NET
    Remoting
    . На серверной стороне он используется для регистрации типов удаленных объектов активированных на сервере объектов и для маршализации удаленных объектов в ссылочный класс маршализованного объекта
    ObjRef
    .
    ObjRef
    является сериализуемым представлением объекта, которое было послано по линии связи. На клиентской стороне работает
    RemotingServices
    , демаршализуя удаленный объект, чтобы создать прокси из объектной ссылки.

    Вот серверный код для регистрации хорошо известного типа удаленного объекта в

    RemotingServices
    :

    RemotingConfiguration.RegisterWellKnownServiceType(

     typeof(Hello),                   // Тип

     "Hi",                            // URI

     WellKnownObjectMode.SingleCall); // Режим

    Первый аргумент метода

    RegisterWellKnownServiceType()
    Wrox.ProfessionalCSharp.Hello
    определяет тип удаленного объекта. Второй аргумент —
    Hi
    является универсальным идентификатором ресурса удаленного объекта, который используется клиентом для доступа к удаленному объекту. Последний аргумент — режим удаленного объекта. Режим может быть значением перечисления
    WellKhownObjectMode
    :
    SingleCall
    или
    Singleton
    .

    □ 

    SingleCall
    означает, что объект не сохраняет состояние. Для каждого вызова удаленного объекта создается новый экземпляр. Объект
    SingleCall
    создается на сервере с помощью метода
    RemotingConfiguration.RegisterWellKnownServiceТуре()
    и аргумента
    WellKnownObjectMode.SingleCall
    . Это очень эффективно для сервера, так как получается, что не требуется поддерживать ресурсы может быть для тысяч клиентов.

    □ С помощью режима Singleton объект совместно используется всеми клиентами сервера. Такие объектные типы могут применяться, если желательно предоставить доступ к некоторым данным всем клиентам. Это не должно быть проблемой для читаемых данных, но для данных чтения-записи необходимо знать о вопросах блокировки и масштабируемости. Объект

    Singleton
    создается на сервере с помощью метода
    RemotingConfiguration.RegisterWellKnownServiceType()
    и аргумента
    WellKnownObjectMode.Singleton
    . Необходимо убедиться, что данные не могут быть повреждены, когда клиенты получают доступ одновременно, но нужно и проверять, что блокирование сделано достаточно эффективно для достижения требуемой масштабируемости.

    Сервер для активизированных клиентом объектов

    Если удаленный объект должен хранить состояние для определенного клиента, то можно использовать активизированные клиентом объекты. В следующем разделе будут рассмотрены возможности клиентской стороны. В частности, как вызывать объекты, активизированные сервером, и объекты, активизированные клиентом. На серверной стороне активизированные клиентом объекты должны регистрироваться по-другому, чем объекты, активизированные сервером.

    Вместо вызова

    RemotingConfiguration.RegisterWellKnownType()
    необходимо вызвать
    RemotingServices.RegisterActivatedServiceType()
    . С помощью этого метода определяются только типы, но не URI. Причина этого заключается в том, что для активированных клиентом объектов создаются экземпляры различных объектных типов с помощью одного URI. URI для всех активированных клиентом объектов должен быть определен с помощью
    RemotingConfiguration.ApplicationName
    :

    RemotingConfiguration.ApplicationName = "HelloServer";

    RemotingConfiguration.RegisterActivatedServiceType(typeof (Hello));

    Активизация объектов

    Для клиентов возможно использование и создание удаленных объектов с помощью класса

    Activator
    . Мы можем получить прокси для активированного сервером или хорошо известного удаленного объекта с помощью метода
    GetObject()
    . Метод
    CreateInstance()
    возвращает прокси для активированного клиентом удаленного объекта.

    Вместо класса

    Activator
    для активации удаленных объектов используется также оператор new. Чтобы сделать это, удаленный объект должен конфигурироваться внутри клиента с помощью класса
    RemotingConfiguration
    .

    URL-приложения

    Во всех сценариях активации необходимо определять URL удаленного объекта. Этот URL является тем же самым, что и в браузере Web. Первая часть определяет протокол, за которым следует имя сервера или адрес IP, номер порта и URI, определенный при регистрации удаленного объекта на сервере в таком виде:

    protocol://server:port/URI

    Мы все время используем в нашем коде два примера URL: определяем протокол

    http
    и
    tcp
    , имя сервера
    localhost
    , номер порта 8085 и 8086, и
    URI
    как
    Hi
    , что дает нам запись:

    http://localhost:8085/Hi

    tcp://localhost:8086/Hi

    Активация хорошо известных объектов

    using System;

    using System.Runtime.Remoting;

    using System.Runtime.Remoting.Channels;

    using System.Runtime.Remoting.Channels.Tcp;

    /// ...

    TcpClientChannel channel = new TcpClientChannel();

    ChannelServices.RegisterChannel(channel);

    Hello obj = (Hello)Activator.GetObject(

     typeof(Hello), "tcp://localhost:8086/Hi");

    GetObject()
    является статическим методом класса
    System.Activator
    , который вызывает метод
    RemotingServices.Connect()
    для возврата объекта прокси удаленному объекту. Первый аргумент определяет тип удаленного объекта. Прокси реализует все открытые и защищенные методы и свойства, так что клиент может вызывать эти методы так же, как для реального объекта. Второй аргумент является URL удаленного объекта. Здесь используется строка
    tcp://localhost:8086/Hello
    , где
    tcp
    — протокол,
    localhost:8086
    — имя хоста и номер порта и, наконец,
    Hello
    — это URI объекта, который был определен с помощью
    RemotingConfiguration.RegisterWellKnownServiceType()
    .

    Вместо

    Activator.GetObject()
    можно также использовать
    RemotingServices.Connect()
    :

    Hello obj =

     (Hello)RemotingServices.Connect(typeof(Hello), "tcp://localhost:8086/Hi");

    Если вы предпочитаете задать просто оператор new для активизации хорошо известных удаленных объектов, то удаленный объект можно зарегистрировать на клиенте с помощью все того же

    RemotingConfiguration.RegisterWellKnownClientType()
    . Здесь понадобятся похожие аргументы: тип удаленного объекта и URI. Теперь можно использовать оператор new, который на самом деле не создает новый удаленный объект, а возвращает прокси аналогично
    Activator.GetObject()
    . Если удаленный объект регистрируется с флажком
    WellKnownObjectMode.SingleCall
    , правило остается тем же самым: удаленный объект создается с каждым вызовом метода:

    RemotingConfiguration.RegisterWellKnownClientType(

     typeof(Hello), "tcp://localhost:8086/Hi");

    Hello obj = new Hello();

    Активизация объектов, активизированных клиентом

    Удаленные объекты могут хранить состояние для клиента.

    Activator.CreateInstance()
    создает активированный клиентом удаленный объект. С помощью метода
    Activator.GetObject()
    удаленный объект создается при вызове метода и разрушается, когда метод заканчивается. Объект не хранит состояние сервера. В этом отличие от
    Activator.CreateInstance()
    . С помощью статического метода
    CreateInstance()
    запускается последовательность активации для создания удаленного объекта. Этот объект живет, пока не закончится время аренды и не произойдет сборка мусора. Позже мы рассмотрим механизм аренды.

    Некоторые из перезагруженных методов

    Activator.CreateInstance()
    используются только для создания локальных объектов. Для получения удаленных объектов требуется метод, куда можно передавать
    activationAttributes
    . Один из таких перезагруженных методов используется в примере. Этот метод получает два строковых параметра, первый из которых является именем сборки, второй — типом, а третий — массивом объектов. В объектном массиве канал и имя объекта определяются с помощью
    UrlAttribute
    . Чтобы использовать класс
    UrlAttribute
    , должно быть определено пространство имен
    System.Runtime.Remoting.Activation
    .

    object [] attrs = { new UrlAttribute("tcp://localhost:8086/Hello") };

    ObjectHandle handle =

     Activator.CreateInstance("RemoteHello",

     "Wrox.ProfessionalCSharp.Hello", attrs);

    if (handle == null) {

     Console.WriteLine("could not locate server");

     return 0;

    }

    Hello obj = (Hello)handle.Unwrap();

    Console.WriteLine(obj.Greeting("Christian"));

    Конечно, для активизированных клиентом объектов также возможно использование оператора

    new
    вместо класса
    Activator
    . Таким образом, мы должны зарегистрировать активизированный клиентом объект с помощью
    RemotingConfiguration.RegisterActivatedClientType()
    . В архитектуре активизированных клиентом объектов оператор new не только возвращает прокси, но также создает удаленный объект:

    RemotingConfiguration.RegisterActivatedClientType(

     typeof (Hello), "tcp://localhost:8086/HelloServer");

    Hello obj = new Hello();

    Объекты прокси

    Методы

    Activator.GetObject()
    и
    Activator.CreateInstance()
    возвращают клиенту прокси. На самом деле создается два прокси (прозрачный и реальный). Прозрачный прокси выглядит как удаленный объект, он реализует все открытые методы удаленного объекта, вызывая метод
    Invoke()
    реального прокси. Реальный прокси посылает сообщения в канал посредством приемников сообщений.

    С помощью

    RemotingServices.IsTransparentProxy()
    проверяется, является ли объект на самом деле прозрачным прокси. Можно также добраться до реального прокси с помощью
    RemotingServices.GetRealProxy()
    . Используя отладчик, легко получить все свойства реального прокси:

    ChannelServices.RegisterChannel(new TCPChannel());

    Hello obj =

     (Hello)Activator.GetObject(typeof(Hello), "tcp://localhost:8086/Hi");

    if (obj == null) {

     Console.WriteLine("could not locate server");

     return 0;

    }

    if (RemotingServices.IsTransparentProxy(Obj)) {

     Console.WriteLine("Using a transparent proxy");

     RealProxy proxy = RemotingServices.GetRealProxy(obj);

     // proxy.Invoke(message);

    }

    Подключаемость прокси

    Реальный прокси может заменяться специально созданным прокси. Специально созданный прокси расширяет базовый класс

    System.Runtime.Remoting.RealProxy
    . Мы получаем тип удаленного объекта в конструкторе специального прокси. Вызов конструктора для
    RealProxy
    создает прозрачный прокси в дополнение к реальному. В конструкторе могут быть доступны зарегистрированные каналы с помощью класса
    ChannelServices
    для создания приемника сообщений в методе
    IChannelSender.CreateMessageSink()
    . Помимо реализации конструктора, специальный канал переопределяет метод
    Invoke()
    . В
    Invoke()
    получают сообщение, которое затем анализируется и посылается в приемник сообщений.

    Сообщения

    Прокси посылает сообщение в канал. На серверной стороне будет сделан вызов метода после анализа сообщения, поэтому давайте рассмотрим сообщения.

    Имеется несколько классов сообщений для вызова методов, ответов, возврата сообщений и т.д. Все классы сообщений должны реализовывать интерфейс

    IMessage
    . Этот интерфейс имеет единственное свойство:
    Properties
    . Это свойство представляет словарь, где
    URI
    указывает объект, а вызываемые
    MethodName
    ,
    MethodSignature
    ,
    TypeName
    ,
    Args
    и
    CallContext
    являются пакетами.

    Ниже представлена иерархия классов и интерфейсов сообщений:

    Посылаемое реальному прокси сообщение является

    MethodCall
    . С помощью интерфейсов
    IMethodCallMessage
    и
    IMethodMessage
    мы имеем более простой доступ к свойствам сообщения, чем через интерфейс
    IMessage
    . Вместо использования интерфейса
    IDictionary
    мы имеем прямой доступ к имени метода,
    URI
    , аргументам и т.д. Реальный прокси возвращает
    ReturnMessage
    прозрачному прокси.

    Приемники сообщений

    Метод

    Activator.GetObject()
    вызывает
    RemotingServicesConnect()
    для соединения с хорошо известным объектом. В методе
    Connect()
    происходит
    Unmarshal()
    , где создается не только прокси, но и уполномоченные приемники. Прокси использует цепочку уполномоченных приемников для передачи сообщения в канал. Все приемники являются перехватчиками, которые могут изменять сообщение и выполнять некоторые дополнительные действия, такие как создание блокировки, запись события, выполнение проверки безопасности и т.д.

    Все приемники событий реализуют интерфейс

    IMessageSink
    . Такой интерфейс определяет одно свойство и два метода:

    □ Свойство

    NextSink
    используется приемником для получения следующего приемника и передачи сообщения дальше.

    □ Для синхронных сообщений вызывается метод

    SyncProcessMessage()
    предыдущим приемником или удаленной инфраструктурой. Он имеет параметр
    IMessage
    для отправки и возврата сообщения.

    □ Для асинхронных сообщений вызывается метод

    AsyncProcessMessage()
    предыдущим приемником в цепочке или удаленной инфраструктурой.
    AsyncProcessMessage()
    имеет два параметра, куда могут передаваться сообщение и приемник сообщения, который получает ответ.

    Рассмотрим три доступных для использования приемника сообщения.

    Уполномоченный приемник

    Можно получить цепочку уполномоченных приемников с помощью интерфейса

    IEnvoyInfo
    . Маршализованная объектная ссылка
    ObjRef
    имеет свойство
    EnvoyInfo
    , которое возвращает интерфейс
    IEnvoyInfo
    . Список уполномоченных приемников создается из серверного контекста, поэтому сервер может добавлять функциональность клиенту. Уполномоченные приемники собирают информацию об идентичности клиента и предают ее серверу.

    Приемник серверного контекста

    Когда сообщение получено на серверной стороне канала, оно передается приемникам серверного контекста. Последний из этих приемников направляет сообщение в цепочку объектных приемников.

    Объектный приемник

    Объектный приемник ассоциируется с определенным объектом. Если объектный класс определяет атрибуты определенного контекста, то для объекта создаются приемники контекста.

    Передача объектов в удаленные методы

    Типы параметров для вызовов удаленных методов не ограничены только базовыми типами данных, но могут также быть классами, которые определяет программист. Для удаленных методов различают три типа классов:

    Классы, маршализуемые по значению, обычно сериализуются через канал. Классы, которые должны быть маршализованы, либо реализуют интерфейс

    ISerializable
    , либо помечаются с помощью атрибута
    [Serializable]
    . Объекты этих классов не имеют удаленной идентичности, так как весь объект маршализуется через канал, а объект, который сериализуется клиенту, является независимым от серверного объекта (или наоборот). Классы, маршализуемые по значению, называются также несвязанными классами, так как они не имеют данных, которые зависят от домена приложения.

    □ Классы, маршализуемые по ссылке, имеют удаленную идентичность. Объекты не передаются по линиям связи, а вместо этого возвращается прокси. Класс, который маршализуется по ссылке, должен выводиться из

    MarshalByRefObject
    . Объекты
    MarshalByRefObject
    называют объектами, связанными с доменом приложения. Специализированной версией
    MarshalByRefObject
    является класс
    ContextBoundObject
    : абстрактный класс
    ContextBoundObject
    выводится из
    MarshalByRefObject
    . Если класс выводится из
    ContextBoundObject
    , требуется прокси даже в том же самом домене приложения, когда пересекаются границы контекстов.

    □ Классы, которые не являются сериализуемыми и не выводятся из

    MarshalByRefObject
    , не могут использоваться в параметрах открытых методов удаленных объектов. Эти классы связаны с доменом приложения, где они созданы. Такие классы должны использоваться, если класс имеет члены данных, допустимые только в домене приложения, такие как дескриптор файла Win32.

    Чтобы увидеть маршализацию в действии, изменим удаленный объект для пересылки двух объектов клиенту: пусть класс

    MySerialized
    посылает маршализацию по значению, а класс
    MyRemote
    маршализует по ссылке. В методах сообщение записывается на консоль, чтобы можно было проверять, сделан ли вызов на клиенте или на сервере. Кроме того, класс Hello изменяется, чтобы возвращать экземпляры
    MySerilized
    и
    MyRemote
    :

    using System;

    namespace Wrox.ProfessionalCSharp {

     [Serilizable]

     public сlass MySerilized {

      public MySerilized(int val) {

       a = val;

      }

      public void Foo() {

       Console.WriteLine("MySerialized.Foo called");

      }

      public int A {

       get {

        Console.WriteLine("MySerialized A called");

        return a;

       }

       set {

        a = value;

       }

      }

      protected int a;

     }


     public class MyRemote : System.MarshalByRefObject {

      public MyRemote(int val) {

       a = val;

      }

      public void Foo() {

       Console.WriteLine("MyRemote.Foo called");

      }

      public int A {

       get

        Сonsole.WriteLine("MyRemote.A called");

        return a;

       }

       set {

        a = value;

       }

      }

      protected int a;

     }


     /// <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;

      }

      public MySerialized GetMySerilized() {

       return new MySerialized(4711);

      }

      public MyRemote GetMyRemote() {

       return new MyRemote(4712);

      }

     }

    }

    Клиентское приложение также необходимо изменить, чтобы увидеть результаты при использовании маршализации объектов по значению и по ссылке. Мы вызываем методы

    GetMySerialized()
    и
    GetMyRemote()
    , чтобы получить новые объекты и проверить, не используется ли прозрачный прокси.

    ChannelServices.RegisterChannel(new TcpChannel());

    Hello obj =

     (Hello)Activator.GetObject(typeof(Hello),

     "tcp://localhost:8086/Hi");

    if (obj == null) {

     Console.WriteLine("could not locate server");

     return;

    }

    MySerialized ser = obj.GetMySerialized();

    if (!RemotingServices.IsTransparentProxy(ser)) {

     Console.WriteLine("ser is not a transparent proxy");

    }

    ser.Foo();

    MyRemote rem = obj.GetMyRemote();

    if (RemotingServices.IsTransparentProxy(rem)) {

     Console.WriteLine("rem is a transparent proxy");

    }

    rem.Foo();

    В консольном окне клиента видно, что объект

    ser
    вызывается на клиенте. Этот объект не является прозрачным прокси, так как он сериализуется клиенту. В противоположность этому, объект
    rem
    на клиенте является прозрачным прокси. Методы, вызванные для этого объекта, передаются на сервер:

    В серверном выводе можно видеть, что метод

    Foo()
    вызывается с удаленным объектом
    MyRemote
    :

    Направляющие атрибуты

    Удаленные объекты никогда не передаются по линиям связи в отличие от типов данных значений и сериализуемых классов. Иногда желательно послать данные только в одном направлении. Это особенно важно, когда данные передаются по сета. С помощью COM можно было объявить для аргументов направляющие атрибуты

    [in]
    ,
    [out]
    и
    [in, out]
    , если данные должны посылаться на сервер, клиенту или в обоих направлениях.

    В C# существуют аналогичные атрибуты как часть языка: параметры методов

    ref
    и
    out
    . Параметры методов
    ref
    и
    out
    могут использоваться для типов данных значений и для ссылочных типов, которые способны сериализоваться. С помощью параметра
    ref
    аргумент маршализуется в обоих направлениях,
    out
    идет от сервера клиенту, а в отсутствие параметра метода посылает данные серверу.

    Управление временем жизни

    Как клиент и сервер определяют, какая возникла проблема и что при этом другая сторона более недоступна?

    Для клиента ответ может быть коротким. Как только клиент вызывает метод для удаленного объекта, мы получаем исключение типа

    System.Runtime.Remoting.RemotingException
    . Необходимо просто обработать это исключение и сделать, например, повторную попытку или записать в журнал, информировать пользователя и т.д.

    А что же сервер? Когда сервер обнаруживает, что клиент отсутствует (что означает возможность очистить ресурсы, которые он удерживает для клиента)? Если ждать следующего вызова метода с клиента, он может никогда не появиться. В области COM протокол DCOM использовал механизм ping. Клиент посылал на сервер ping с информацией об используемых объектах. Так как клиент мог иметь на сервере сотни используемых клиентов, то, чтобы сделать этот механизм более эффективным, посылалась информация не обо всех объектах, а только о различии с предыдущим ping.

    Этот механизм был эффективен в LAN, но не подходит для Интернета. Подумайте о тысячах или миллионах клиентов, посылающих ping-информацию на сервер. .NET Remoting использует существенно лучшее масштабируемое решение для управления временем жизни — LDGC (Leasing Distributed Garbage Collector — Сборщик мусора распределенной аренды. 

    Это управление временем жизни активно только для активизированных клиентом объектов. Объекты

    SingleCall
    могут разрушаться после каждого вызова метода, так как они не сохраняют состояние. Активизированные клиентам объекты имеют состояние и нам необходимо знать о ресурсах. Для активизированных клиентом объектов, на которые ссылаются из вне домена приложения, создается аренда. Аренда имеет время аренды. Когда время аренды достигает нуля, аренда заканчивается, удаленный объект отсоединяется и, наконец, мусор убирается.

    Для управления временем жизни можно сконфигурировать следующие значения:

    LeaseTime
    определяет время, пока не закончится аренда.

    □ 

    RenewOnCallTime
    является временем, которое аренда задает для вызова метода, если текущее время аренды имеет меньшее значение.

    □ Если спонсор недоступен в течение

    SponsorshipTimeout
    , то удаленная инфраструктура ищет следующего спонсора. Если больше нет спонсоров, аренда заканчивается.

    LeaseManagerPollTime
    определяет интервал времени, в течение которого менеджер аренды проверяет отслуживший объект.

    Конфигурация аренды Значение по умолчанию (секунды)
    LeaseTime
    300
    RenewOnCallTime
    120
    SponsorshipTimeout
    120
    LeaseManagerPollTime
    10

    Обновление аренды

    Как показано в таблице, время аренды по умолчанию для объекта составляет 300 секунд. Если клиент вызывает метод на объекте, когда аренда истекла, возникает исключение. Если имеется клиент, где удаленный объект может понадобиться на время более 300 с, то существует три способа обновления аренды:

    □ Неявное обновление делается автоматически, когда клиент вызывает метод на удаленном объекте. Если текущее время аренды меньше, чем значение

    RenewOnCallTime
    , то аренда задается как
    RenewOnCallTime
    .

    □ При явном обновлении клиент определяет новое время аренды. Это делается с помощью метода

    Renew()
    из интерфейса
    ILease
    . Доступ к интерфейсу
    ILease
    можно получить, вызывая метод
    GetLifetimeService()
    на прозрачном прокси.

    □ Третьей возможностью обновления аренды является спонсорство. Клиент может создать спонсора, который реализует интерфейс

    ISponsor
    и регистрирует спонсора в службах аренды с помощью метода
    Register()
    из интерфейса
    ILease
    . Когда аренда заканчивается, у спонсора запрашивают ее продления. Механизм спонсорства используется, если на сервере требуются долгоживущие удаленные объекты.

    Классы, используемые для управления временем жизни

    ClientSponsor
    является спонсором, который реализует интерфейс
    ISponsor
    . Он применяется на клиентской стороне для продления аренды. С помощью интерфейса
    ILease
    можно получить всю информацию об аренде, все свойства аренды, а также время и состояние текущей аренды. Состояние определяется с помощью перечисления
    LeaseState
    . С помощью служебного класса
    LifetimeServices
    можно получить и задать свойства аренды для всех удаленных объектов в домене приложения.

    Пример: получение информации об аренде

    В этом небольшом примере кода доступ к информации аренды осуществляется с помощью вызова метода

    GetLifetimeService()
    на прозрачном прокси. Для интерфейса
    ILease
    необходимо открыть пространство имен
    System.Runtime.Remoting.Lifetime
    :

    Помните, что эти действия годятся только для активизированных клиентом объектов. Экземпляры объектов

    SingleCall
    создаются для каждого вызова метода, поэтому механизм аренды не используется.

    ILease lease = (ILease)obj.GetLifetimeService();

    if (lease != null) {

     Console.WriteLine("Lease Configuration:");

     Console.WriteLine(

      "InitialLeaseTime: " + lease.InitialLeaseTime);

     Console.WriteLine(

      "RenewOnCallTime: " + lease.RenewOnCallTime);

     Console.WriteLine(

      "SponsorshipTimeout: " + lease.SponsorshipTimeout);

     Console.WriteLine(lease.CurrentLeaseTime);

    }

    В результате получается следующий вывод в окне клиентской консоли:

    Изменение используемых по умолчанию конфигураций аренды

    Сам сервер может изменить используемую по умолчанию конфигурацию аренды для всех удаленных объектов, используя служебный класс

    System.Runtime.Remoting.Lifetime.LifetimeServices
    :

    LifetimeServices.LeaseTime = TimeSpan.FromMinutes(10);

    LifetimeServices.RenewOnCallTime = TimeSpan.FromMinutes(2);

    Если требуются другие используемые по умолчанию значения параметров времени жизни, в зависимости от типа удаленного объекта, можно изменить конфигурацию аренды удаленного объекта, переопределяя метод

    InitializeLifetimeService()
    базового класса
    MarshalByRefObject
    :

    public class Hello : System.MarshalByRefObject {

     public Hello() {

      Console.WriteLine("Constructor called");

     }

     ~Hello() {

      Console.WriteLine("Destructor called");

     }

     public override Object InitializeLifetimeService() {

      ILease lease = (ILease)base.InitializeLifetimeService();

      lеase.InitialLeaseTime = TimeSpan.FromMinutes(10);

      lease.RenewOnCallTime = TimeSpan.FromSeconds(40);

      return lease;

     }

    }

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

    Конфигурационные файлы

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

    .config
    . Для сервера
    HelloServer.exe
    конфигурационным файлом будет
    HelloServer.exe.config
    . В коде, загружаемом с web-сайта издательства Wrox, можно найти примеры конфигурационных файлов в корневом каталоге примеров с именами
    clientactivated.config
    ,
    wellknown.config
    и
    wellknownhttp.config
    . Чтобы воспользоваться ими, переименуйте их, как показано выше, и поместите в каталог, содержащий исполнимый файл.

    Вот только один пример, как мог бы выглядеть такой файл. Мы рассмотрим все различные конфигурационные параметры:

    <configuration>

     <system.runtime.remoting>

      <application name="Hello">

       <service>

        <wellknown mode="SingleCall" type="Wrox.ProfessionalCSharp.Hello, RemoteHello" objectUri="Hi" />

       </service>

       <channels>

        <channel type="System.Runtime.Remoting.Channels.Tcp.TcpChannel, System.Runtime.Remoting" port="6791" />

        <channel type="System.Runtime.Remoting.Channels.Http.HttpChannel, System.Runtime.Remoting" port="6792" />

       </channels>

      </application>

     </system.runtime.remoting>

    </configuration>

    <configuration>
    является корневым элементом XML для всех конфигурационных файлов .NET. Все удаленные конфигурации можно найти в подэлементе
    <system.runtime.remoting>
    .
    <application>
    является подэлементом
    <system.runtime.remoting>
    .

    Посмотрим на основные элементы и атрибуты в

    <system.runtime.remoting>
    :

    □ В элементе

    <application>
    определяется имя приложения с помощью атрибута
    name
    . На серверной стороне это имя сервера, а на клиентской стороне — имя клиентского приложения. Пример серверной конфигурации
    <application name="Hellо">
    определяет имя удаленного приложения
    Hello
    , которое используется клиентом как часть URL для доступа к удаленному объекту.

    □ На сервере элемент

    <service>
    используется для определения совокупности удаленных объектов. Он может иметь подэлементы
    <wellknown>
    и
    <activated>
    вместе с определенным типом удаленного объекта — well known или client-activated.

    □ Клиентской частью элемента

    <service>
    является
    <client>
    . Подобно элементу
    <service>
    он может иметь подэлементы
    <wellknown>
    и
    <activated>
    для определения типа удаленного объекта. В отличие от <service> элемент <client> имеет атрибут url для определения URL удаленного объекта.

    □ 

    <wellknown>
    является элементом, который используется на сервере и на клиенте для определения хорошо известных удаленных объектов. Серверная часть выглядит так:

    <wellknown mode="SingleCall" type="Wrox.ProfessionalCSharp.Hello, RemoteHello" objectURI="Hi" />

    □ В то время как атрибут

    mode
    может принимать значения
    SingleCall
    или
    Singleton
    ,
    type
    является типом удаленного класса, включая пространство имен
    Wrox.ProfessionalCSharp.Hello
    , за которым следует имя сборки
    RemoteHello
    . Именем удаленного объекта является
    objectURI
    , который зарегистрирован в канале. На клиенте атрибут
    type
    является таким же, как и для серверной версии.
    mode
    и
    objectURI
    не нужны, вместо них используется атрибут
    url
    для определения пути доступа к удаленному объекту: протокол, имя хоста, номер порта, имя приложения и URI объекта:

    <wellknown type="Wrox.ProfessionalCSharp.Hello, RemoteHello" url="tcp://localhost:6791/Hello/Hi" />

    □ Элемент

    <activated>
    используется для активированных клиентом объектов. С помощью атрибута
    type
    должны быть определены тип данных и сборка как для клиентского, так и для серверного приложений:

    <activated type="Wrox.ProfessionalCSharp.Hello, RemoteHello" />

    □ Для определения канала, используется элемент

    <channel>
    . Это подэлемент
    <channels>
    , так что совокупность каналов можно сконфигурировать для одного приложения. Его использование аналогично для клиентов и серверов. Атрибут
    type
    используется для определения типа канала и сборки. Атрибут
    port
    является номером порта, который нужен только для серверной конфигурации:

    <channels>

     <channel type = "System.Runtime.Remoting.Channels.Tcp.TcpChannel, System.Runtime.Remoting" port="6791" />

     <channel type = "System.Runtime.Remoting.Channels.Http.HttpChannel, System.Runtime.Remoting" port="6792" />

    </channels>

    Конфигурация сервера для хорошо известных объектов

    Этот пример файла

    wellknown.config
    имеет значение
    Hello
    для свойства
    Name
    . Мы используем канал TCP для прослушивания порта 6791, а канал HTTP для прослушивания порта 6792. Класс удаленного объекта —
    Wrox.ProfessionalCSharp.Hello
    в сборке
    RemoteHello.dll
    , объект в канале называется
    Hi
    , и используется режим
    SingleCall
    :

    <configuration>

     <system.runtime.remoting>

      <application name="Hello">

       <service>

        <wellknown mode="SingleCall" type="Wrox.ProfessionalCSharp.Hello, RemoteHello" objectUri ="Hi" />

       </service>

       <channels>

        <channel type="System.Runtime.Remoting.Channels.Tcp.TcpChannel, System.Runtime.Remoting" port="6791" />

        <channel type="System.Runtime.Remoting.Channels.Http.HttpChannel, System.Runtime.Remoting" port="6792" />

       </channels>

      </application>

     </system.runtime.remoting>

    </configuration>

    Конфигурация клиента для хорошо известных объектов

    Для хорошо известных объектов в клиентском конфигурационном файле

    wellknown.config
    необходимо определить сборку и канал. Типы для удаленного объекта можно найти в сборке
    RemoteHello.dll
    ,
    Hi
    является именем объекта в канале, a URI для удаленного типа
    Wrox.ProfessionalCSharp.Hello
    — это
    tcp://localhost:6791/Hi
    . На клиенте также работает канал TCP, но на клиенте не определяется порт, поэтому выбирается свободный порт.

    <configuration>

     <system.runtime.remoting>

      <application name="Client">

       <client url="tcp:/localhost:6791/Hello">

        <wellknown type = "Wrox.ProfessionalCSharp.Hello, RemoteHello" url="tcp://localhost:6791/Hello/Hi" />

       </client>

       <channels>

        <channel type="System.Runtime.Remoting.Channels.Tcp.TcpChannel, System.Runtime.Remoting" />

       </channels>

      </application>

     </system.runtime.remoting>

    </configuration>

    Внесем небольшое изменение в конфигурационный файл и можем использовать канал HTTP (как видно в

    wellknownhttp.config
    ):

    <client url="http://localhost:6792/Hello">

     <wellknown type="Wrox.ProfessionalCSharp.Hello, RemoteHello" url="http://localhost:6792/Hello/Hi" />

    </client>

    <channels>

     <channel type="System.Runtime.Remoting.Channels.Http.HttpChannel, System.Runtime.Remoting" />

    </channels>

    Серверная конфигурация для активизированных клиентом объектов

    Преобразуя только конфигурационный файл (который находится в

    clientactivated.config
    ), можно изменить сервер с активизированных сервером объектов на активизированные клиентом объекты. Здесь определяется подэлемент
    <activated>
    элемента
    <service>
    . С его помощью для серверной конфигурации должен быть определен атрибут
    type
    . Атрибут
    name
    элемента
    application
    определяет URI:

    <configuration>

     <system.runtime.remoting>

      <application name="HelloServer">

       <service>

        <activated type="Wrox.ProfessionalCSharp.Hello, RemoteHello" />

       </service>

       <channels>

        <channel type="System.Runtime.Remoting.Channels.Http.HttpChannel, System.Runtime.Remoting" ports="6788" />

        <channel type="System.Runtime.Remoting.Channels.Tcp.TcpChannel, System.Runtime.Remoting" ports="6789" /»

       </channels>

      </application>

     </system.runtime.remoting>

    </configuration>

    Клиентская конфигурация для активизированных клиентом объектов

    Файл

    clientactivated.config
    определяет активированный клиентом удаленный объект с помощью атрибута
    url
    элемента
    <client>
    и атрибута
    type
    элемента
    <activated>
    :

    <configuration>

     <system.runtime.remoting>

      <application>

       <client url="http://localhost:6788/HelloServer" >

        <activated type="Wrox.ProfessionalCSharp.Hello, RemoteHello" />

       </client>

       <channels>

        <channel type="System.Runtime.Remoting.Channels.Http.HttpChannel, System.Runtime.Remoting" />

        <channel type="System.Runtime.Remoting.Channels.Tcp.TcpChannel, System.Runtime.Remoting" />

       </channels>

      </application>

     </system.runtime.remoting>

    </configuration>

    Серверный код, использующий конфигурационные файлы

    В серверном коде необходимо сконфигурировать удаленное использование статического метода

    Configure()
    из класса
    RemotingConfiguration
    . Здесь создаются экземпляры всех определяемых каналов. Может быть мы захотим также узнать о конфигурациях каналов из серверного приложения. Поэтому созданы статические методы
    ShowActivatedServiceTypes()
    и
    ShowWellKnovmServiceTypes()
    , которые вызываются после загрузки и запуска удаленной конфигурации:

    public static void Main(string[] args) {

     RemotingConfiguration.Configure("HelloServer.exe.config");

     Console.WriteLine(

      "Application: " + RemotingConfiguration.ApplicationName);

     ShowActivatedServiceTypes();

     ShowWellKnownServiceTypes();

     System.Console.WriteLine("hit to exit");

     System.Console.ReadLine();

     return;

    }

    Эти две функции показывают данные конфигурации хорошо известных и активированных клиентом типов:

    public static void ShowWellKnownServiceTypes() {

     WellKnownServiceTypeEntry[] entries =

      RemotingConfiguration.GetRegisteredWellKnownServiceTypes();

     foreach (WellKnownServiceTypeEntry entry in entries) {

      Console.WriteLine("Assembly: " + entry.AssemblyName);

      Console.WriteLine("Mode: " + entry.Mode);

      Console.WriteLine("URI " + entry.ObjectUri);

      Console.WriteLine("Type: " + entry.TypeName);

     }

    }


    public static void ShowActivatedServiceTypes() {

     ActivatedServiceTypeEntry[] entries =

      RemotingConfiguration.GetRegisteredActivatedServiceTypes();

     foreach(ActivatedServiceTypeEntry entry in entries) {

      Console.WriteLine("Assembly: " + entry.AssemblyName);

      Console.WriteLine("Type: " + entry.TypeName);

     }

    }

    Клиентский код, использующий конфигурационные файлы

    В клиентском коде с помощью конфигурационного файла

    client.exe.config
    нужно сконфигурировать только удаленные службы. После этого можно использовать оператор new для создания новых экземпляров класса
    Remote
    независимо от того, происходит ли работа с активированными сервером или с активированными клиентов удаленными объектами. Но помните, что существует небольшая разница. Для активированных клиентом объектов теперь можно использовать произвольные конструкторы с помощью оператора
    new
    . Это невозможно для активированных сервером объектов и не имеет смысла в этом случае: объекты
    SingleCall
    не могут иметь состояния, так как они разрушаются вместе с каждым вызовом, объекты
    Singleton
    создаются только однажды. Вызов произвольных конструкторов полезен только для активированных клиентом объектов, так как только для этого вида объектов оператор new реально вызывает конструктор удаленного объекта:

    RemotingConfiguration.Configure("HelloClient.exe.config");

    Hello obj = new Hello();

    if (obj == null) {

     Console.WriteLine("could not locate server");

     return 0;

    }

    for (int i=0; i < 5; i++) {

     Console.WriteLine(obj.Greeting("Christian"));

    }

    Службы времени жизни в конфигурационных файлах

    Аренда конфигурации для удаленных серверов также может делаться с помощью конфигурационных файлов приложений. Элемент

    <lifetime>
    имеет атрибуты
    leaseTime
    ,
    sponsorshipTimeOut
    ,
    renewOnCallTime
    и
    pollTime
    :

    <configuration>

     <system.runtime.remoting>

      <application>

       <lifetime leaseTime="15M" sponsorshipTimeOut="4M" renewOnCallTime="3M" pollTime="30s" />

      </application>

     </system.runtime.remoting>

    </configuration>

    Используя конфигурационные файлы, можно изменить удаленную конфигурацию, редактируя файлы вместо работы с исходным кодом. Легко изменить канал для использования HTTP вместо TCP, изменить порт, имя канала, и т. д. С помощью добавления одной строки сервер может слушать два канала вместо одного.

    Инструменты для файлов удаленной конфигурации

    Не обязательно начинать создавать конфигурационный файл XML для .NET Remoting с чистого листа. Для этого существует несколько инструментов:

    □ При использовании версии .NET Remoting Beta 1 можно найти пример

    convertconfig.exe
    в списке примеров Framework SDK. С помощью этого инструмента можно преобразовать использовавшийся ранее компактный формат файлов в новый формат на основе XML.

    □ С помощью примера

    configfilegen.exe
    можно создать конфигурационный файл из сборки. Запустите эту программу без параметров, чтобы увидеть все возможные конфигурации. Следующая командная строка создает активированный клиентом (-а) конфигурационный файл для сервера (-s).

    configfilegen -ia:RemoteHello.dll -ос:HelloServer.exe.config -s -a

    Системный администратор использует утилиту .NET Admin, чтобы реконфигурировать существующие конфигурационные файлы. Утилиту .NET Admin можно запустить с помощью команды:

    mmc mecorcfg.msc

    При помощи этой утилиты можно изменить значения времени жизни, URI удаленных объектов и свойства канала.

    Приложения хостинга

    До этого момента все примеры серверов выполнялись на автономных (self-hosted) серверах .NET. Автономный сервер должен запускаться вручную. Удаленный сервер .NET может запускаться во множестве других типов приложений. В службе Windows сервер автоматически запускается во время старта, и кроме того, процесс может выполняться с полномочиями системной учетной записи. Создание служб Windows описано в главе 24.

    Хостинг удаленных серверов в ASP.NET

    B ASP.NET существует специальная поддержка для серверов .NET Remoting. ASP.NET может использоваться для автоматического запуска удаленных серверов. В противоположность приложениям exe, ASP.NET Remoting использует для конфигурации другой файл.

    Для того чтобы использовать инфраструктуру Информационного сервера Интернета (IIS) и ASP.NET, необходимо создать класс, произвольный из

    System.MarshalByRefObject
    , который имеет конструктор по умолчанию. Использованный ранее код для нашего сервера с целью создания и регистрации канала больше не требуется; это делается средой выполнения ASP.NET. Необходимо только создать виртуальный каталог на сервере Web, который отображает каталог, куда помещается конфигурационный файл
    web.config
    . Сборка удаленного класса должна находиться в подкаталоге
    bin
    .

    Чтобы сконфигурировать виртуальный каталог на сервере Web, воспользуйтесь Информационными службами ММС. Выберите Default Web Site и, открыв меню Action, создайте новый виртуальный каталог.

    Конфигурационный файл

    web.config
    на сервере Web должен быть помещен в домашний каталог виртуального сайта Web. Согласно используемой по умолчанию конфигурации IIS, используемый канал слушает порт 80.

    <configuration>

     <system.runtime.remoting>

      <application>

       <service>

        <wellknown mode="SingleCall" type="Wrox.ProfessionalCSharp.Hello, RemoteHello" objectUri = "HelloService.soap" />

       </service>

      </application>

     </system.runtime.remoting>

    </configuration>

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

    RemoteHello
    , которое было определено при создании виртуального сайта Web и URI удаленного объекта
    HelloService.soap
    , определенного в файле
    web.config
    . Не обязательно определять порт номер 80, так как это порт по умолчанию для протокола http:

    <configuration>

     <system.runtime.remoting>

      <application>

       <client url="http:/localhost/RemoteHello">

        <wellknown type="Wrox.ProfessionalCSharp.Hello, RemoteHello" url="http://localhost/RemoteHello/HelloService.soap" />

       </client>

       <channels>

        <channel type="System.Runtime.Remoting.Channels.Http.HttpChannel, System.Runtime.Remoting" />

       </channels>

      </application>

     </system.runtime.remoting>

    </configuration>

    Хостинг удаленных объектов в ASF.NET поддерживает только хорошо известные объекты.

    Классы, интерфейсы и SOAPSuds

    В клиент/серверных примерах, которые были выполнены до сих пор, мы всегда ссылались на удаленные объекты в клиентском приложении. В этом случае копируется код CIL удаленного объекта, хотя нужны только метаданные. Также невозможно, чтобы клиент и сервер программировались независимо. Значительно лучшим способом является использование вместо этого интерфейсов или утилиты

    SoapSuds.exe
    .

    Интерфейсы

    Мы имеем четкое разделение клиентского и серверного кода с помощью интерфейсов. Интерфейс просто определяет методы без реализации. Мы отделяем контакт между клиентом и серверов от реализации. Вот необходимые шаги для использование интерфейса:

    1. Определить интерфейс, который будет помещен в сборку.

    2. Реализовать интерфейс в классе удаленного объекта. Чтобы сделать это, необходимо сослаться на сборку интерфейса.

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

    4. На клиентской стороне сошлитесь на сборку интерфейса вместо сборки удаленного объекта.

    5. Клиент может теперь использовать интерфейс удаленного объекта, а не класс удаленного объекта. Объект можно создать с помощью класса

    Activator
    , как это делалось ранее. Нельзя при этом использовать
    new
    , так как невозможно создать экземпляр самого интерфейса.

    Интерфейс определяет контракт между клиентом и сервером. Два приложения могут теперь разрабатываться независимо друг от друга. Если при этом придерживаться старых правил COM об интерфейсах (что интерфейсы никогда не должны меняться), то не будет никаких проблем с версиями.

    SOAPSuds

    Можно также использовать утилиту

    soapsuds
    , чтобы получить метаданные из сборки,
    soapsuds
    преобразовывает сборки в XML Schemas, XML Schemas для погружения классов и другие директивы.

    Следующая команда преобразует тип

    Hello
    из сборки
    RemoteHello.dll
    в сборку
    HelloWrapper.dll
    , где генерируется прозрачный прокси, который вызывает удаленный объект.

    soapsuds -types:Wrox.ProfessionalCSharp.Hello, RemoteHello -oa:HelloWrapper.dll

    С помощью

    soapsuds
    можно также получить информацию о типах непосредственно с выполняющегося сервера:

    soapsuds -url:http://localhost:6792/hi -oa:HelloWrapper.dll

    На клиенте теперь можно ссылаться вместо исходной сборки на созданную soapsuds сборку. Некоторые из возможностей перечислены в таблице.

    Параметр Описание
    -url
    Извлекает схему из указанного URL.
    -proxyurl
    Если требуется прокси сервер для доступа к серверу, определите прокси с помощью этого параметра.
    -types
    Определяет тип и сборку для чтения из нее информации о схеме.
    -is
    Файл ввода схемы.
    -ia
    Файл ввода сборки.
    -os
    Файл вывода схемы.
    -oa
    Файл вывода сборки.

    Отслеживание служб

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

    System.Runtime.Remoting.Services.TrackingService
    предоставляет службу слежения для получения информации о том, когда происходит маршализация и демаршализация, когда вызываются удаленные объекты и разъединяются и т. д.

    □ С помощью служебного класса

    TrackingServices
    регистрируется и отменяется регистрация обработчика, который реализует
    ITrackingHandler
    .

    □ Интерфейс

    ITrackingHandler
    вызывается, когда на удаленном объекте или на прокси происходит событие. Можно реализовать три метода в обработчике:
    MarshaledObject()
    ,
    UnmarshaledObject()
    и
    DisconnectedObject()
    .

    Чтобы увидеть службы слежения в действии на клиенте и на сервере, создадим новую библиотеку классов

    TrackingHandler
    . Класс
    TrackingHandler
    реализует интерфейс
    ITrackingHandler
    . В методах задаются два аргумента: сам объект и
    ObjRef
    . С помощью
    ObjRef
    выдается информация об URI, канале и уполномоченных приемниках. Можно также присоединить новые приемники для добавления спонсоров всех вызываемых методов. В данном примере на консоль записывается URI и информация о канале.

    using System;

    using System.Runtime.Remoting;

    using System.Runtime.Remoting.Services;
     

    namespace Wrox.ProfessionalCSharp (

     public class TrackingHandler : ITrackingHandler {

      public TrackingHandler() {

      }

      public void MarshaledObject(object obj, ObjRef or) {

       Console.WriteLine("--- Marshaled Object " + obj.GetType() + " ---");

       Console.WriteLine("Object URI: " + or.URI);

       object[] ChannelData = or.ChannelInfo.ChannelData;

       foreach(object data in ChannelData) {

        ChannelDataStore dataStore = data as ChannelDataStore;

        if (dataStore != null) {

         foreach (string uri in dataStore.ChannelUris) {

          Console.WriteLine("Channel URI: " + uri);

         }

        }

    }

       Console.WriteLine("---------");

       Console.WriteLine();

      }

      public void UnmarshaledObject(object obj, ObjRef or) {

       Console.WriteLine("Unmarshal");

       public void DisconnectedObject(Object obj) {

       Console.WriteLine("Disconnect");

      }

     }

    }

    Серверная программа изменяется, чтобы регистрировать

    TrackingHandler
    . Необходимо добавить только две строки, чтобы зарегистрировать обработчик.

    using System.Runtime.Remoting.Services;

    // ...

    public static void Main(string[] args) {

     TrackingServices.RegisterTrackingHandler(new TrackingHandler());

     TCPChannel channel = new TCPChannel(8086);

     // ...

    При запуске сервера первый экземпляр создается во время регистрации хорошо известного типа, и мы получаем следующий вывод. Вызывается

    MarshaledObject()
    и выводит тип объекта для маршализации —
    Wrox.ProfessionalCSharp.Hello
    . С помощью Object URI мы видим GUID, который, используется внутренне в удаленной среде выполнения для различения определенных экземпляров и URI. С помощью канала URI можно проверить конфигурацию канала. В этом случае именем хоста будет
    Cnagel
    :

    Асинхронная удаленная работа

    Если серверные методы требуют времени для завершения работы и клиенту нужно произвести некоторую другую работу в это время- то необходимо запустить отдельный поток выполнения, чтобы сделать удаленный вызов. Асинхронные вызовы могут делаться на удаленном объекте так же, как они делаются на локальном объекте.

    Чтобы сделать асинхронный метод, создается делегат

    GreetingDelegate
    с тем же аргументом и возвращается значение как метод
    Greeting()
    удаленного объекта. Аргумент этого делегата является ссылкой на метод
    Greeting()
    . Мы запускаем вызов
    Greeting()
    , используя метод делегата
    BeginInvoke()
    . Второй аргумент
    BeginInvoke()
    является экземпляром:
    AsyncCallback
    , определяющим метод
    НеlloClient.Callback()
    , который вызывается когда удаленный метод заканчивается. В методе
    Callback()
    удаленный вызов заканчивается с помощью
    EndInvoke()
    :

    using System;

    using System.Runtime.Remoting;

    namespace Wrox.ProfessionalCSharp {

     public class HelloClient {

      private delegate String GreetingDelegate(String name);

      private statiс string greeting; public static old Main(string[] args) {

       RemotingConfiguration.Configure("HelloClient.exe.config");

       Hello obj = new Hello();

       if (obj == null) {

        Console.WriteLine("could not locate server");

        return 0;

       }

       // синхронная версия

       // string greeting = obj.Greeting("Christian");

       // асинхронная версия

       GreetingDelegate d = new GreetingDelegate(obj.Greeting);

       IAsyncResult ar = d.BeginInvoke("Christian", null, null);

       // выполнить некоторую работу и затем ждать

       ar.AsyncWaitHandle.WaitOne();

       if (ar.IsCompleted) {

        greeting = d.EndInvoke(ar);

       }

       Console. WriteLine(greeting);

      }

     }

    }

    О событиях, делегатах и асинхронных методах можно прочитать в главе 6.

    Атрибут OneWay

    Метод, который возвращает

    void
    и имеет только входящие параметры, может быть помечен атрибутом
    OneWay
    . Атрибут
    OneWay
    делает метод автоматически асинхронным независимо от того, как вызывает его клиент. Добавление метода
    TakeAWhile()
    в класс удаленного объекта
    RemoteHello
    соответствует созданию метода "породить и забыть". Если клиент вызывает его через прокси, то прокси немедленно возвращает управление клиенту. На сервере метод заканчивается немного позже:

    [OneWay]

    public Void TakeAWhile(int ms) {

     Console.WriteLine("TakeAWhile started");

     System.Threading.Thread.Sleep(ms);

     Console.WriteLine("TakeAWhile finished");

    }

    Удаленное выполнение и события

    С помощью .NET Remoting не только клиент может вызывать методы на удаленном объекте через сеть, но и сервер может также вызывать методы на клиенте. Для этого используются детали механизма основных свойств языка: делегаты и события.

    В принципе это простая архитектура. Сервер имеет объект, который может вызывать клиент, а клиент имеет объект, который в свою очередь может вызывать сервер:

    □ Удаленный объект на сервере должен объявить внешнюю функцию (делегата) с сигнатурой метода, который клиент будет реализовывать в обработчике.

    □ Аргументы, которые передаются клиенту с функцией-обработчиком, должны быть маршализуемыми. Поэтому все посылаемые клиенту данные должны быть сериализуемыми.

    □ Удаленный объект должен также объявить экземпляр функции делегата, модифицированный ключевым словом event. Клиент будет использовать его для регистрации обработчика.

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

    Чтобы понять это, рассмотрим пример. Создадим пять классов для всех частей обработки событий в .NET Remoting. Класс

    Server
    является удаленным сервером, таким как один из тех, которые уже до этого встречались. Класс
    Server
    будет создавать канал на основе информации из конфигурационного файла и регистрировать удаленный объект, который реализуется в классе
    RemoteObject
    в удаленной среде выполнения. Удаленный объект объявляет аргументы делегата и порождает события в зарегистрированных функциях обработчика. Аргумент, который передается в функцию обработчика, имеет тип
    StatusEventArgs
    . Класс
    StatusEventArgs
    должен быть сериализуемым, поэтому его можно маршализовать клиенту.

    Класс

    Client
    представляет клиентское приложение. Этот класс создает экземпляр класса
    EventSink
    и регистрирует метод
    StatusHandler()
    этого класса как обработчика для делегата в удаленном объекте.
    EventSink
    должен быть удаленным объектом, подобным классу
    RemoteClass
    , так как этот класс также будет вызываться через сеть.

    Удаленный объект

    Класс удаленного объекта реализуется в файле

    RemoteObject.cs
    . Класс удаленного объекта должен выводиться из
    MarshalByRefObject
    так, как было показано в предыдущих примерах. Чтобы сделать возможным для клиента регистрацию обработчика событий, который вызывается из удаленного объекта, необходимо объявить внешнюю функцию с помощью ключевого слова
    delegate
    . Мы объявляем делегата
    StatusEvent()
    с двумя аргументами:
    sender
    (поэтому клиент знает об объекте, который порождает событие) и переменную типа
    StatusEventArgs
    . В класс аргумента помещаем всю дополнительную информацию, которую необходимо послать клиенту.

    Метод, который реализуется на стороне клиента, требует строгих ограничений. Он может иметь только входные параметры, возвращаемые типы, при этом параметры

    ref
    и
    out
    недопустимы; а типы аргументов должны быть либо
    [Serializable]
    , либо удаленными (выводимыми из
    MarshalByRefObject
    ):

    public delegate void StatusEvent(object sender, StatusEventArgs e);

    public class RemoteObject : MarshalByRefObject {

    Внутри класса

    RemoteObject
    объявляется экземпляр функции делегата
    Status
    , модифицированный ключевым словом
    event
    . Клиент должен добавить обработчик событий в событие
    Status
    , чтобы получить статусную информацию из удаленного объекта:

    public class RemoteObject : MarshalByRefObject {

     public RemoteObject() {

      Console.WriteLine("RemoteObject constructor called");

     }

     public event StatusEvent Status;

    В методе

    LongWorking()
    проверяется, что обработчик событий регистрируется прежде, чем событие порождается с помощью
    Status(this, е)
    . Чтобы удостовериться, что событие порождается асинхронно, мы получаем событие в начале метода перед выполнением
    Thread.Sleep()
    и после
    Sleep
    :

     public void LongWorking(int ms) {

      Console.WriteLine("RemoteObject: LongWorking() Started");

      StatusEventArgs e = new StatusEventArgs("Message for Client: LongWorking() Started");

      // породить событие

      if (Status != null) {

       Console.WriteLine("RemoteObject: Firing Starting Event");

       Status(this, e);

      }

      System.Threading.Thread.Sleep(ms);

      e.Message = "Message for Client: LongWorking() Ending"; // породить событие окончания

      if (Status != null) {

       Console.WriteLine("RemoteObject: Firing Ending Event");

       Status(this, e);

      }

      Console.WriteLine("RemoteObject: LongWorking() Ending");

     }

    }

    Аргументы событий

    Мы видели в классе

    RemoteObject
    , что класс
    StatusEventArgs
    используется как аргумент для делегата. С помощью атрибута
    [Serializable]
    экземпляр этого класса может передаваться от сервера клиенту. Мы используем простое свойство типа string для пересылки клиенту сообщения:

    [Serializable]

    public class StatusEventArgs {

     public StatusEventArgs(string m) {

      message = m;

     }

     public string Message {

      get {

       return message;

      }

      set {

       message = value;

      }

     }

     private string message;

    }

    Сервер

    Сервер реализуется внутри консольного приложения. Мы ожидаем только, чтобы пользователь завершил работу сервера после чтения конфигурационного файла и настройки канала и удаленного объекта:

    using System;

    using System.Runtime.Remoting;

    namespace Wrox.ProfessionalCSharp {

     class Server {

      static void Main(string[] args) {

       RemotingConfiguration.Configure("Server.exe.config");

       Console.WriteLine("Hit to exit");

       Console.ReadLine();

      }

     }

    }

    Конфигурационный файл сервера

    Способ создания конфигурационного файла сервера

    Server.exe.config
    мы уже обсуждали. Существует только один важный момент. Так как клиент сначала регистрирует обработчик событий и после этого вызывает удаленный метод, то удаленный объект должен сохранять состояние клиента. Можно использовать с событиями объекты
    SingleCall
    , поэтому класс
    RemoteObject
    конфигурируется в виде активированного клиентом типа:

    <configuration>

     <system.runtime.remoting>

      <application name="CallbackSample">

       <service>

        <activated type="Wrox.ProfessionalCSharp.RemoteObject, RemoteObject" />

       </service>

       <channels>

        <channel type="System.Runtime.Remoting.Channels.Http.HttpChannel, System.Runtime.Remoting" port="6791" />

       </channels>

      </application>

     </system.runtime.remoting>

    </configuration>

    Приемник событий

    Приемник событий реализует обработчик

    StatusHandler()
    , который определен в делегате. Как ранее отмечалось, метод может иметь только входные параметры и возвращать только
    void
    . Это в точности соответствует требованиям методов
    [OneWay]
    , как мы видели ранее при рассмотрении асинхронной удаленной работы.
    StatusHandler()
    будет вызываться асинхронно. Класс
    EventSink
    должен также наследовать из класса
    MarshalByRefObject
    , чтобы сделать его удаленным, так как он будет вызывать с сервера удаленным образом:

    using System;

    using System.Runtime.Remoting.Messaging;

    namespace Wrox.ProfessionalCSharp; {

     public class EventSink MarshalByRefObject {

      public EventSink() { }

      [OneWay]

      public void StatusHandler(object sender, StatusEventArgs e) {

       Сonsole.WriteLine("EventSink: Event occurred: " + e.Message);

      }

     }

    }

    Клиент

    Клиент читает конфигурационный файл клиента с помощью класса

    RemotingConfiguration
    . Так было со всеми клиентами, которые создавались до сих пор. Клиент создает локально экземпляр удаленного класса приемника
    EventSink
    . Метод, который должен вызываться из удаленного объекта на сервере, передается в удаленный объект:

    using System;

    using System.Runtime.Remoting;

    namespace Wrox.ProfessionalCSharp {

     class Client {

      static void Main(string[] args) {

       RemotingConfiguration.Configure("Client.exe.config");

    Различие начинается здесь. Мы должны создать локально экземпляр удаленного класса приемника

    EventSink
    . Так как этот класс не будет конфигурироваться элементом
    <client>
    , то его экземпляр создается локально. Затем мы получаем экземпляр класса удаленного объекта
    RemoteObject
    . Этот класс конфигурируется в элементе
    <client>
    , поэтому его экземпляр создается на удаленном сервере:

       EventSink sink = new EventSink();

       RemoteObject obj = new RemoteObject();

    Теперь можно зарегистрировать метод обработчика объекта

    EventSink
    на удаленном объекте.
    StatusEvent
    является именем делегата, который был определен на сервере. Метод
    StatusHandler()
    имеет те же самые аргументы, которые определены в
    StatusEvent
    .

    Вызывая метод

    LongWorking()
    , сервер будет делать обратный вызов в методе
    StatusHandler()
    в начале и в конце метода:

       // зарегистрировать клиентский приемник на сервере — подписаться

       // на событие

       obj.Status += new StatusEvent(sink.StatusHandler);

       obj.LongWorking(5000);

    Теперь мы более не заинтересованы в получении событий с сервера и отменяем подписку на событие. Следующий раз при вызове

    LongWorking()
    никакие события не будут получены.

       // отменить подписку на событие

       obj.Status -= new StatusEvent(sink.StatusHandler);

       obj.LongWorking(5000);

       Console.WriteLine("Hit to exit");

       Console.ReadLine();

      }

     }

    }

    Конфигурационный файл клиента

    Конфигурационный файл для клиента —

    client.exe.config
    является почти таким же конфигурационным файлом, как и для активированных клиентом объектов. Различие можно найти в определении номера порта для канала. Поскольку сервер должен соединяться с клиентом через известный порт, то необходимо определить номер порта для канала как атрибут элемента
    <channel>
    . Не требуется определять раздел
    <service>
    для класса
    EventSink
    , так как экземпляр этого класса будет создаваться клиентом локально с помощью оператора new. Сервер не получает доступ к этому объекту по его имени, вместо этого он получит маршализированную ссылку на экземпляр:

    <configuration>

     <system.runtime.remoting>

      <application name="Client">

       <client url="http://localhost:6791/CallbackSample">

        <activated type="Wrox.ProfessionalCSharp.RemoteObject, RemoteObject" />

       </client>

       <channels>

        <channel type="System.Runtime.Remoting.Channels.Http.HttpChannel, System.Runtime.Remoting" port="777" />

       <channels>

      </application>

     </system.runtime.remoting>

    </configuration>

    Выполнение программы

    Мы видим результирующий вывод на сервере: конструктор удаленного объекта вызывается один раз, так как имеется активированный клиентом объект. Затем происходит вызов метода

    LongWorking()
    и порождение события на клиенте. Следующий запуск метода
    LongWorking()
    не порождает событий, так как клиент уже отменил регистрацию своего интереса к событию:

    В выводе клиента видно, что события достигают его по сети:

    Контексты вызова

    Активированные клиентом объекты могут сохранять состояние для определенного клиента. Для активированных клиентом объектов на сервере требуются ресурсы. Для активированных сервером объектов

    SingleCall
    новый экземпляр создается для каждого вызова экземпляра и никакие ресурсы не удерживаются на сервере, эти объекты не могут хранить состояние для клиента. Для управления состоянием можно держать состояние на клиентском стороне, данные о состоянии этого объекта посылаются с каждым вызовом метода на сервер. Для передачи состояния не требуется изменять сигнатуры всех методов с целью включения дополнительного параметра, так как можно использовать контексты вызова.

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

    Можно присвоить данные контексту вызова с помощью метода

    CallContext.SetData()
    . Класс с объекта, который используется в качестве данных для метода
    SetData()
    , должен реализовать интерфейс
    ILogicalThreadAffinative
    . Эти данные можно получить снова в том же логическом потоке выполнения (но, возможно, в другом физического потоке выполнения) с помощью
    CallContext.GetData()
    .

    Для данных контекста вызова здесь создается новая библиотека классов C# с вновь созданным классом

    CallContextData
    . Этот класс будет использоваться для передачи некоторых данных от клиента серверу с каждым вызовом метода. Класс, который передается с контекстом вызова, должен реализовать интерфейс
    System.Runtime.Remoting.Messaging.ILogicalThreadAffinative
    . Этот интерфейс не имеет метода, это просто отметка для среды выполнения, определяющая, что экземпляры этого класса перемещаются вместе с логическим потока выполнения. Класс
    CallContextData
    также помечается атрибутом
    Serializable
    , чтобы он мог передаваться по каналу:

    using System;

    using System.Runtime.Remoting.Messaging

    namespace Wrox.ProfessionalCSharp {

     [Serializable]

     public class CallContextData : ILogicalThreadAffinative {

      public CallContextData() { }

      public string Data {

       get {

        return data;

       }

       set {

        data = value;

       }

      }

      protected string data;

     }

    }

    В классе

    Hello
    метод
    Greeting()
    изменяется так, чтобы можно было получить доступ к контексту вызова. Для использования класса
    CallContextData
    необходимо сослаться на созданную ранее сборку
    CallContextData.dll
    . Чтобы работать с классом
    CallContext
    , должно быть открыто пространство имен
    System.Runtime.Remoting.Messaging
    :

    public string Greeting(string name) {

     Console.WriteLine("Greeting started");

     CallContextData cookie = (CallContextData)CallContext.GetData("mycookie");

     if (cookie ! = null) {

      Console.WriteLine("Cookie: " + cookie.Data);

     }

     Console.WriteLine("Greeting finished");

     return "Hello, " + name;

    }

    В клиентском коде передается информация контекста вызова:

    CallContextData cookie = new CallContextData();

    cookie.Data = "information for the server";

    CallContext.SetData("mycookie", cookie);

    for (int i=0; i< 5; i++) {

     Console.WriteLine(obj.Greeting("Christian"));

    }

    Такой контекст вызова может использоваться для отправки информации о пользователе, имени клиентской системы или просто как уникальный идентификатор, который применяется на серверной стороне для получения из базы данных некоторой информации о состоянии.

    Заключение

    В этой главе мы видели, что .NET Remoting использовать очень легко. Удаленный объект должен просто наследовать из объекта

    MarshalByRefObject
    . В серверном приложении требуется только один метод для загрузки конфигурационного файла, чтобы настроить и запустить каналы и удаленные объекты. На клиенте загружается конфигурационный файл и используется оператор new для создания экземпляра удаленного объекта.

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

    Наравне с этими приемами применяют много механизмов из других частей .NET Framework, которые также работают с .NET Remoting, такие как вызов асинхронных методов, выполнение обратных вызовов с помощью ключевых слов

    delegate
    и
    event
    и т. д.

    Таким образом, использование .NET Remoting является очень простым, архитектура достаточно гибкой и по желанию расширяемой. Можно использовать каналы HTTP и TCP, которые также расширяются, или написать новые каналы с самого начала. Существуют форматтер SOAP и двоичный форматтер, но легко можно использовать свой собственный. Также имеется много точек перехвата, где возможно добавление в классы специальной функциональности, которая доставляется с помощью .NET Framework.







     

    Главная | В избранное | Наш E-MAIL | Добавить материал | Нашёл ошибку | Наверх