26
Собственно, про long polling я узнал от своего коллеги Ильи Кубаря. Long polling весьма простое в реализации решение для осуществления реалтайм обновлений на клиентской стороне, например, в чатах или например в текстовых трансляциях футбольных матчей.
Суть метода состоит в следующем:
- с клиента посылается на сервер AJAX-запрос;
- на серверной части если данные обновились, то отправляется ответ, если нет – ответ не отправляется до тех пор пока нет обновлений;
- на клиенте и на сервере выставляется таймаут.
Long polling по сравнению с polling’ом имеет значительные плюсы:
- данные действительно обновляются моментально
- уменьшается кол-во запросов с клиента к серверу (не страшно, что они дольше выполняются)
Итак, рассмотрим в качестве простейшего примера прототип текстовой трансляции совместив на одной странице список сообщений и их добавление (сделаем прототип страницы управления так сказать), а также дополнительно посмотрим как сделать wcf-сервис так сказать RESTful-сервисом.
Создадим WCF Service Application и переименуем дефолтный интерфейс к ILongPollingService со следующим содержанием:
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:
[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);
};
}
}
Принцип я думаю понятен: на каждый запрос создаётся отдельный экземпляр сервиса обрабатывающий запрос и отдающий данные. Список сообщений доступен всем экземплярам поэтому все могут отследить изменения.
Ну, и напоследок, класс сообщений выглядит так:
public class Message
{
[DataMember]
public string ID
{
get;
set;
}
[DataMember]
public string MessageBody
{
get;
set;
}
}
Для поддержки REST нужно внести кое-какие настройки в кофиг, например:
<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:
Этих простых шагов достаточно для добавления поддержки REST.
Ну. а код страницы базируется на паре js-функций и очень прост:
<!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, но замарачиваться на этот счёт я не стал.
