Как реализовать базовый "длинный опрос"?
Я могу найти много информации о том, как долго работает опрос (например,этой и этой), но не простой примеры, как реализовать это в коде.
все, что я могу найти-это cometd, который опирается на фреймворк Dojo JS и довольно сложную серверную систему..
в принципе, как бы я использовал Apache для обслуживания запросов, и как бы я написал простой скрипт (скажем, на PHP), который бы "долго опрашивал" сервер для новых сообщений?
пример не должен быть масштабируемым, безопасным или полным, он просто должен работать!
18 ответов:
это проще, чем я первоначально думал.. В основном у вас есть страница, которая ничего не делает, пока не будут доступны данные, которые вы хотите отправить (скажем, новое сообщение приходит).
вот действительно простой пример, который отправляет простую строку через 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 быстро свяжет все "рабочие потоки" и оставит его неспособным отвечать на другие запросы.. Есть способы обойти это, но рекомендуется написать "long-poll server" в чем-то вроде Python's витая, который не зависит от одного потока на запрос. принятый в cometd - это популярный (который доступен на нескольких языках), и Торнадо это новый фреймворк, созданный специально для таких задач (он был построен для кода с длинным опросом FriendFeed)... но как простой пример, Apache более чем достаточно! Этот скрипт может быть легко написан на любом языке (я выбрал Apache / PHP, поскольку они очень распространены, и я случайно запускал их локально)
затем, в Javascript, вы запрашиваете вышеуказанный файл (
msg_srv.php
), и ждать ответа. Когда вы получаете один, вы действуете на основе данных. Затем вы запрашиваете файл и снова ждете, действуете на данные (и повторяете)ниже приведен пример такой страницы.. Когда страница загруженный, он отправляет первоначальный запрос на .. Если это удастся, мы добавим сообщение к
#messages
div, затем через 1 секунду мы снова вызываем функцию waitForMsg, которая запускает ожидание.1 секунда
setTimeout()
это действительно основной ограничитель скорости, он отлично работает без этого, но еслиmsgsrv.php
всегда возвращает мгновенно (с синтаксической ошибкой, например) - вы заливаете браузер, и он может быстро замерзнуть. Это лучше сделать проверку, если файл содержит допустимый ответ JSON и/или сохраняет общее количество запросов в минуту / секунду и паузу соответствующим образом.если страница ошибается, она добавляет ошибку к
#messages
div, ждет 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.
Edit: (так как каждый вставляет свой код здесь)
Это полный многопользовательский чат на основе 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>
Торнадо предназначен для длительного опроса и включает в себя очень минимальный (несколько сотен строк на Python) чат приложение in/примеры/chatdemo, включая код сервера и код клиента JS. Это работает следующим образом:
клиенты используют JS для запроса обновлений с момента (номер последнего сообщения), сервер URLHandler получает их и добавляет обратный вызов для ответа клиенту в очередь.
когда сервер получает новое сообщение, onmessage событие срабатывает, циклы через обратные вызовы и отправляет сообщения.
клиентский JS получает сообщение, добавляет его на страницу, а затем запрашивает обновления с этого нового идентификатора сообщения.
Я думаю, что клиент выглядит как обычный асинхронный запрос AJAX, но вы ожидаете, что это займет "много времени", чтобы вернуться.
сервер тогда выглядит так.
while (!hasNewData()) usleep(50); outputNewData();
Итак, запрос AJAX отправляется на сервер, вероятно, включая метку времени, когда это было последнее обновление, так что ваш
hasNewData()
знает, какие данные у вас уже есть. Затем сервер находится в цикле ожидания, пока не будут доступны новые данные. Все это время ваш запрос AJAX все еще подключен, просто висит там в ожидании данных. Наконец, когда новые данные доступны, сервер передает их вашему запросу AJAX и закрывает соединение.
здесь некоторые классы, которые я использую для длительного опроса в C#. Существует 6 классов (см. ниже).
- контроллер: обрабатывает действия, необходимые для создания допустимого ответа (операции с БД и т. д.)
- процессор: управляет асинхронной связью с веб-страницей (сама)
- IAsynchProcessor: экземпляры процессов службы, которые реализуют это интерфейс
- сервис: процессы запрашивают объекты, реализующие IAsynchProcessor
- запрос: оболочка IAsynchProcessor, содержащая ваш ответ (объект)
- ответ: содержит пользовательские объекты или поля
Это хороший 5-минутный скринкаст о том, как сделать длинный опрос с помощью PHP & jQuery: http://screenr.com/SNH
код очень похож на dbrпример выше.
здесь простой пример длительного опроса в PHP от Erik Dubbelboer С помощью :
<? 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 ';
и вот демо:
Я этой чтобы справиться с Comet, я также настроил Comet с помощью сервера 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 иметь шаблоны
Это один из сценариев, для которых PHP является очень плохим выбором. Как уже упоминалось ранее, вы можете связать всех своих работников Apache очень быстро, делая что-то вроде этого. PHP построен для запуска, выполнения, остановки. Он не создан для начала, подождите...выполнить, остановить. Вы будете топить свой сервер очень быстро и обнаружите, что у вас есть невероятные проблемы масштабирования.
тем не менее, вы все еще можете сделать это с PHP и не убить свой сервер с помощью Nginx HttpPushStreamModule: http://wiki.nginx.org/HttpPushStreamModule
вы устанавливаете nginx перед Apache (или что-то еще), и он будет заботиться о сохранении открытых параллельных соединений. Вы просто отвечаете полезной нагрузкой, отправляя данные на внутренний адрес, который вы могли бы сделать с фоновым заданием, или просто отправляете сообщения людям, которые ждали, когда появятся новые запросы. Это не позволяет PHP-процессам оставаться открытыми во время длительного опроса.
Это не эксклюзивно для PHP и может быть сделано с помощью nginx с любым бэкэнд языком. Нагрузка параллельных открытых соединений равна узлу.js так что самое большое преимущество заключается в том, что он избавляет вас от необходимости узла для чего-то подобного.
вы видите, что многие другие люди упоминают другие языковые библиотеки для выполнения длительного опроса, и это не зря. PHP просто не очень хорошо построен для такого типа поведения, естественно.
вот узел.пример Яш это поставляется с клиентом jquery. Есть также инструкции по настройке его на heroku.
Почему бы не рассмотреть веб-сокеты вместо опроса? Они очень эффективны и просты в настройке. Однако они поддерживаются только в современных браузерах. Вот это краткий справочник.
группа WS-I опубликовала что-то под названием "Надежный Безопасный Профиль" который имеет стеклянную рыбу иреализация .NET видимо взаимодействия хорошо.
с любой удачей есть Javascript реализация там тоже.
существует также реализация Silverlight, которая использует дуплекс HTTP. Вы можете подключите javascript к Silverlight объект для получения обратных вызовов когда происходит толчок.
также коммерческие платные версии Как хорошо.
для a ASP.NET реализация MVC, посмотрите на SignalR который доступен на NuGet.. обратите внимание, что NuGet часто устарел от источник ГИТ который получает очень частые коммиты.
подробнее о SignalR на A блог на Скотт Hanselman
вы можете попробовать icomet(https://github.com/ideawu/icomet), сервер c1000k c++ comet, построенный с помощью 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), IEs (Windows), Firefox, Chrome и др.
Простейшие NodeJS
const http = require('http'); const server = http.createServer((req, res) => { SomeVeryLongAction(res); }); server.on('clientError', (err, socket) => { socket.end('HTTP/1.1 400 Bad Request\r\n\r\n'); }); server.listen(8000); // the long running task - simplified to setTimeout here // but can be async, wait from websocket service - whatever really function SomeVeryLongAction(response) { setTimeout(response.end, 10000); }
производство мудрый сценарий в Экспресс-К примеру, вы получили бы
response
в middleware. Вы делаете то, что вам нужно сделать, можете охватить все длинные опрошенные методы для отображения или что-то (что видно другим потокам) и вызвать<Response> response.end()
когда вы готовы. В длинных опрошенных связях нет ничего особенного. Отдых-это то, как вы обычно структурируете свое приложение.если вы не знаете, что я имею в виду, обшаривать, это должен дать вам представление
const http = require('http'); var responsesArray = []; const server = http.createServer((req, res) => { // not dealing with connection // put it on stack (array in this case) responsesArray.push(res); // end this is where normal api flow ends }); server.on('clientError', (err, socket) => { socket.end('HTTP/1.1 400 Bad Request\r\n\r\n'); }); // and eventually when we are ready to resolve // that if is there just to ensure you actually // called endpoint before the timeout kicks in function SomeVeryLongAction() { if ( responsesArray.length ) { let localResponse = responsesArray.shift(); localResponse.end(); } } // simulate some action out of endpoint flow setTimeout(SomeVeryLongAction, 10000); server.listen(8000);
как вы видите, вы могли бы действительно реагировать на все соединения, один, делать все, что вы хотите. Есть
id
для каждого запроса, так что вы должны быть в состоянии использовать карту и доступ конкретные из вызова API.