php http comet

php - ¿Cómo implemento la “Encuesta Larga” básica?



http comet (17)

¿Por qué no considerar los sockets web en lugar de encuestas largas? Son muy eficientes y fáciles de instalar. Sin embargo, son compatibles sólo en los navegadores modernos. Aquí hay una referencia rápida .

Puedo encontrar mucha información sobre cómo funciona el sondeo largo (por ejemplo, this y this ), pero no hay ejemplos simples de cómo implementar esto en el código.

Todo lo que puedo encontrar es cometd , que se basa en el marco Dojo JS, y en 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 haría una "encuesta larga" al servidor en busca de nuevos mensajes?

El ejemplo no tiene que ser escalable, seguro o completo, solo necesita funcionar.


A continuación se muestra una solución de sondeo largo que he desarrollado para Inform8 Web. Básicamente, reemplaza la clase e implementa el método loadData. Cuando loadData devuelve un valor o la operación se agota, imprimirá el resultado y regresará.

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

Licencia Apache 2.0. Ú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(); }


Aquí hay un ejemplo simple de sondeo largo en PHP por Erik Dubbelboer usando el 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 '';

Y aquí hay una demo:

http://dubbelboer.com/multipart.php


Creo que el cliente se ve como una solicitud AJAX asíncrona normal, pero espera que tarde un "largo tiempo" en volver.

El servidor entonces se ve así.

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

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




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

Este es un ejemplo realmente básico, que envía una cadena simple después de 2-10 segundos. 1 en 3 posibilidad 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 amarrará rápidamente todos los "subprocesos de trabajo" y lo dejará incapaz de responder a otras solicitudes. Hay formas de evitarlo, pero se recomienda escribir un "servidor de sondeo largo" en algo como Python''s twisted , que no se basa en un solo hilo por solicitud. cometD es popular (disponible en varios idiomas), y Tornado es un nuevo marco creado específicamente para tales tareas (fue creado para el código de sondeo largo de FriendFeed) ... pero como un simple ejemplo, Apache es más que adecuado ! Este script podría escribirse fácilmente en cualquier idioma (elegí Apache / PHP porque son muy comunes, y los ejecuté localmente)

Luego, en Javascript, solicita el archivo anterior ( msg_srv.php ) y espera una respuesta. Cuando obtienes uno, actúas sobre los datos. Luego solicita el archivo y espera de nuevo, actúa sobre los datos (y repite)

Lo que sigue es un ejemplo de una página de este tipo ... Cuando la página está cargada, envía la solicitud inicial para el archivo msgsrv.php . Si tiene éxito, #messages el mensaje al #messages div, luego de 1 segundo llamamos La función waitForMsg nuevamente, lo que desencadena la espera.

El setTimeout() 1 segundo es un limitador de velocidad realmente básico, funciona bien sin esto, pero si msgsrv.php siempre devuelve instantáneamente (con un error de sintaxis, por ejemplo): inunda el navegador y puede congelarse rápidamente. Esto se haría mejor verificando si el archivo contiene una respuesta JSON válida y / o manteniendo un total acumulado de solicitudes por minuto / segundo, y haciendo una pausa apropiada.

Si la página presenta un error, agrega el error a la #messages , 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 interrumpe, se agotará el tiempo de espera, luego intente volver a conectarse. Esto es inherente a la duración de la encuesta, no se requiere un manejo de errores complicado

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


Este es un buen video de 5 minutos sobre cómo hacer encuestas largas usando PHP y jQuery: http://screenr.com/SNH

El código es bastante similar al ejemplo anterior de dbr .


Este es uno de los escenarios para los cuales PHP es una muy mala elección. Como se mencionó anteriormente, puede atar a todos sus trabajadores de Apache muy rápidamente haciendo algo como esto. PHP está construido para iniciar, ejecutar, detener. No está construido para comenzar, esperar ... ejecutar, detener. Atascará su servidor muy rápidamente y encontrará que tiene problemas de escala increíbles.

Dicho esto, aún puede hacer esto con PHP y no matar el servidor utilizando el nginx HttpPushStreamModule: http://wiki.nginx.org/HttpPushStreamModule

Configura nginx delante de Apache (o cualquier otra cosa) y se encargará de mantener abiertas las conexiones concurrentes. Simplemente responda con la carga útil enviando datos a una dirección interna que podría hacer con un trabajo en segundo plano o simplemente haga que se disparen los mensajes a las personas que estaban esperando cada vez que llegan las nuevas solicitudes. Esto evita que los procesos de PHP permanezcan abiertos durante un sondeo largo.

Esto no es exclusivo de PHP y se puede hacer usando nginx con cualquier lenguaje backend. La carga de conexiones abiertas concurrentes es igual a Node.js, por lo que la ventaja más grande es que te saca del Nodo DE NECESIDAD para algo como esto.

Usted ve a muchas otras personas que mencionan bibliotecas de otros idiomas para realizar encuestas largas y eso es por una buena razón. PHP simplemente no está bien construido para este tipo de comportamiento naturalmente.


Gracias por el código, dbr . Sólo un pequeño error tipográfico 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 sondeo largo:

django-admin.py startproject lp

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

python manage.py startapp msgsrv

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

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 tales:

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, templates / long_poller.htm debe ser el mismo que el anterior con error tipográfico corregido. Espero que esto ayude.


NodeJS más simple

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

Escenario de producción en Express para ejemplo, obtendría response en el middleware. Haga lo que necesita hacer, puede abarcar todos los métodos de sondeo largo para Mapear o algo (que es visible en otros flujos), e invocar <Response> response.end() cuando esté listo. No hay nada especial en las conexiones de largo recorrido. El descanso es la forma en que normalmente estructura su aplicación.

Si no sabes a qué me refiero con el alcance, esto te dará una idea.

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

Como ves, realmente puedes responder a todas las conexiones, una, hacer lo que quieras. Hay una id para cada solicitud, por lo que debería poder usar el mapa y acceder a una llamada específica fuera de la API.



Puede probar icomet ( https://github.com/ideawu/icomet ), un servidor cometa C1000K C ++ creado con libevent. icomet también proporciona una biblioteca de JavaScript, es fácil de usar tan simple como

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 admite una amplia gama de navegadores y sistemas operativos, incluidos Safari (iOS, Mac), IEs (Windows), Firefox, Chrome, etc.


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

Edición : (ya que todos están pegando su código aquí)

Este es el chat multiusuario completo basado en JSON que utiliza un sondeo y un sondeo slosh . Esta es una demostración de cómo hacer las llamadas, así que ignore los problemas de XSS. Nadie debería desplegar esto sin desinfectarlo primero.

Tenga en cuenta que el cliente siempre tiene una conexión con el servidor, y en el momento en que alguien envía un mensaje, todos deben verlo casi 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 <[email protected]> --> <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>


Usé esto para familiarizarme con Comet, también configuré Comet usando el servidor Java Glassfish y encontré muchos otros ejemplos al suscribirme a cometdaily.com


Tornado está diseñado para un sondeo largo e incluye una aplicación de chat muy mínima (unos pocos cientos de líneas de Python) en / examples / chatdemo , que incluye 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 URLHandler del servidor los recibe y agrega una devolución de llamada para responder al cliente en una cola.

  • Cuando el servidor recibe un nuevo mensaje, el evento onmessage se dispara, 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.


Here hay algunas clases que uso para encuestas largas en C #. Básicamente hay 6 clases (ver abajo).

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