Сообщений 28    Оценка 595 [+0/-1]         Оценить  
Система Orphus

Работа с Indigo

Первое знакомство с новой коммуникационной подсистемой Windows

Автор: Андрей Корявченко
The RSDN Group

Источник: RSDN Magazine #1-2005
Опубликовано: 22.05.2005
Исправлено: 10.12.2016
Версия текста: 1.0
Что такое Indigo
Сервисы Indigo
Первые шаги
Создание клиента
Сервис с одной реализацией и несколькими контрактами
Архитектура Indigo
Сервис-ориентированная архитектура
Модель сервисов Indigo
Системные сервисы и сервисы сообщений

Примеры к статье: Пример1, Пример 2.

Что такое Indigo

Indigo – это новая коммуникационная подсистема Windows, предназначенная для создания распределенных приложений. Основная задача Indigo – обеспечить взаимодействие частей распределенного приложения. Помимо этого она обеспечивает безопасность, транзакционность и надежность коммуникаций.

Сервисы Indigo

Серверная часть приложения, использующего Indigo, называется сервисом. Сервис, прежде всего, содержит функциональность, которую необходимо реализовать на сервере (реализацию). Кроме того, он может содержать метаданные, необходимые для его публикации, и контракта – абстрактного описания методов, параметров и возвращаемых значений точки доступа (endpoint). Взаимодействие с клиентом осуществляется посредством сообщений.

Простейший способ создания как реализации, так и контракта – это разметка обычного класса специальными атрибутами, ServiceContractAttribute и OperationContractAttribute. Первым нужно пометить класс, который является сервисом, вторым – методы, которые будут доступны публично.

ПРИМЕЧАНИЕ

На первый взгляд это очень похоже на Web-сервисы. Однако есть различия. Прежде всего, в Indigo нет необходимости наследовать класс от специального класса. Необходимость такового признана архитектурной ошибкой, и вместо наследования предложен атрибут. Это повышает гибкость решений при построении распределенных приложений.

Еще одно отличие – поддержка сессий теперь задается не для методов, а для сервиса целиком. Это больше соответствует принципам построения сервисов.

Ниже приведена таблица, описывающая возможные способы хостинга сервисов, и те их особенности, на которые указывает Microsoft.

Способ хостинга Сценарий применения Ключевые особенности Ограничения
IIS 5.1, IIS 6.0 Работа сервисов в составе ASP.NET приложений.Работа сервисов в интернете.Работа сервиса на машине разработчика с установленным IIS. Среда сервиса интегрирована со средой ASP.NET, следовательно, доступны все преимущества этой среды – пересоздание процессов при утечках памяти и критических сбоях, остановка сервиса при отсутствии обращений, мониторинг состояния процесса и активация по запросу извне. Поддерживается только протокол HTTP.
IIS 7.0 Работа сервисов в составе ASP.NET-приложений.Работа сервисов в интернете.Работа сервиса на машине разработчика с установленным IIS. Построен поверх WAS, так что обладает всеми его возможностями. Кроме того, доступно взаимодействие с ASP.NET приложениями. В настоящий момент поддерживается только в Longhorn.
Сервис Windows Работа сервисов в качестве сервиса Windows, таким образом, что они могут быть сконфигурированы для запуска при старте ОС. Сервис может быть запущен автоматически. Возможна работа со всеми поддерживаемыми Indigo протоколами в ОС Windows XP, Windows Server 2003, Windows “Longhorn”. Возможности мониторинга и управления процессами ограничены возможностями используемой ОС.
Windows Activation Services (WAS) Работа сервиса в среде, где нет желания устанавливать IIS. Обеспечивает все возможности, предоставляемые IIS без необходимости устанавливать Web-сервер.Поддерживает протоколы TCP, HTTP, IPC и MSMQ.Предоставляет возможность автоматической активации сервисов, специальным сервисом активации, наподобие того, как работает активация для приложений COM. В настоящий момент поддерживается только в Longhorn.
Отдельное приложение Работа сервиса в среде, где нет желания устанавливать IIS. Запускается вручную. Возможна работа со всеми поддерживаемыми Indigo протоколами в ОС Windows XP, Windows Server 2003, Windows “Longhorn”.
ПРИМЕЧАНИЕ

Однако все эти способы в итоге сводятся к использованию ServiceHost<T>, о котором мы поговорим ниже.

Первые шаги

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

ПРИМЕЧАНИЕ

Все описания и исходные коды соответствуют Microsoft WinFX SDK – "Avalon" and "Indigo" Community Technology Preview Edition (Март 2005). Версия сборок Indigo – 2.0.5110.20.

Напишем класс, реализующий требуемую функциональность, и разметим его:

      using System;
using System.ServiceModel;

namespace Service
{
  /// <summary>/// Сервис, возвращающий время сервера./// </summary>
  [ServiceContract]
  publicclass TimeService
  {
    /// <summary>/// Возвращает время сервера./// </summary>
    [OperationContract]
    public DateTime GetServerTime()
    {
      Console.WriteLine("GetServerTime() called");
      return DateTime.Now;
    }
  }
}

После того как мы создали реализацию, необходимо опубликовать сервис, чтобы он был доступен извне. Существует несколько способов сделать это, но в данном случае мы воспользуемся самым наглядным – хостингом сервиса в собственном приложении.

Для формирования среды хостинга служит generic-класс ServiceHost<T>. Ниже приведен пример программы, осуществляющей хостинг сервиса.

      using System;
using System.ServiceModel;

namespace Service
{
  class Program
  {
    staticvoid Main(string[] args)
    {
      // Создаем базовый адрес.
      Uri baseHttpAddress = new Uri("http://localhost:8080");

      // Формируем среду исполнения сервисаusing (ServiceHost<TimeService> host = new ServiceHost<TimeService>(baseHttpAddress))
      {
        // Создаем совместимый с веб-сервисом HTTP binding.
        BasicProfileBinding binding = new BasicProfileBinding();

        // Добавляем точку доступа к сервису
        host.AddEndpoint(typeof(TimeService), binding, "/TimeService");

        // Запускаем прослушивание
        host.Open();

        // Выводим подсказку и останавливаем основной поток
        Console.WriteLine("Service is running. Press ENTER to exit...");
        Console.ReadLine();
      }
    }
  }
}

В первой строке мы создаем URI сервиса, по которому он будет доступен. Далее создаем экземпляр хоста. Следующим шагом идет создание т.н. связывания (binding), способа взаимодействия сервиса с клиентом. Связывание отвечает за протокол взаимодействия, и в какой-то мере является аналогом каналов (channels) в Remoting. В примере используется самое простое связывание, совместимое с Web-сервисами и обеспечивающее взаимодействие по протоколу HTTP с использованием SOAP.

После создания связывания создаем точку доступа к сервису (endpoint). Этой операции не было в Web-сервисах, поскольку такая точка у Web-сервиса могла быть только одна. В случае Indigo мы можем задать несколько точек, при этом выставив в разные точки разные интерфейсы (контракты) сервиса, и назначить им различные права доступа, URL, протоколы и т.п.

Наконец, вызовом метода Open() переводим хост-объект в режим ожидания вызова.

Теперь, если набрать в браузере адрес http://localhost:8080, мы должны получить такую страничку:


По завершению работы для освобождения ресурсов необходимо вызвать у хост-объекта метод Close() или использовать конструкцию using для автоматического вызова метода Dispose.

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

<?xmlversion="1.0"encoding="utf-8" ?>
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">

  <appSettings>
    <add key="baseAddress" value="http://localhost:8080" />
  </appSettings>

  <system.serviceModel>
    <services>
      <service serviceType="Service.TimeService">
        <endpoint bindingSectionName="basicProfileBinding"
                  contractType="Service.TimeService"/>
      </service>
    </services>

  </system.serviceModel>
