Выбрать главу

После этого в этой же системе регистрируется класс, к которому будет обеспечен доступ удаленных клиентов. Задается его тип, его URI — определяемый разработчиком идентификатор, и режим работы объекта. В данном случае это Singleton. Это означает, что такой объект создается после получения первого запроса от какого-либо клиента и далее живет (сохраняя состояние), отвечая на запросы как этого, так и других объектов.

После запуска серверного приложения на консоль сервера выдается уведомление о том, что сервер готов к работе. С этого момента сервер ожидает получения сообщений по каналу http на порт 8080. Остановить работу сервера можно нажав на клавишу <Enter>.

Теперь обратимся к клиенту. Он регистрирует один из каналов, зарегистрированных сервером (но не указывает номер порта).

Метод Activator.Getobject возвращает ссылку на прокси, сам же объект при этом не создается. Если он не был создан ранее, то он будет создан при получении первого запроса от какого-либо клиента. Среди параметров задаются тип компонента, путь к нему и режим его работы. Все остальное не изменилось по сравнению с клиентом, предназначенным для работы с сервером в домене клиентского приложения.

Теперь рассмотрим результаты работы нашего приложения. В отдельном консольном окне компилируем сервер (создаем сборку типа ехе) и запускаем его. В другом консольном окне компилируем клиента, задавая ссылку на копию серверной сборки, размещенной в каталоге клиента. Запускаем клиента два раза и видим, что сумма на счете накапливается. Кроме того видим, что сервер и клиент выполняются в различных доменах приложения.

Консоль сервера

>csc /t: exe MyServer.cs

>MyServer.ехе

Server is listening

Server AppDomain = MyServer.exe

Server AppDomain = MyServer.exe

Bye

>

Консоль клиента

>csc /r: MyServer\MyServer.ехе МуАрр. cs

>MyApp.ехе

Total = 5

Client AppDomain = MyApp.exe

>MyApp.ехе

Total = 10

Client AppDomain = MyApp.exe

>

Несколько комментариев, касающихся удаленных компонентов.

В.NET определены три типа режимов работы удаленных объектов:

1. SingleCall

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

2. Singleton

Удаленный объект совместно используется несколькими клиентами, сохраняя состояние между вызовами. Для контроля за его жизненным циклом используется распределенная сборка мусора — лизинг. При этом объект получает некоторое время жизни, продлеваемое автоматически после каждого нового вызова. После исчерпания этого времени следует запрос к спонсору объекта (обычно к самому клиенту) о продлении жизни компонента. При отсутствии ответа объект уничтожается. Такой механизм позволяет экономить на коммуникации между клиентом и удаленным сервером.

3. Активируемый клиентом

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

Говоря об удаленных компонентах и сопоставляя .NET с СОМ нельзя не вспомнить об апартаментах. В одном процессе может быть несколько апартаментов, и ссылка на объект, полученная в одном апартаменте, не может непосредственно использоваться в другом апартаменте. Необходимо выполнить ее маршалинг между апартаментами. В.NET такой проблемы нет. Любая объектная ссылка (прокси на удаленный компонент) используется глобально по всему домену приложения.

И наконец необходимо упомянуть о передаче объектов по значению. Это возможно и полезно если объект небольшой. В этом случае клиент получает не прокси на объект, а его копию. Для передачи объекта по значению необходимо, чтобы соответствующий класс был определен с пользовательским атрибутом [Serializable] для использования стандартного метода сериализации, либо можно самостоятельно реализовать интерфейс ISeriaiizabie для задания собственного способа сериализации. Пользовательские атрибуты и вопросы их использования будут рассмотрены далее.

Обработка ошибок

До сих пор мы не рассматривали обработку ошибок. В СОМ каждый метод каждого интерфейса (за исключением методов AddRef и Release интерфейса IUnknown) должен возвращать значение типа HRESULT, говорящее об успехе или неудаче вызова метода и о причине неудачи.

Получатель этого значения должен его обработать. Но у него не всегда есть возможность сделать это.

В.NET используется технология обработки исключений — блоки try, catch, finally, инициализация исключения — throw.

В нашем распределенном примере при запуске клиента без запуска сервера возникает никем не перехваченная ошибка. Включим часть кода клиента, где происходит работа с сервером, в блок try и добавим блоки catch и finally для задания реакции на ошибки и на выход из блока try.

using System;

using MyServer;

using System. Runtime.Remoting;

using System.Runtime.Remoting.Channels;

using System.Runtime.Remoting.Channels.Http;

using System.Net;

public class MyApp {

     public static void Main() {

          HttpChannel с = new HttpChannel();

          ChannelServices.RegisterChannel(c);

          try {

                 Account a = (Account)Activator.GetObject(typeof(Account),

                 "http://localhost:8080/Account",

                 WellKnownObjectMode.Singleton);

           a. Add(5);

           Console.WriteLine("Total = {0}", a.Totalf));

      }

      catch(WebException e) {

            Console.WriteLine(e.Message);

      }

     catch(Exception e){

             Console.WriteLine(e.Message);

      }

      finally {

             Console.WriteLine("Bye");

      }

   }

}

Первый блок catch перехватывает специальное исключение WebException, связанное именно с работой http канала, а второй блок catch перехватывает все остальные исключения. Независимо от наличия ошибки и ее типа всегда отрабатывает блок finally.

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

Клиент запущен до запуска сервера

>МуАрр. ехе

The underlying connection was closed: Unable to connect

to the remote server

Bye

>

Клиент запущен после запуска сервера

>МуАрр. ехе

Total = 5

Bye

>

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

Синхронизация

Теперь рассмотрим случай двух клиентов, параллельно посылающих некоторые суммы на один и тот же счет, поддерживаемый нашим сервером.

Вспомним, что в СОМ использовались апартаменты типа STA для объектов, не допускающих параллельный вызов своих методов, и типа МTА для потоко-безопасных объектов. В.NET по умолчанию считается, что все объекты являются потоко-безопасными.

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