Как реализовать базовый «длинный опрос»?

Я могу найти много информации о том, как работает Long Polling (например, это и это ), но нет простых примеров того, как реализовать это в коде.

Все, что я могу найти, это cometd , который опирается на инфраструктуру Dojo JS и довольно сложную серверную систему.

В принципе, как я могу использовать Apache для обслуживания запросов, и как бы я написал простой скрипт (скажем, на PHP), который бы «опросил» сервер для новых сообщений?

Пример не должен быть масштабируемым, безопасным или полным, он просто должен работать!

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

Вот действительно простой пример, который отправляет простую строку через 2-10 секунд. 1 из 3 вероятность возврата ошибки 404 (чтобы показать обработку ошибок в следующем примере Javascript)

msgsrv.php

 <?php if(rand(1,3) == 1){ /* Fake an error */ header("HTTP/1.0 404 Not Found"); die(); } /* Send a string after a random number of seconds (2-10) */ sleep(rand(2,10)); echo("Hi! Have a random number: " . rand(1,10)); ?> 

Примечание. С реальным сайтом запуск этого на обычном веб-сервере, например Apache, быстро свяжет все «рабочие потоки» и не позволяет ему отвечать на другие запросы. Есть способы обойти это, но рекомендуется писать «сервер с длинными опросами» в чем-то вроде перекрученного Python, который не полагается на один поток на запрос. cometD является популярным (который доступен на нескольких языках), а Tornado – это новая структура, специально разработанная для таких задач (она была построена для длинного кода FriendFeed) … но, как простой пример, Apache более чем адекватен ! Этот скрипт может быть легко написан на любом языке (я выбрал Apache / PHP, поскольку они очень распространены, и я случайно их запускал)

Затем в Javascript вы запрашиваете указанный выше файл ( msg_srv.php ) и ждите ответа. Когда вы его получите, вы действуете на данные. Затем вы запрашиваете файл и ждете снова, действуете на данные (и повторяете)

Ниже приведен пример такой страницы. Когда страница загружается, она отправляет исходный запрос для файла msgsrv.php . Если это удается, мы добавляем сообщение в div #messages , а затем через 1 секунду мы вызываем функция waitForMsg снова, которая вызывает ожидание.

1 секунда setTimeout() – действительно базовый ограничитель скорости, он отлично работает без этого, но если msgsrv.php всегда возвращается мгновенно (например, с синтаксической ошибкой), вы наводите на браузер браузер и можете быстро замерзать. Лучше было бы проверить, содержит ли файл действительный ответ JSON, и / или поддерживать текущее количество запросов в минуту / секунду и приостанавливать его соответствующим образом.

Если ошибки страницы, она добавляет ошибку в div #messages , ждет 15 секунд, а затем снова пытается (идентично тому, как мы ждем 1 секунду после каждого сообщения)

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

В любом случае, код long_poller.htm , используя структуру jQuery:

 <html> <head> <title>BargePoller</title> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js" type="text/javascript" charset="utf-8"></script> <style type="text/css" media="screen"> body{ background:#000;color:#fff;font-size:.9em; } .msg{ background:#aaa;padding:.2em; border-bottom:1px #000 solid} .old{ background-color:#246499;} .new{ background-color:#3B9957;} .error{ background-color:#992E36;} </style> <script type="text/javascript" charset="utf-8"> function addmsg(type, msg){ /* Simple helper to add a div. type is the name of a CSS class (old/new/error). msg is the contents of the div */ $("#messages").append( "<div class='msg "+ type +"'>"+ msg +"</div>" ); } function waitForMsg(){ /* This requests the url "msgsrv.php" When it complete (or errors)*/ $.ajax({ type: "GET", url: "msgsrv.php", async: true, /* If set to non-async, browser shows page as "Loading.."*/ cache: false, timeout:50000, /* Timeout in ms */ success: function(data){ /* called when request to barge.php completes */ addmsg("new", data); /* Add response to a .msg div (with the "new" class)*/ setTimeout( waitForMsg, /* Request next message */ 1000 /* ..after 1 seconds */ ); }, error: function(XMLHttpRequest, textStatus, errorThrown){ addmsg("error", textStatus + " (" + errorThrown + ")"); setTimeout( waitForMsg, /* Try again after.. */ 15000); /* milliseconds (15seconds) */ } }); }; $(document).ready(function(){ waitForMsg(); /* Start the inital request */ }); </script> </head> <body> <div id="messages"> <div class="msg old"> BargePoll message requester! </div> </div> </body> </html> 

У меня действительно простой пример чата как часть slosh .

Изменить : (поскольку все вставляют здесь свой код)

Это полный многопользовательский чат на основе JSON, использующий длинный опрос и slosh . Это демонстрация того, как делать вызовы, поэтому, пожалуйста, игнорируйте проблемы XSS. Никто не должен развертывать это без предварительной очистки.