</configuration>

Теперь код формирования точек доступа можно убрать. Итоговый текст программы будет выглядеть следующим образом:

      using System;
using System.Configuration;
using System.ServiceModel;

namespace Service
{
  class Program
  {
    staticvoid Main(string[] args)
    {
      // Формируем базовый адрес из настроек приложения.
      Uri baseHttpAddress = 
        new Uri(ConfigurationManager.AppSettings["baseAddress"]);

      // Создаем сервис и запускаем прослушивание
using (ServiceHost<TimeService> host = 
        new ServiceHost<TimeService>(baseHttpAddress))
      {
        host.Open();
        Console.WriteLine("Service is running. Press ENTER to exit...");
        Console.ReadLine();
      }
    }
  }
}

Обратите внимание на то, что, в отличие от Remoting, нет необходимости явно вызывать аналог RemotingServices.Configure. Конфигурирование производится автоматически, на основании типа сервиса.

Создание клиента

Теперь нам необходимо написать клиентский код, использующий сервис. Поскольку использованное нами связывание совместимо с Web-сервисами, то можно просто использовать обычную Web-ссылку (web reference), однако мы используем родные средства Indigo.

Для начала, так же, как и в случае с Web-сервисами, необходимо создать прокси-классы, представляющие сервис на клиенте. Для создания таких классов существует утилита svcutil. Ее необходимо запустить, указав в качестве аргумента URL сервиса. Помимо URL сервиса можно указать массу дополнительных параметров, как совпадающих с таковыми у wsdl, так и новыми (например, можно сделать генерируемые классы сериализуемыми).

В результате работы утилиты получаем такой исходный код:

        //------------------------------------------------------------------------------
        // <auto-generated>
        //   This code was generated by a tool.
        //   Runtime Version:2.0.50110.28
        //
        //   Changes to this file may cause incorrect behavior and will be lost if
        //   the code is regenerated.
        // </auto-generated>
        //------------------------------------------------------------------------------

[System.ServiceModel.ServiceContractAttribute()]
publicinterface TimeService
{
  
  [System.ServiceModel.OperationContractAttribute(
    Action = "http://tempuri.org/TimeService/GetServerTime",
    ReplyAction = "http://tempuri.org/TimeService/GetServerTimeResponse")
  ]
  [return: System.ServiceModel.MessageBodyAttribute(
    Name = "GetServerTimeResult", Namespace = "http://tempuri.org/")
  ]
  System.DateTime GetServerTime();
}

publicinterface TimeServiceChannel 
  : TimeService, System.ServiceModel.IProxyChannel
{
}

public partial class TimeServiceProxy 
: System.ServiceModel.ProxyBase<TimeService>, TimeService
{
  
  public TimeServiceProxy()
  {
  }
  
  public TimeServiceProxy(string configurationName) : 
      base(configurationName)
  {
  }
  
  public TimeServiceProxy(System.ServiceModel.Binding binding) : 
      base(binding)
  {
  }
  
  public TimeServiceProxy(System.ServiceModel.EndpointAddress address, 
System.ServiceModel.Binding binding) 
    : base(address, binding)
  {
  }
  
  public System.DateTime GetServerTime()
  {
    returnbase.InnerProxy.GetServerTime();
  }
}

Сравним результат с тем, что генерирует утилита wsdl для того же сервиса:

WSDL прокси

Очевидно, прокси-класс Indigo намного компактнее. Прежде всего потому что не произошло формирования прокси-класса для аргументов. Вместо фиктивного dateTime в случае WSDL в Indigo мы видим полноценный System.DateTime. Базовый класс теперь представляет собой generic-класс, поэтому нет никаких приведений при обращении к его методам и свойствам, а сам прокси-класс обзавелся типизированным интерфейсом (это позволит использовать несколько разных прокси-классов с одинаковыми контрактами). Нет массы асинхронных методов – функциональность асинхронных вызовов вынесена наружу. Наконец, вместо конструктора без параметров и жесткой привязки к конкретному URL мы имеем несколько перегруженных конструкторов, в параметрах которых можно передать требуемое связывание и точку доступа.

