Простейшая реализация long polling в WCF-сервисах на примере текстовой трансляции

Программирование

Tagged Under : , , , , ,

Собственно, про long polling я узнал от своего коллеги Ильи Кубаря. Long polling весьма простое в реализации решение для осуществления реалтайм обновлений на клиентской стороне, например, в чатах или например в текстовых трансляциях футбольных матчей.
Суть метода состоит в следующем:

  • с клиента посылается на сервер AJAX-запрос;
  • на серверной части если данные обновились, то отправляется ответ, если нет – ответ не отправляется до тех пор пока нет обновлений;
  • на клиенте и на сервере выставляется таймаут.

Long polling по сравнению с polling’ом имеет значительные плюсы:

  • данные действительно обновляются моментально
  • уменьшается кол-во запросов с клиента к серверу (не страшно, что они дольше выполняются)

Итак, рассмотрим в качестве простейшего примера прототип текстовой трансляции совместив на одной странице список сообщений и их добавление (сделаем прототип страницы управления так сказать), а также дополнительно посмотрим как сделать wcf-сервис так сказать RESTful-сервисом.
Создадим WCF Service Application и переименуем дефолтный интерфейс к ILongPollingService со следующим содержанием:

[ServiceContract(SessionMode = SessionMode.NotAllowed)] // сессия нам не нужна
public interface ILongPollingService
{
    [OperationContract]
    [WebGet(ResponseFormat = WebMessageFormat.Json)] //метод доступен при обращении по http
    List<Message> PushData(string msg); //новое сообщение от ведущего трансляции

    [OperationContract]
    [WebGet(ResponseFormat = WebMessageFormat.Json)] //метод доступен при обращении по http
    List<Message> GetData(string ID); // ID - это идентификатор последнего сообщения полученного клиентом
}

Далее опишем класс реализующий интерфейс ILongPollingService:

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] //нужно дя поддержки REST'а
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Single, InstanceContextMode = InstanceContextMode.PerCall)]
public class Service : ILongPollingService
{
    static List<Message> list = new List<Message>(); //здесь храним сообщения трансляции

    DateTime end; // время когда нужно выдать ответ

    public List<Message> PushData(string msg) //метод добавляющий сообщения
    {
        string id = Guid.NewGuid().ToString(); //создаём идентификатор
        list.Add(new Message { ID = id, MessageBody = msg }); // добавляем сообщение в список
        return list;
    }

    public List<Message> GetData(string ID) //метод, возвращающий текст трансляции
    {
        if (list.Count > 0)
            if (list[list.Count - 1].ID == ID) //если у клиента то же сообщение, что и последнее в списке добавленных, ждём
            {
                end = DateTime.Now.AddMinutes(2); //через две минуты отдаём то, что есть, чтобы если что, очередь запросов двигалась
                Wait(ID); // ждём
            }
        return list;
    }

    private void Wait(string ID)
    {
        while (list[list.Count - 1].ID == ID && DateTime.Now < end)
        {
            System.Threading.Thread.Sleep(1);
        };
    }
}

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

[DataContract]
public class Message
{
    [DataMember]
    public string ID
    {
        get;
        set;
    }

    [DataMember]
    public string MessageBody
    {
        get;
        set;
    }
}

Для поддержки REST нужно внести кое-какие настройки в кофиг, например:

<system.serviceModel>
    <services>
      <service name="LongPollingService.Service" behaviorConfiguration="LongPollingService.ServiceBehavior">
        <endpoint address="/json" binding="webHttpBinding" contract="LongPollingService.ILongPollingService" behaviorConfiguration="LongPollingServiceJsonBehavior"/>
      </service>
    </services>
    <bindings>
    </bindings>
    <behaviors>
      <endpointBehaviors>
        <behavior name="LongPollingServiceJsonBehavior">
          <webHttp/>
        </behavior>
      </endpointBehaviors>
      <serviceBehaviors>
        <behavior name="LongPollingService.ServiceBehavior">
          <serviceMetadata httpGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="false"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
</system.serviceModel>

И в разметку LongPollingService.svc добавить Factory:

<%@ ServiceHost Language="C#" Debug="false" Service="LongPollingService.Service" CodeBehind="LongPollingService.svc.cs" Factory="System.ServiceModel.Activation.WebScriptServiceHostFactory" %>

Этих простых шагов достаточно для добавления поддержки REST.

Ну. а код страницы базируется на паре js-функций и очень прост:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="LongPollingService.Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
    <script src="jquery-1.6.4.min.js" type="text/javascript"></script>
    <script type="text/javascript">
        var id = '';

        $(document).ready(function () {
            getmsgs();
        });

        function getmsgs() {
            $.ajax({
                async: true,
                url: '/LongPollingService.svc/json/GetData',
                data: 'ID=' + id,
                type: 'GET',
                dataType: 'json',
                timeout: 130000, //ставим времени больше двух минут, а то будут Aborted-ответы, а нам нужны 200
                success: function (data) {
                    var str = '';
                    for (var index = 0; index < data.length; index++) {
                        str += data[index].MessageBody + '<br/>';
                        id = data[index].ID;
                    }
                    $("#msgs").html(str);
                },
                complete: getmsgs
            });
        }

        function send() {
            $.ajax({
                async: true,
                url: '/LongPollingService.svc/json/PushData',
                data: 'msg=' + $("#txt").val(),
                type: 'GET',
                dataType: 'json',
                success: function (data) {
                    var str = '';
                    for (var index = 0; index < data.length; index++) {
                        str += data[index].MessageBody + '<br/>';
                        id = data[index].ID;
                    }
                    $("#msgs").html(str);
                }
            });
        }
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    <div id="msgs">
    </div>
    <input type="text" id="txt" />
    <input type="button" value="send" onclick="send(); return false;" />
    </div>
    </form>
</body>
</html>

CodeBehind абсолютно пуст, поэтому его не привожу.

Исходный код примера можно скачать.

P.S.: возможно в WCF есть встроенная поддержка long polling, но замарачиваться на этот счёт я не стал.

Ajax-запрос к методу страницы с помощью PageMethods и jQuery

Программирование

Tagged Under : , ,

Ещё раз убедился в правильности слов: «век живи, век учись», перелистывая книгу по ASP.NET + понял, что весьма прилично отстал, причём не один: поспрашивал коллег — они как бы тоже не в курсе были.

Как оказалось на странице можно сделать метод, к которому запросто обращаться из клиентского кода и «нахаляву» получать результат, который метод возвращает. Наряду с этим можно к этому же методу обратиться и с помощью jQuery.
Читать дальше »

ASP.NET MVC: загрузка файлов на сервер (ajax)

Программирование

Tagged Under : , , , ,

Итак, рассмотрим ajax-загрузку файлов в mvc. Ситуация здесь не столь безоблачная как при обычной загрузке, но не фатальная.
Читать дальше »

Тиражирование html-кода

Программирование

Tagged Under : , , , ,

Итак, что же такое тиражирование и как оно осуществляется?
Читать дальше »

JavaScript и Flash в веб-проектах: так ли это удобно пользователю?

Программирование

Tagged Under : , , ,

До недавнего времени я таким вопросом и не задавался вовсе, считая, что использование JavaScript и, есстественно, Ajax делают сайт более удобным и привлекательным для пользователя. Думал я так пока не потребовалось ввести подобную функциональность в один коммерческий веб-проект. Читать дальше »