Как реализовать базовый «длинный опрос»? - PullRequest
761 голосов
/ 02 декабря 2008

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

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

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

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

Ответы [ 18 ]

502 голосов
/ 02 декабря 2008

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

Вот действительно простой пример, который отправляет простую строку через 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 twisted , который не полагается на один поток на запрос. cometD является популярным (доступен на нескольких языках), а Tornado - это новый фреймворк, созданный специально для таких задач (он был создан для длинного кода FriendFeed) .. Но в качестве простого примера Apache более чем адекватен! Этот скрипт легко может быть написан на любом языке (я выбрал Apache / PHP, так как они очень распространены, и мне довелось запускать их локально)

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

Ниже приведен пример такой страницы. Когда страница загружается, она отправляет начальный запрос для файла msgsrv.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>
41 голосов
/ 04 декабря 2008

У меня очень простой пример чата в составе 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>
31 голосов
/ 04 марта 2011

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

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

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

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

24 голосов
/ 02 декабря 2008

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

Сервер выглядит следующим образом.

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

outputNewData();

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

17 голосов
/ 01 сентября 2011

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

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

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

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

12 голосов
/ 08 декабря 2012

Вот простой пример длинного опроса в 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

11 голосов
/ 02 декабря 2008

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

9 голосов
/ 19 ноября 2009

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

9 голосов
/ 21 апреля 2011

Ниже приведено длинное решение для опроса, которое я разработал для 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

Ryan

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();

}
...