Можно еще заметить интерфейс TimeServiceChannel. Он предназначен для управления запросами и сессиями на клиенте.

Осталось только воспользоваться полученным кодом. Работать с сервисами Indigo так же просто, как и с веб-сервисами. Единственное отличие, видное сразу – экземпляры прокси-классов необходимо явно уничтожать.

Набросаем простую форму, содержащую кнопку вызова серверного метода и TextBox для вывода результатов.


Теперь напишем обработчик нажатия кнопки.

        private
        void _getServerTimeButton_Click(object sender, EventArgs e)
{
  using (TimeServiceProxy proxy = new TimeServiceProxy(
      new EndpointAddress("http://localhost:8080/"),
      new BasicProfileBinding()))
    _serverTimeBox.Text = proxy.GetServerTime().ToString();
}

Код предельно прост – создаем экземляр прокси-класса, передав на вход адрес сервиса и связывание, после этого вызываем метод.

Запускаем и убеждаемся, что все работает. Простейшее Indigo-приложение создано.

Конфигурацию клиентского прокси-объекта также можно сделать декларативно. В описываемом примере достаточно добавить конфигурационный файл со следующим содержимым:

<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
  <system.serviceModel>
    <client>
      <endpoint
            address="http://localhost:8080" 
            bindingSectionName="basicProfileBinding" 
            contractType="TimeService" />
    </client>
  </system.serviceModel>
</configuration>

В этом случае передавать параметры в конструктор прокси-класса не нужно.

Сервис с одной реализацией и несколькими контрактами

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

Для начала отделим контракт от реализации. Опишем его в виде отдельного интерфейса.

      using System;
using System.ServiceModel;

namespace Service
{
  [ServiceContract]
  interface ITimeService
  {
    [OperationContract]
    DateTime GetServerTime();
  }
}

Теперь опишем интерфейс управления.

      using System;
using System.ServiceModel;

namespace Service
{
  [ServiceContract]
  publicinterface ITimeServiceController
  {
    [OperationContract]
    void StopServer();
  }
}

Наконец, реализация.

      using System;
using System.ServiceModel;

namespace Service
{
  /// <summary>/// Сервис, возвращающий время сервера./// </summary>internalclass TimeService : ITimeService, ITimeServiceController
  {
    /// <summary>/// Возвращает время сервера./// </summary>public DateTime GetServerTime()
    {
      Console.WriteLine("GetServerTime() called");
      return DateTime.Now;
    }

    /// <summary>/// Останавливает сервер./// </summary>publicvoid StopServer()
    {
      Console.WriteLine("StopServer() called");
      Program.ServerRunEvent.Set();
    }
  }
}

Обратите внимание на то, что разметка перекочевала в интерфейсы, а реализация получила модификатор доступа internal. Такая механика позволяет спрятать реализацию внутри сборки – возможность, раньше доступная только Remoting и недоступная Web-сервисам.

Управление работой сервера осуществляется объектом синхронизации "событие".

Ниже приведен декларативный вариант описания точек доступа.

<system.serviceModel>
<services>
    <service serviceType="Service.TimeService">
      <endpoint address="" 
        bindingSectionName="basicProfileBinding"
        contractType="Service.ITimeService"/>
      <endpoint address="controller" 
        bindingSectionName="basicProfileBinding"
        contractType="Service.ITimeServiceController"/>
    </service>
  </services>
<system.serviceModel>

Точек теперь две и у них разные типы контрактов.

В запускающий код сервера добавлено управление событием.

      using System;
using System.Configuration;
using System.ServiceModel;
using System.Threading;

namespace Service
{
  class Program
  {
    internalstatic ManualResetEvent ServerRunEvent;

