Pregunta ¿Cómo implemento "Long Polling" básico?


Puedo encontrar mucha información sobre cómo funciona Long Polling (por ejemplo, estay esta), pero no sencillo ejemplos de cómo implementar esto en el código.

Todo lo que puedo encontrar es cometd, que se basa en el framework Dojo JS, y un sistema de servidor bastante complejo.

Básicamente, ¿cómo usaría Apache para atender las solicitudes y cómo escribiría un script simple (por ejemplo, en PHP) que "encuestaría largamente" al servidor para obtener nuevos mensajes?

El ejemplo no tiene que ser escalable, seguro o completo, ¡solo tiene que funcionar!


744
2017-12-02 11:14


origen


Respuestas:


Es más simple de lo que pensé inicialmente. Básicamente tienes una página que no hace nada, hasta que los datos que deseas enviar estén disponibles (por ejemplo, llega un nuevo mensaje).

Aquí hay un ejemplo realmente básico, que envía una cadena simple después de 2-10 segundos. 1 en 3 posibilidades de devolver un error 404 (para mostrar el manejo de errores en el próximo ejemplo de 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));
?>

Nota: con un sitio real, ejecutar esto en un servidor web normal como Apache atará rápidamente todos los "hilos de trabajo" y no podrá responder a otras solicitudes. Hay formas de evitar esto, pero se recomienda escribir un "servidor de larga encuesta" en algo como Python retorcido, que no depende de un hilo por solicitud. cometD es uno popular (que está disponible en varios idiomas), y Tornado es un nuevo marco creado específicamente para tales tareas (fue creado para el código de larga duración de FriendFeed) ... pero como un simple ejemplo, ¡Apache es más que adecuado! Esta secuencia de comandos podría escribirse fácilmente en cualquier idioma (elegí Apache / PHP ya que son muy comunes, y por casualidad los ejecuté localmente)

Luego, en Javascript, solicita el archivo anterior (msg_srv.php), y espera una respuesta. Cuando obtienes uno, actúas según los datos. Luego solicite el archivo y espere nuevamente, actúe según los datos (y repita)

Lo que sigue es un ejemplo de dicha página. Cuando se carga la página, envía la solicitud inicial del msgsrv.php archivo ... Si tiene éxito, adjuntamos el mensaje al #messages div, luego de 1 segundo llamamos de nuevo a la función waitForMsg, lo que activa la espera.

El 1 segundo setTimeout() es un limitador de velocidad realmente básico, funciona bien sin esto, pero si msgsrv.php  siempre regresa instantáneamente (con un error de sintaxis, por ejemplo) - inunda el navegador y puede congelarse rápidamente. Lo mejor sería verificar si el archivo contiene una respuesta JSON válida y / o mantener un total acumulado de solicitudes por minuto / segundo y pausar de manera apropiada.

Si los errores de la página añaden el error al #messages div, espera 15 segundos y luego vuelve a intentarlo (idéntico a cómo esperamos 1 segundo después de cada mensaje)

Lo bueno de este enfoque es que es muy resistente. Si la conexión a Internet de los clientes se agota, se agotó el tiempo de espera y luego se intenta reconectar. Esto es inherente a la duración del sondeo, no se requiere un manejo de errores complicado.

De todos modos, el long_poller.htm código, utilizando el marco 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>

491
2017-12-02 13:15



Tengo un ejemplo de chat muy simple como parte de pegar.

Editar: (ya que todos pegan su código aquí)

Este es el chat completo de usuarios múltiples basado en JSON que usa encuestas largas y pegar. Esto es un manifestación de cómo hacer las llamadas, por lo tanto, ignore los problemas de XSS. Nadie debería implementar esto sin desinfectarlo primero.

Tenga en cuenta que el cliente siempre tiene una conexión con el servidor, y tan pronto como alguien envía un mensaje, todos deberían verlo más o menos al instante.

<?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>

41
2017-12-03 21:08



