25 июля 2017 г.

WCF. Callback через BasicHttpBinding без дуплексной связи.

Оглавление:

  1. WCF service. С чего начать. Основные моменты создание службы. 
  2. WCF. Callback через BasicHttpBinding без дуплексной связи.
  3. Realtime WCF Tracker вызовов клиентского приложения.
  4. Как отключить публикацию метаданных mex WCF сервисом. 

В данной статье я хочу поделиться с вами примером того, как можно в WCF инициировать передачу данных с сервера на клиент используемые BasicHttpBinding в качестве привязки для взаимодействия.
Как вам должно быть известно, для того, что бы сервер выступа в качестве инициатора передачи данных на клиент используя WCF, ему надо использовать одну из схем ниже:
  • Производить подключение к клиенту самостоятельно в ответ на вызов клиента (что выглядит глупо).
  • Использовать Duplex связь, которая была создана специально для этого.
    [ServiceContract(CallbackContract = typeof(ICallback))]
    internal interface IMyContract
    {
    }
Duplex связь покрывает наши запросы, но есть одно "но" - привязка (Binding) должена поддерживать работу с использованием сессии. А BasicHttpBinding (на основе протокола HTTP) в силу своей природы не может поддерживать сессию (постоянное TCP соединение), так как работает по схеме "Запрос-ответ". Как же тогда поступить?

WSDualHttpBinding привязка.


Для работы через HTTP транспорт, с возможностью использования Duplex связи, специально был создан WSDualHttpBinding, который, для имитации сессии, используя HTTP заголовок, добавляя туда техническую информацию. А для обратного соединения сам создаёт подключение к клиенту. Замечу, что обратное соединение создаётся через WCF engine, и используется как при работе с любой другой привязкой, а не программистом, при первом подключении клиента. В качестве обратного соединения используются подключения на 80 (по умолчанию) порт клиента со стороны сервера, когда надо передать данные.
Все было бы хорошо, но в какой то момент, может возникнуть ситуация, когда клиент будет находиться за NAT, что приводит к невозможности подключиться к нему сервером. Почему? Читайте как работает NAT, он разрешает соединения из внутренней сети, но запрещает подключения из внешней (за маршрутизатором).  В это случае выходом может быть лишь привязки на основе протокола TCP (NetTcpBinding), в которые, для обратного и прямого соединения, используется одно TCP соединение. Но что, если нам нужно как то через HTTP производить обратный вызов клиента.

Передача данных порциями используя BasicHttpBinding.


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

Таким образом мы не получим Timeout исключение при вызове GetMessages, а так же не будет огромного количества "пустых" вызовов клиентом.

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

/// <summary>
/// Service contract.
/// </summary>
[ServiceContract]
public interface IMyService
{
    /// <summary>
    /// Client method.
    /// </summary>
    [OperationContract]
    string Login(string login, string password);
 
    /// <summary>
    /// Get callback message call.
    /// </summary>
    [OperationContract]
    IEnumerable<MessageBase> GetMessages(string clientId, TimeSpan timeout);
}

Пример я добавил в проект, созданный для статьи "WCF service. С чего начать. Основные моменты создание службы.", который доступен по ссылке:

https://bitbucket.org/sergey_vaulin/wcf-examples/src


В него был добавлен examle с названием "BasicHttpBinding callback emulation.". После запуска, клиентам будет через разные промежутки приходить персональные сообщения с сервера и широковещательные. Результат запуска показан на изображении ниже, сверху в окне запущен сервер, снизу клиент:



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

    // 1. Login method call.
    var clientId = client.Login(Guid.NewGuid().ToString(), Guid.NewGuid().ToString());
                
    do
    {
        // 2. Infinity GetMessage requests
        var callbackMessages = client.GetMessages(clientId, TimeSpan.FromSeconds(10));
     
        // 3. Processing messages
        foreach (var callbackMessage in callbackMessages)
        {
            var broadcast = callbackMessage as BroadcastMessage;
            if (broadcast != null)
            {
                ProcessBroadcast(clientId, broadcast);
                continue;
            }
     
            var personal = callbackMessage as PersonalMessage;
            if (personal != null)
            {
                ProcessPersonal(clientId, personal);
                continue;
            }
        }
    } while (true);
  2. QueueManager.cs содержит всю "магию" - управляет очередью на обработку сообщений и удаляет клиентов по Timeout. Метод GetMessage выглядит таким образом:

    /// <summary>
    /// Get client messages.
    /// </summary>
    /// <param name="clientId">Client identity.</param>
    /// <param name="sleep">Sleep timeout.</param>
    /// <returns>Message sequence or empty sequence in the case no messages.</returns>
    public IEnumerable<MessageBase> GetMessages(string clientId, TimeSpan sleep)
    {
        Client client;
        if (!_clients.TryGetValue(clientId, out client))
        {
            throw new ClientNotFoundedException(clientId);
        }
     
        client.WaitOne(sleep);
        client.Reset();
        return client.Pop();
    }
  3. TestMessageEmulator.cs эмитирует вызовы на стороне сервера. 
  4. PersonalMessage.cs и BroadcastMessage.cs демонстрируют два примера сообщений с сервера, который на клиенте интерпретируются как отдельные вызовы с сервера.
Внимание! Для того, что бы новый тип сообщения унаследованного от базового без проблем передался через метод GetMessages, его необходимо добавить в [KnowTypes()] класса MessageBase.
.

Комментариев нет:

Отправить комментарий