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

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

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

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

using System;

using System.Reflection;

public class MyApp {

     public static void Main() {

          Assembly assem = Assembly.Load("MyServer, " +

              "Version=0.0.0.0, " +

              "Culture=neutral, " +

              "PublicKeyToken=047772996d01a6d4");

         Type accountType = assem.GetType("MyServer.Account");

         if (accountType!= null) {

              MethodInfо addMethod = accountType.GetMethod("Add");

              MethodInfo totalMethod =

                  accountType.GetMethod("Total");

              Object obj = Activator.CreateInstance(accountType);

              Object [] args = new Object[1];

              args [0] = 3;

              if (addMethod!= null){

                   addMethod.Invoke(obj, args);

                   args [0] = 5;

                   addMethod.Invoke(obj, args);

               }

               if (totalMethod!= null)

                   Console.WriteLine("Total = {0}",

                          totalMethod.Invoke(obj, null));

               }

      }

}

В данном примере статический метод Load класса Assembly загружает из GAC сборку с заданным именем, версией (по умолчанию 0.0.0.0), культурой (по умолчанию neutral) и токеном публичного ключа. Далее, используя механизм рефлексии, получаем ссылку на объект типа Tуре, содержащий всю информацию о классе Account из данной сборки. Если информация об этом типе имеется в загруженной сборке, получаем информацию о методах Add и Total. Потом создаем экземпляр класса Account и формируем массив аргументов. В данном случае массив состоит из одного элемента со значением 3.

Если метод Add реализован в классе Account, вызываем этот метод на построенном объекте, передавая ему сформированный массив аргументов. Модифицируем этот массив, занося в него 5, и еще раз вызываем метод Add.

И, наконец, если метод Total также реализован, вызываем его без аргументов и выводим полученное значение на консоль.

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

Удаленный сервер

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

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

Сервер

//MyServer.cs

using System;

using System.Runtime.Remoting;

using System.Runtime.Remoting.Channels;

using System.Runtime.Remoting.Channels.Http;

namespace MyServer {

public interface IAccumulator {

       void Add(int sum);

}

public interface IAudit {

       int Total();

}

public class Account: MarshalByRefObject, IAccumulator, IAudit {

        protected int _sum = 0;

        public void Add(int sum) {

               _sum += sum;

        }

        public int Total () {

                Console.WriteLine("Server AppDomain = {0}",

                       AppDomain.CurrentDomain.FriendlyName);

                return _sum;

         }

}

public class AccountApp {

         public static void Main() {

                 HttpChannel myChannel = new HttpChannel (8080);

                 ChannelServices.RegisterChannel(myChannel);

                 RemotingConfiguration.RegisterWellKnownServiceType (

                        typeof(Account),

                        "Account",

                        WellKnownObjectMode.Singleton);

                Console.WriteLine("Server is listening");

                Console.ReadLine();

                Console.WriteLine("Bye");

             }

}

}

Клиент

//МуАрр. сs

using System; using MyServer;

using System.Runtime.Remoting;

using System.Runtime.Remoting.Channels;

using System.Runtime.Remoting.Channels.Http;

public class MyApp {

          public static void Main() {

                HttpChannel с = new HttpChannel();

                ChannelServices.RegisterChannel(c);

                Account a = (Account)Activator.GetObject (

                      typeof(Account), "http://localhost:8080/Account",

                      WellKnownObjectMode.Singleton);

                a. Add(5);

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

                Console.WriteLine("Client AppDomain = {0}",

                     AppDomain.CurrentDomain.FriendlyName);

        }

}

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

System.Runtime.Remoting,

System.Runtime.Remoting.Channels,

System.Runtime.Remoting.Channels.Http.

Последнее пространство имен необходимо для работы с каналом, использующим http. Есть возможность работы с tcp каналом (что в несколько раз быстрее), но http канал по умолчанию передает сообщения по протоколу SOAP в стандартизованном XML формате, a tcp по умолчанию передает SOAP сообщения в нестандартизованном бинарном формате. Иными словами, при использовании html канала потенциальный клиент может вообще не принадлежать миру .NET и платформе Windows.

Необходимо обратить внимание на то, что класс Account теперь наследует классу MarshalByRefObject. Все объекты в .NET делятся на три типа:

1. передаваемые по ссылке

2. передаваемые по значению

3. не передаваемые за пределы своего домена приложения

Наш класс должен быть передаваем по ссылке. Это обеспечивается тем, что он наследует классу MarshalByRefObject. При работе с таким объектом клиент реально работает не с самим объектом, а с его прокси. Сам же объект формируется удаленно.

Наш удаленный объект должен жить в некотором приложении. Здесь мы описываем консольное приложение, функция Main (в коде сервера) — его точка входа.

В данном приложении формируется объект — http-канал (с указанием произвольного номера порта) и этот канал регистрируется в системе Remoting.