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


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

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

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

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

18 749

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 классов (см. ниже).

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

Это хороший 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
';

и вот демо:

http://dubbelboer.com/multipart.php

Я этой чтобы справиться с 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 иметь шаблоны

посмотри этот блог который имеет код для простого приложения чата в Python / Django/gevent.

Это один из сценариев, для которых 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.