2 сентября 2012 г.

Использование привязки Duplex Binding в WCF


Добрый день. Сегодня мы рассмотрим один из нестандартных типов связи, который предоставляет разработчикам Windows Communication Foundation - двухстороння связь или Duplex mode.
Классической схемой взаимодействия клиента с сервисом является режим запрос - ответ. Клиент посылает сервису запрос и немедленно получает от сервиса ответ с данными. Однако такая схема не всегда способна удовлетворить потребности разработчика, особенно при разработке сервисных систем со сложной архитектурой.

Рассмотри следующий сценарий:
Допустим, у нас есть центральный сервис, который хранит список товаров магазина. У сервиса есть множество клиентов, которые могут добавлять товары в базу магазина и запрашивать информацию о товарах в магазине. При этом каждый клиент должен немедленно узнавать о новых товарах, которые поступили в магазин от других клиентов.
В случае реализации данного сценария через схему “запрос - ответ”, каждый клиент должен будет периодически (скажем раз в секунду) устанавливать соединение с главным сервисом и спрашивать его о наличии новых товаров. Такое решение, несомненно, будет работать, но при большом количестве клиентов центральный сервис будет перегружен запросами, что негативно скажется на быстродействии всей системы.
Для решения задач такого типа WCF предлагает разработчикам двухстороннюю (duplex) модель взаимодействия клиента с сервисам. Принцип работы заключается в следующем: клиент соединяется с сервисом и подписывается на некоторое событие (в нашем сценарии - добавление нового товара в магазин) и закрывает соединение с сервисом. Теперь, когда в магазин будет добавлен новый товар - центральный сервис сам установит соединение с клиентом, передав ему информацию о новом товаре.
Давайте попробуем реализовать такой сценарий. Начнем с проектирования центрального сервиса. В отличии от классического сценария разработки WCF сервиса, здесь нам потребуется создать для сервиса не один контракт операций, а два. Первый контракт будет декларировать список операций, предоставляемых сервисом:
   1:      [ServiceContract(CallbackContract=typeof(IShopServiceCallback))]
   2:      public interface IShopService
   3:      {
   4:          [OperationContract]
   5:          void AddNewDevice(Device device);
   6:      }
Второй контракт называется callback контракт. Этот контракт определяется на сервисе, но должен быть реализован стороной клиента. Именно методы этого контракта будет вызывать центральный сервис, когда установит соединение с клиентом:
   1:      public interface IShopServiceCallback
   2:      {
   3:          [OperationContract(IsOneWay=true)]
   4:          void NewDeviceArrivedNotify(Device device);
   5:      }
Контракт операций сервиса связан с callback контрактом при помощи атрибута ServiceContract. Благодаря этой опции, при создании ссылки на сервис, клиент получит callback-интерфейс, который он должен реализовать для взаимодействия с сервисом.
Теперь реализуем функционал сервиса соответствующий контракту, добавим в проект новый файл ShopService.svc:
   1:      [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
   2:      public class ShopService : IShopService
   3:      {
   4:          private IShopServiceCallback Callback
   5:          {
   6:              get
   7:              {
   8:                  return OperationContext.Current.GetCallbackChannel<IShopServiceCallback>();
   9:              }
  10:          }
  11:   
  12:          public void AddNewDevice(Device device)
  13:          {
  14:              Callback.NewDeviceArrivedNotify(device);
  15:          }
  16:      }
При добавлении нового устройства сервис должен уведомит всех своих подписчиков. Из текущего контекста операции извлекается канал связи с подписчиками, при его вызове канал будет открыт и WCF самостоятельно установит связь с каждым подписчиком, отправив новые данные.
Далее займемся конфигурацией хостинга нашего сервиса. Двухстороння связь реализуется в WCF при помощи привязки DuplexBinding. Конфигурационный код приведен в листинге ниже.
   1:      <system.serviceModel>
   2:          <behaviors>
   3:              <serviceBehaviors>
   4:                  <behavior name="serviceBehaviour">
   5:                      <serviceMetadata httpGetEnabled="false" />
   6:                  </behavior>
   7:              </serviceBehaviors>
   8:          </behaviors>
   9:          <services>
  10:              <service name="Service.ShopService"  behaviorConfiguration="serviceBehaviour"  >
  11:                  <endpoint name="duplexEndpoint"  binding="wsDualHttpBinding"  contract="Service.IShopService"  />
  12:                  <endpoint name="metaEndpoint"  address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
  13:              </service>
  14:          </services>
  15:      </system.serviceModel>
Разработка клиента для сервиса проходит в несколько этапов. Для начала необходимо запустить сервис и скопировать адрес точки svc. Далее создать проект клиента (например, простую консоль) и добавить в него Service Reference на сервис. После этого на клиенте будут доступны контракт операций и callback контракт сервиса.
Сначала необходимо реализовать на клиенте callback контракт – это будет класс, принимающий ответ от сервиса:
   1:      public class NewDeviceNotifyHandler : IShopServiceCallback
   2:      {
   3:          public void NewDeviceArrivedNotify(Device device)
   4:          {
   5:              Console.WriteLine(
   6:                  String.Format("Recieved new device - {0} {1}", device.Name, device.Price));
   7:          }
   8:      }
Теперь все готово для тестирования сервиса. Напишем простой код, который будет отправлять новые девайсы на сервис.
   1:      class Program
   2:      {
   3:          static void Main(string[] args)
   4:          {
   5:              InstanceContext instanceContext = new InstanceContext(new NewDeviceNotifyHandler());            
   6:              ShopServiceClient client = new ShopServiceClient(instanceContext);
   7:   
   8:              for (int i = 0; i < 5; i++)
   9:              {
  10:                  var device = new Device() { Name = String.Concat("Device", i.ToString()), Price = i };
  11:                  Console.WriteLine("Device sended");
  12:                  client.AddNewDevice(device);
  13:                  Thread.Sleep(2000);
  14:              }
  15:          }
  16:      }
После прибытия девайса на сервис, он соединится с клиентом и в отдельном потоке запустит класс NewDeviceNotifyHandler. Результат работы отобразится на консоли.
В данной статье был рассмотрен двухсторонний тип связи и приведен способ ее реализации на основе технологии WCF.

1 комментарий: