Симон Робинсон - C# для профессионалов. Том II
Приемники сообщений
Метод 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. Необходимо просто обработать это исключение и сделать, например, повторную попытку или записать в журнал, информировать пользователя и т.д.