    staticvoid Main(string[] args)
    {
      Uri baseHttpAddress = new Uri(
        ConfigurationManager.AppSettings["baseAddress"]);

      using (ServiceHost<TimeService> host = 
        new ServiceHost<TimeService>(baseHttpAddress))
      {
        ServerRunEvent = new ManualResetEvent(false);

        host.Open();
        Console.WriteLine("Service is running ...");

        ServerRunEvent.WaitOne();
        // Подождем отработки последнего вызова
        Thread.Sleep(300);
      }
    }
  }
}

Thread.Sleep в конце нужен для того, чтобы сервис успел отработать все текущие запросы.

На клиенте создаем новые прокси и вносим соответствующие правки в конфигурацию.

<system.serviceModel>
<client>
    <endpoint
      address="http://localhost:8080" 
      bindingSectionName="basicProfileBinding" 
      contractType="ITimeService" />
    <endpoint
      address="http://localhost:8080/controller" 
      bindingSectionName="basicProfileBinding" 
      contractType="ITimeServiceController" />
  </client>
</system.serviceModel>

Запускаем и убеждаемся, что все работает по-прежнему. Теперь в обработчик события нажатия кнопки Exit добавляем завершение работы сервера.

      private
      void _exitButton_Click(object sender, EventArgs e)
{
  using (TimeServiceControllerProxy proxy = new TimeServiceControllerProxy())
    proxy.StopServer();
  Close();
}

Запускаем и убеждаемся, что при нажатии кнопки Exit сервер завершает работу.

Архитектура Indigo

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

Прежде всего, следует отметить, что Indigo не создавалась на пустом месте. В ее основе лежат более старые технологии – COM+, MSMQ, Remoting и Web-сервисы ASP.NET. Была предпринята попытка взять от этих технологий самое лучшее, и на полученной основе создать единый фреймворк для создания распределенных приложений.

Кроме того, Indigo была спроектирована в расчете на взаимодействие не только с новыми приложениями, но и со старыми. Для этого в нее добавлена поддержка большого количества транспортных протоколов (HTTP, TCP, UDP, IPC); стандартные механизмы аутентификации и шифрования; различные топологии взаимодействия (клиент-сервер, P2P, издатель-подписчик): поддержка транзакций.

В качестве основы построения распределенных приложений Indigo предлагает сервис-ориентированную архитектуру (SOA). Остановимся на ней поподробнее.

Сервис-ориентированная архитектура

Ориентация на сервисы – это подход к построению распределенных приложений, служащий дополнением к ООП и компонентному программированию. Если в ООП строительным блоком приложения служит класс, в SOA таковым является автономный сервис. Сервис – это программа, которая способна обмениваться с внешним миром сообщениями. Набор развернутых сервисов образует распределенную систему. Сервисы и клиенты должны быть спроектированы таким образом, чтобы добавление новых сервисов не сказалось на функционировании остальной части системы.

Microsoft выделяет 4 основных принципа построения сервис-ориентированных систем:

1. Границы отражены явно. Современные распределенные системы географически могут быть расположены чрезвычайно широко. Скорость и надежность каналов коммуникаций между частями может сильно различаться. Поэтому сервис-ориентированное приложение должно явно выделять процесс преодоления границ между частями системы, вместо того чтобы делать это неявно при вызове метода. Это позволяет избежать проблем, связанных с нестабильностью и низкой скоростью глобальных каналов коммуникаций.

Идеология явного выделения границ коммуникации распространяется не только на различные сервисы различных поставщиков, но и на разработку нескольких сервисов внутри одной компании, поскольку в ходе дальнейшего развития системы может понадобиться использовать эти сервисы удаленно. Упрощение и универсализация сервисов в сервис-ориентированной архитектуре – это жизненно важный момент для создания надежных и легко изменяемых SOA-приложений.

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