Обратите внимание, что клиент всегда имеет соединение с сервером, и как только кто-либо отправляет сообщение, каждый должен увидеть его примерно мгновенно.

 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <!-- Copyright (c) 2008 Dustin Sallings <dustin+html@spy.net> --> <html lang="en"> <head> <title>slosh chat</title> <script type="text/javascript" src="http://code.jquery.com/jquery-latest.js"></script> <link title="Default" rel="stylesheet" media="screen" href="style.css" /> </head> <body> <h1>Welcome to Slosh Chat</h1> <div id="messages"> <div> <span class="from">First!:</span> <span class="msg">Welcome to chat. Please don't hurt each other.</span> </div> </div> <form method="post" action="#"> <div>Nick: <input id='from' type="text" name="from"/></div> <div>Message:</div> <div><textarea id='msg' name="msg"></textarea></div> <div><input type="submit" value="Say it" id="submit"/></div> </form> <script type="text/javascript"> function gotData(json, st) { var msgs=$('#messages'); $.each(json.res, function(idx, p) { var from = p.from[0] var msg = p.msg[0] msgs.append("<div><span class='from'>" + from + ":</span>" + " <span class='msg'>" + msg + "</span></div>"); }); // The jQuery wrapped msgs above does not work here. var msgs=document.getElementById("messages"); msgs.scrollTop = msgs.scrollHeight; } function getNewComments() { $.getJSON('/topics/chat.json', gotData); } $(document).ready(function() { $(document).ajaxStop(getNewComments); $("form").submit(function() { $.post('/topics/chat', $('form').serialize()); return false; }); getNewComments(); }); </script> </body> </html> 

Tornado предназначен для длительного опроса и включает в себя очень минимальное (несколько сотен строк Python) приложение для чата в / examples / chatdemo , включая код сервера и код клиента JS. Он работает следующим образом:

  • Клиенты используют JS для запроса обновлений с (номер последнего сообщения), сервер URLHandler получает их и добавляет обратный вызов, чтобы ответить клиенту на очередь.

  • Когда сервер получает новое сообщение, событие onmessage запускается, перебирает обратные вызовы и отправляет сообщения.

  • Клиент JS на стороне клиента получает сообщение, добавляет его на страницу, а затем запрашивает обновления с этого нового идентификатора сообщения.

Я думаю, что клиент выглядит как обычный асинхронный запрос AJAX, но вы ожидаете, что он вернется к «длинному времени».

Затем сервер выглядит так.

 while (!hasNewData()) usleep(50); outputNewData(); 

Таким образом, запрос AJAX отправляется на сервер, возможно, включая hasNewData() времени, когда это последнее обновление, так что ваш hasNewData() знает, какие данные у вас уже есть. Затем сервер сидит в петле, пока не появятся новые данные. Все время ваш запрос AJAX по-прежнему подключен, просто вися там, ожидая данных. Наконец, когда новые данные доступны, сервер передает запрос AJAX и закрывает соединение.

Вот несколько классов, которые я использую для длительного опроса в C #. В основном 6 классов (см. Ниже).

  1. Контроллер : обрабатывает действия, необходимые для создания действительного ответа (операции db и т. Д.)
  2. Процессор : управляет асинхронной связью с веб-страницей (сама)
  3. IAsynchProcessor : Службы обрабатывают экземпляры, реализующие этот интерфейс
  4. Sevice : объекты запроса процессов, которые реализуют IAsynchProcessor
  5. Запрос : оболочка IAsynchProcessor, содержащая ваш ответ (объект)
  6. Ответ . Содержит пользовательские объекты или поля.

Это хороший 5-минутный скринкаст о том, как делать длинные опросы с помощью PHP & jQuery: http://screenr.com/SNH

Код очень похож на приведенный выше пример dbr .

Вот простой пример длинного опроса в PHP Эриком Дуббелбойером, используя заголовок Content-type: multipart/x-mixed-replace :

 <? header('Content-type: multipart/x-mixed-replace; boundary=endofsection'); // Keep in mind that the empty line is important to separate the headers // from the content. echo 'Content-type: text/plain After 5 seconds this will go away and a cat will appear... --endofsection '; flush(); // Don't forget to flush the content to the browser. sleep(5); echo 'Content-type: image/jpg '; $stream = fopen('cat.jpg', 'rb'); fpassthru($stream); fclose($stream); echo ' --endofsection '; 

И вот демо:

http://dubbelboer.com/multipart.php

Я использовал это, чтобы справиться с кометами, я также создал комету с использованием сервера Java Glassfish и нашел множество других примеров, подписавшись на cometdaily.com

Ниже приведен длинный опрос, который я разработал для Inform8 Web. В основном вы переопределяете класс и реализуете метод loadData. Когда loadData возвращает значение или время выполнения операции, оно будет печатать результат и возвращать.

Если обработка вашего скрипта может занять более 30 секунд, вам может потребоваться изменить вызов set_time_limit () на что-то более продолжительное.

Лицензия Apache 2.0. Последняя версия на github https://github.com/ryanhend/Inform8/blob/master/Inform8-web/src/config/lib/Inform8/longpoll/LongPoller.php