Tornado está diseñado para largas encuestas, e incluye un mínimo (unos cientos de líneas de Python) aplicación de chat en /ejemplos / chatdemo , incluido el código del servidor y el código del cliente JS. Funciona así:

  • Los clientes usan JS para solicitar actualizaciones desde (número del último mensaje), el servidor URLHandler los recibe y agrega una devolución de llamada para responder al cliente en una cola.

  • Cuando el servidor recibe un mensaje nuevo, el evento onmessage se activa, recorre las devoluciones de llamada y envía los mensajes.

  • El JS del lado del cliente recibe el mensaje, lo agrega a la página y luego solicita actualizaciones desde este nuevo ID de mensaje.


31
2018-03-04 14:53



Creo que el cliente parece una solicitud asíncrona normal de AJAX, pero espera que tarde un tiempo "largo".

El servidor entonces se ve así.

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

outputNewData();

Entonces, la solicitud de AJAX va al servidor, probablemente incluyendo una marca de tiempo de cuando fue la última actualización para que su hasNewData() sabe qué datos ya tiene. El servidor se sienta en un bucle durmiendo hasta que haya nuevos datos disponibles. Mientras tanto, su solicitud AJAX todavía está conectada, simplemente esperando allí datos. Finalmente, cuando hay nuevos datos disponibles, el servidor los entrega a su solicitud de AJAX y cierra la conexión.


24
2017-12-02 11:39



aquí son algunas clases que uso para las encuestas largas en C #. Básicamente hay 6 clases (ver a continuación).

  1. Controlador: Procesa las acciones necesarias para crear una respuesta válida (operaciones de DB, etc.)
  2. Procesador: Gestiona la comunicación asincrónica con la página web (en sí)
  3. IAsynchProcessor: El servicio procesa instancias que implementan esta interfaz
  4. Servicio: Procesos solicitan objetos que implementan IAsynchProcessor
  5. Solicitud: El contenedor IAsynchProcessor que contiene su respuesta (objeto)
  6. Respuesta: Contiene objetos o campos personalizados

17
2017-09-01 14:20



Este es un buen screencast de 5 minutos sobre cómo hacer un sondeo largo usando PHP y jQuery: http://screenr.com/SNH

El código es bastante similar a dbrEl ejemplo de arriba.


16
2017-10-20 15:41



Aquí está un simple ejemplo de larga encuesta en PHP por Erik Dubbelboer utilizando el Content-type: multipart/x-mixed-replace encabezamiento:

<?

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
';

Y aquí hay una demostración:

http://dubbelboer.com/multipart.php


12
2017-12-08 12:23



solía esta para familiarizarme con Comet, también configuré Comet usando el servidor Java Glassfish y encontré muchos otros ejemplos suscribiéndome a cometdaily.com


11
2017-12-02 11:21



A continuación se muestra una solución de encuesta larga que he desarrollado para Inform8 Web. Básicamente, usted anula la clase e implementa el método loadData. Cuando loadData devuelve un valor o la operación expira, imprimirá el resultado y lo devolverá.

Si el procesamiento de su script puede llevar más de 30 segundos, es posible que deba modificar la llamada a set_time_limit () por algo más.

Licencia de Apache 2.0. La última versión en 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();

}

9
2018-04-21 03:25



Gracias por el código, dbr. Solo un pequeño error en long_poller.htm alrededor de la línea

1000 /* ..after 1 seconds */

Creo que debería ser

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

para que funcione

Para aquellos interesados, probé un equivalente de Django. Comience un nuevo proyecto de Django, diga lp para largas encuestas:

django-admin.py startproject lp

Llamar a la aplicación msgsrv para el servidor de mensajes:

python manage.py startapp msgsrv

Agregue las siguientes líneas a settings.py tener un plantillas directorio:

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

Defina sus patrones de URL en urls.py como tal:

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'}),
)

Y msgsrv /views.py debería verse así:

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

Por último, plantillas /long_poller.htm debe ser el mismo que el anterior con error tipográfico corregido. Espero que esto ayude.


8
2017-09-15 12:30



Echa un vistazo a esta publicación en el blog que tiene el código para una aplicación de chat simple en Python / Django /gevent.


8
2017-11-19 17:36