3. Сервисы описываются схемой и контрактом, а не классами. Необходимость сохранения работоспособности системы при смене версии сервиса выдвигает дополнительные требования к гибкости описания контракта сервиса. Описание, сходное с ОО-интерфейсами, для сервисов не годится, поскольку не обеспечивает должного уровня совместимости. Поэтому в SOA следует использовать гибкий формат сообщений, снабженный машинно-проверяемыми схемами, а также возможностью передавать дополнительную информацию, не нарушая структуры основной информации, чтобы обеспечить работоспособность уже использующегося кода.

4. Совместимость сервисов определена и основана на политике совместимости. В классическом ООП на совместимость частей кода влияет как структура публичных интерфейсов кода, так и семантика действий, выполняемых этим кодом. В SOA эти два аспекта совместимости объединены в один. Структурная совместимость обеспечивается машинным контролем формата сообщений на соответствие схеме, семантическая – политикой совместимости.

Политика совместимости представляет собой набор машинно-читаемых выражений. Выражения описывают условия и гарантии (называемые утверждениями, assertion), которые должны быть обеспечены для нормального функционирования сервиса. Утверждения политики идентифицируются стабильным глобально уникальным идентификатором, вне зависимости от того, к какому сервису они применяются. Утверждения должно быть достаточно формальными, чтобы обеспечить простую механику их проверки.

Модель сервисов Indigo

Модель сервисов предназначена для легкого использования Indigo в CLR-коде. Она представляет собой набор атрибутов, предназначенных для разметки контрактов сервиса. Поддерживаются два типа контракта: контракт сервиса и контракт данных.

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

Контракт данных описывает структуру сообщения. Этот вид контракта аналогичен XML-схеме, и описывает способ преобразования CLR-типов в сообщения. Для создания контракта данных достаточно пометить тип атрибутом DataContractAttribute, а его поля или свойства, являющиеся частью контракта, атрибутом DataMemberAttribute.

Основной задачей модели сервисов является обеспечение связи между контрактом и пользовательским кодом. Эта задача решается атрибутом OperationContractAttribute. Методы, помеченные этим атрибутом, являются обработчиками определенного типа сообщений. В приведенных выше примерах мы использовали этот атрибут для пометки методов, которые потом вызывали удаленно. Обработчики могут быть как однонаправленными, так и работающими в режиме запрос-ответ, что определяется значением свойства IsOneWay атрибута. В процессе работы сообщение преобразовывается в типизированный набор параметров, а возвращаемое значение преобразовывается в ответное сообщение.

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

Наконец, модель сервисов Indigo обеспечивает декларативный механизм связывания сервисов и контроля безопасности взаимодействия. В примерах использовалось связывание, обеспечивающее взаимодействие в стиле Web-сервисов.

Системные сервисы и сервисы сообщений

Indigo содержит несколько сервисов, предназначенных для упрощения реализации отдельных аспектов распределенных систем. Например, для обеспечения сложных схем авторизации с установлением доверительных отношений между различными сервисами и обменом идентификационной информацией (identity federation). В будущих версиях Windows будет использоваться подобная схема, основанная на WS-Federation (протокол обмена идентификационной информацией между Web-сервисами).

Indigo также обеспечивает значительную поддержку программирования с использованием транзакций. Для поддержки транзакций может использоваться соответствующий протокол Web-сервисов (WS-AtomicTransactions), либо новый фреймворк, интегрированный с Windows – System.Transaction. Этот фреймворк обеспечивает взаимодействие с существующими системами транзакций (DTC, WS-AtomicTransactions), предоставляет облегченный менеджер транзакций, хранящий состояние транзакции в памяти и не поддерживающий постоянные хранилища, позволяет легко создавать собственные менеджеры ресурсов в управляемом коде. В ОС Windows Longhorn, кроме того, добавится поддержка нового менеджера транзакций, интегрированного в ядро (KTM, KernelTransaction Manager) и транзакционного режима работы NTFS (TxNTFS).

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


Эта статья опубликована в журнале RSDN Magazine #1-2005. Информацию о журнале можно найти здесь
    Сообщений 28    Оценка 595 [+0/-1]         Оценить