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