Райан

 abstract class LongPoller { protected $sleepTime = 5; protected $timeoutTime = 30; function __construct() { } function setTimeout($timeout) { $this->timeoutTime = $timeout; } function setSleep($sleep) { $this->sleepTime = $sleepTime; } public function run() { $data = NULL; $timeout = 0; set_time_limit($this->timeoutTime + $this->sleepTime + 15); //Query database for data while($data == NULL && $timeout < $this->timeoutTime) { $data = $this->loadData(); if($data == NULL){ //No new orders, flush to notify php still alive flush(); //Wait for new Messages sleep($this->sleepTime); $timeout += $this->sleepTime; }else{ echo $data; flush(); } } } protected abstract function loadData(); } 

Спасибо за код, dbr . Просто маленькая опечатка в long_poller.htm вокруг линии

 1000 /* ..after 1 seconds */ 

Я думаю, это должно быть

 "1000"); /* ..after 1 seconds */ 

чтобы он работал.

Для желающих я попробовал эквивалент Django. Начните новый проект Django, скажите lp для длительного опроса:

 django-admin.py startproject lp 

Вызовите приложение msgsrv для сервера сообщений:

 python manage.py startapp msgsrv 

Добавьте следующие строки в settings.py, чтобы иметь каталог шаблонов :

 import os.path PROJECT_DIR = os.path.dirname(__file__) TEMPLATE_DIRS = ( os.path.join(PROJECT_DIR, 'templates'), ) 

Определите свои шаблоны URL-адресов в urls.py как таковые:

 from django.views.generic.simple import direct_to_template from lp.msgsrv.views import retmsg urlpatterns = patterns('', (r'^msgsrv\.php$', retmsg), (r'^long_poller\.htm$', direct_to_template, {'template': 'long_poller.htm'}), ) 

И msgsrv / views.py должен выглядеть так:

 from random import randint from time import sleep from django.http import HttpResponse, HttpResponseNotFound def retmsg(request): if randint(1,3) == 1: return HttpResponseNotFound('<h1>Page not found</h1>') else: sleep(randint(2,10)) return HttpResponse('Hi! Have a random number: %s' % str(randint(1,10))) 

Наконец, шаблоны / long_poller.htm должны быть такими же, как указано выше, с исправлением опечатки. Надеюсь это поможет.

Взгляните на это сообщение в блоге, в котором есть код для простого приложения чата в Python / Django / gevent .

Это один из сценариев, для которых PHP – очень плохой выбор. Как уже упоминалось ранее, вы можете очень быстро связать всех своих сотрудников Apache и сделать что-то подобное. PHP создан для запуска, выполнения, остановки. Он не создан для запуска, ожидания … выполнить, остановить. Вы очень быстро взломаете свой сервер и обнаружите, что у вас невероятные проблемы с масштабированием.

Тем не менее, вы все еще можете сделать это с помощью PHP и не убивать свой сервер с помощью nginx HttpPushStreamModule: http://wiki.nginx.org/HttpPushStreamModule

Вы устанавливаете nginx перед Apache (или что-то еще), и он позаботится о том, чтобы открыть параллельные соединения. Вы просто отвечаете полезной нагрузкой, отправляя данные на внутренний адрес, который вы могли бы сделать с фоновым заданием, или просто отправляли сообщения людям, которые ждали всякий раз, когда появлялись новые запросы. Это препятствует открытию процессов PHP во время длительного опроса.

Это не относится к PHP и может быть выполнено с использованием nginx с любым исходным языком. Нагрузка одновременных открытых соединений равна Node.js, поэтому самым большим преимуществом является то, что он выталкивает вас из NEEDING Node для чего-то подобного.

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

Вот пример node.js, который поставляется с клиентом jquery. Также есть инструкции по настройке его на героку.

Группа WS-I опубликовала статью под названием «Надежный защищенный профиль» , в которой реализована реализация Glass Fish и .NET, которая, по – видимому, хорошо взаимодействует .

В любом случае есть реализация Javascript .

Существует также реализация Silverlight, которая использует HTTP Duplex. Вы можете подключить javascript к объекту Silverlight, чтобы получить обратные вызовы, когда происходит нажатие.

Есть также коммерческие платные версии .

Почему бы не рассмотреть веб-сокеты вместо длительного опроса? Они очень эффективны и просты в установке. Однако они поддерживаются только в современных браузерах. Вот краткая справка .

Для реализации ASP.NET MVC посмотрите на SignalR, который доступен на NuGet .. обратите внимание, что NuGet часто устарел от источника Git, который получает очень частые фиксации.

Узнайте больше о SignalR в блоге Скоттом Хансельманом

Вы можете попробовать icomet ( https://github.com/ideawu/icomet ), сервер кометы C1000K C ++, созданный с помощью libevent. icomet также предоставляет библиотеку JavaScript, ее легко использовать так же просто, как

 var comet = new iComet({ sign_url: 'http://' + app_host + '/sign?obj=' + obj, sub_url: 'http://' + icomet_host + '/sub', callback: function(msg){ // on server push alert(msg.content); } }); 

icomet поддерживает широкий спектр браузеров и ОС, включая Safari (iOS, Mac), IE (Windows), Firefox, Chrome и т. д.