Eventos enviados por el servidor y php: ¿que desencadena eventos en el servidor?


91

Todas,

HTML5 Rocks tiene un buen tutorial para principiantes sobre eventos enviados por el servidor (SSE):

http://www.html5rocks.com/en/tutorials/eventsource/basics/

Pero no entiendo un concepto importante: ¿qué desencadena el evento en el servidor que hace que se envíe un mensaje?

En otras palabras, en el ejemplo de HTML5, el servidor simplemente envía una marca de tiempo una vez :

<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache'); // recommended to prevent caching of event data.
function sendMsg($id, $msg) {
  echo "id: $id" . PHP_EOL;
  echo "data: $msg" . PHP_EOL;
  echo PHP_EOL;
  ob_flush();
  flush();
}
$serverTime = time();
sendMsg($serverTime, 'server time: ' . date("h:i:s", time()));

Si estuviera construyendo un ejemplo práctico, por ejemplo, un "muro" al estilo de Facebook o un indicador de cotización, en el que el servidor "enviaría" un nuevo mensaje al cliente cada vez que cambia algún dato, ¿cómo funciona?

En otras palabras ... ¿El script PHP tiene un bucle que se ejecuta continuamente, verificando un cambio en los datos y luego envía un mensaje cada vez que encuentra uno? Si es así, ¿cómo sabe cuándo finalizar ese proceso?

O bien, ¿el script PHP simplemente envía el mensaje y luego finaliza (como parece ser el caso en el ejemplo de HTML5Rocks)? Si es así, ¿cómo obtiene actualizaciones continuas? ¿El navegador simplemente consulta la página PHP a intervalos regulares? Si es así, ¿cómo es eso un "evento enviado por el servidor"? ¿En qué se diferencia esto de escribir una función setInterval en JavaScript que usa AJAX para llamar a una página PHP a intervalos regulares?

Lo siento, esta es probablemente una pregunta increíblemente ingenua. Pero ninguno de los ejemplos que he podido encontrar aclara esto.

[ACTUALIZAR]

Creo que mi pregunta estaba mal redactada, así que aquí tienes algunas aclaraciones.

Digamos que tengo una página web que debería mostrar el precio más reciente de las acciones de Apple.

Cuando el usuario abre la página por primera vez, la página crea un EventSource con la URL de mi "flujo".

var source = new EventSource('stream.php');

Mi pregunta es la siguiente: ¿cómo debería funcionar "stream.php"?

¿Me gusta esto? (pseudocódigo):

<?php
    header('Content-Type: text/event-stream');
    header('Cache-Control: no-cache'); // recommended to prevent caching of event data.
    function sendMsg($msg) {
        echo "data: $msg" . PHP_EOL;
        echo PHP_EOL;
        flush();
    }

    while (some condition) {
        // check whether Apple's stock price has changed
        // e.g., by querying a database, or calling a web service
        // if it HAS changed, sendMsg with new price to client
        // otherwise, do nothing (until next loop)
        sleep (n) // wait n seconds until checking again
    }
?>

En otras palabras, ¿"stream.php" permanece abierto mientras el cliente esté "conectado" a él?

Si es así, ¿eso significa que tiene tantos subprocesos en ejecución stream.phpcomo usuarios simultáneos? Si es así, ¿es eso remotamente factible o una forma adecuada de crear una aplicación? ¿Y cómo sabe cuándo puede FINALIZAR una instancia de stream.php?

Mi impresión ingenua es que, si este es el caso, PHP no es una tecnología adecuada para este tipo de servidor. Pero todas las demostraciones que he visto hasta ahora implican que PHP está bien para esto, por eso estoy tan confundido ...


Esa es la parte que un desarrollador debe codificar por su cuenta. Los medios para obtener los datos son a través de websockets / sondeos largos, etc., sin embargo, el truco es lo que desencadena el evento. En lo personal, he experimentado con algunos enfoques y uno de los enfoques que me ha gustado (pero no era que a prueba de fallos) estaba haciendo MySQL gatillo de un programa de consola cada vez que algo se insertó en una tabla específica. El programa de la consola recibiría la información sobre el registro cambiado / insertado y enviaría una notificación al usuario correspondiente a través de WebSockets. Básicamente, tenía un demonio de PHP esperando para enviar mensajes.
NB

Un problema con esto, SSE no es compatible con IE: - / También leería este prodigyproductionsllc.com/articles/programming/javascript/ ... Creo que está usando un puerto para evitar el problema de demasiados niños, pero en general se parece a él. La recomendación es evitar la ESS. Parece mucho más problema de lo que vale, en mi opinión.
PJ Brunet

Actualmente no es compatible con IE11 o el navegador de Android caniuse.com/eventsource
PJ Brunet


4
Tenía la misma pregunta y yo creo profundamente entiendo lo que quiere decir con lo que desencadena el evento en el servidor ... . Cuando creas un objeto de EventSource('stream.php'), el cliente abre una conexión con la stream.phpque es como llamarlo por ajax. ESTA conexión activa el código del lado del servidor y mantiene la conexión abierta siempre que el código del lado del servidor tenga algo que decir. Luego, la conexión se cierra y, después de un breve retraso (3 segundos en Chrome, creo), el cliente vuelve a abrir la conexión que activa su stream.phparchivo nuevamente.
Ahmad Maleki

Respuestas:


28

"... ¿" stream.php "permanece abierto mientras el cliente esté" conectado "a él?"

Sí, y su pseudocódigo es un enfoque razonable.

"¿Y cómo sabes cuándo puedes FINALIZAR una instancia de stream.php?"

En el caso más típico, esto sucede cuando el usuario abandona su sitio. (Apache reconoce el socket cerrado y mata la instancia de PHP). El momento principal en que puede cerrar el socket desde el lado del servidor es si sabe que no habrá datos durante un tiempo; el último mensaje que envía al cliente es para decirle que vuelva en un momento determinado. Por ejemplo, en su caso de transmisión de acciones, podría cerrar la conexión a las 8 p.m. y decirle a los clientes que regresen en 8 horas (suponiendo que NASDAQ esté abierto para cotizaciones de 4 a.m. a 8 p.m.). El viernes por la noche les dices que regresen el lunes por la mañana. (Tengo un próximo libro sobre SSE y dedico un par de secciones a este tema).

"... si este es el caso, PHP no es una tecnología adecuada para este tipo de servidor. Pero todas las demostraciones que he visto hasta ahora implican que PHP está bien para esto, por eso estoy tan confuso..."

Bueno, la gente argumenta que PHP no es una tecnología adecuada para sitios web normales, y tienen razón: podría hacerlo con mucha menos memoria y ciclos de CPU si reemplazara toda su pila LAMP con C ++. Sin embargo, a pesar de esto, PHP funciona perfectamente en la mayoría de los sitios. Es un lenguaje muy productivo para el trabajo web, debido a una combinación de una sintaxis familiar similar a C y tantas bibliotecas, y una reconfortante para los gerentes ya que hay muchos programadores PHP para contratar, muchos libros y otros recursos, y algunos grandes casos de uso (por ejemplo, Facebook y Wikipedia). Esas son básicamente las mismas razones por las que puede elegir PHP como tecnología de transmisión.

La configuración típica no será una conexión a NASDAQ por instancia de PHP. En su lugar, tendrá otro proceso con una sola conexión al NASDAQ, o quizás una sola conexión de cada máquina en su clúster al NASDAQ. Eso luego empuja los precios a un servidor SQL / NoSQL o a la memoria compartida. Luego PHP simplemente sondea esa memoria compartida (o base de datos) y envía los datos. O tenga un servidor de recopilación de datos y cada instancia de PHP abre una conexión de socket a ese servidor. El servidor de recopilación de datos envía actualizaciones a cada uno de sus clientes PHP, a medida que las recibe, y ellos, a su vez, envían esos datos a su cliente.

El principal problema de escalabilidad con el uso de Apache + PHP para la transmisión es la memoria para cada proceso de Apache. Cuando alcance el límite de memoria del hardware, tome la decisión comercial de agregar otra máquina al clúster o elimine Apache del ciclo y escriba un servidor HTTP dedicado. Esto último se puede hacer en PHP para que todo su conocimiento y código existentes se puedan reutilizar, o puede reescribir toda la aplicación en otro idioma. El desarrollador puro que hay en mí escribiría un servidor HTTP dedicado y optimizado en C ++. El gerente en mí agregaría otra caja.


Como cada proceso de conexión de Apache consume memoria, ¿será mejor utilizar Nginx en su lugar?
Zhang Buzz

@ZhangBuzz Donde he dicho Apache + PHP realmente significa "servidor web + proceso PHP", así que básicamente no hay diferencia con el uso de un servidor web diferente.
Darren Cook


También tenga en cuenta que Nginx podría ser mucho más eficiente para esto porque usa menos memoria: blog.webfaction.com/2008/12/…
Enrique

31

Los eventos enviados por el servidor se actualizan en tiempo real del lado del servidor al lado del cliente. En el primer ejemplo, la conexión desde el servidor no se mantiene y el cliente intenta conectarse nuevamente cada 3 segundos y hace que los eventos enviados por el servidor no sean diferentes al sondeo ajax.

Por lo tanto, para que la conexión persista, debe envolver su código en un bucle y buscar actualizaciones constantemente.

PHP está basado en subprocesos y los usuarios más conectados harán que el servidor se quede sin recursos. Esto se puede resolver controlando el tiempo de ejecución del script y finalizando el script cuando exceda una cantidad de tiempo (es decir, 10 minutos). La EventSourceAPI se conectará automáticamente de nuevo para que el retraso esté en un rango aceptable.

Además, consulte mi biblioteca PHP para eventos enviados por el servidor , puede comprender más sobre cómo hacer eventos enviados por el servidor en PHP y facilitar la codificación.


5
¿Podría dar más detalles sobre "Esto se puede solucionar controlando el tiempo de ejecución del script y finalizando el script cuando exceda una cantidad de tiempo"? Si tiene un número excesivo de usuarios, ¿realmente mejoraría mucho el uso de recursos cerrando la conexión, ya que el usuario se conectaría nuevamente en 3 segundos?
Lucas

4

Me he dado cuenta de que el sse techink envía cada par de datos de demora al cliente (algo así como invertir el techink de datos de agrupación de la página del cliente, por ejemplo, los datos de agrupación de Ajax), así que para superar este problema hice esto en una página sseServer.php:

<?php
        session_start();
        header('Content-Type: text/event-stream');
        header('Cache-Control: no-cache'); // recommended to prevent caching of event data
        require 'sse.php';
        if ($_POST['message'] != ""){
                $_SESSION['message'] = $_POST['message'];
                $_SESSION['serverTime'] = time();
        }
        sendMsg($_SESSION['serverTime'], $_SESSION['message'] );
?>

y el sse.php es:

<?php
function sendMsg($id, $msg) {
  echo "id: $id" . PHP_EOL;
  echo "data: $msg" . PHP_EOL;
  echo PHP_EOL;
  ob_flush();
  flush();
}
?>

¡Observe que en sseSerer.php comienzo una sesión y uso una variable de sesión! para superar el problema.

También llamo al sseServer.php a través de Ajax (publicando y estableciendo valor en variable message) cada vez que quiero "actualizar" el mensaje.

Ahora en jQuery (javascript) hago algo así: 1º) declaro una variable global var timeStamp = 0; 2do) uso el siguiente algoritmo:

if(typeof(EventSource)!=="undefined"){
        var source=new EventSource("sseServer.php");
        source.onmessage=function(event)
        if ((timeStamp!=event.lastEventId) && (timeStamp!=0)){
                /* this is initialization */
                timeStamp=event.lastEventId;
                $.notify("Please refresh "+event.data, "info");
        } else {
                if (timeStamp==0){
                         timeStamp=event.lastEventId;
                }
        } /* fi */

} else {
        document.getElementById("result").innerHTML="Sorry, your browser does not support server-sent events...";
} /* fi */

En la línea de: ¿ $.notify("Please refresh "+event.data, "info"); está ahí donde puede manejar el mensaje?

Para mi caso, solía enviar una notificación de jQuery.

Puede usar POSIX PIPES o una tabla DB en su lugar para pasar el "mensaje" a través de POST ya que sseServer.php hace algo así como un "bucle infinito".

Mi problema en ese momento es que el código anterior NO ENVÍA EL "mensaje" a todos los clientes, sino solo al par (el cliente que llamó al sseServer.php funciona como individuo para cada par) así que cambiaré el technik y un Actualización de la base de datos desde la página en la que quiero activar el "mensaje" y luego el sseServer.php en su lugar para obtener el mensaje a través de POST lo obtendrá de la tabla DB.

¡Espero tener ayuda!


3

Esta es realmente una pregunta estructural sobre su aplicación. Los eventos en tiempo real son algo en lo que desea pensar desde el principio, para que pueda diseñar su aplicación en torno a ellos. Si ha escrito una aplicación que solo ejecuta un montón de mysql(i)_querymétodos aleatorios utilizando consultas de cadena y no los pasa a través de ningún tipo de intermediario, muchas veces no tendrá más remedio que reescribir gran parte de su aplicación o hacerlo sondeo constante del lado del servidor.

Sin embargo, si administra sus entidades como objetos y las pasa a través de algún tipo de clase intermedia, puede conectarse a ese proceso. Mira este ejemplo:

<?php
class MyQueryManager {
    public function find($myObject, $objectId) {
        // Issue a select query against the database to get this object
    }

    public function save($myObject) {
        // Issue a query that saves the object to the database
        // Fire a new "save" event for the type of object passed to this method
    }

    public function delete($myObject) {
        // Fire a "delete" event for the type of object
    }
}

En su aplicación, cuando esté listo para guardar:

<?php
$someObject = $queryManager->find("MyObjectName", 1);
$someObject->setDateTimeUpdated(time());
$queryManager->save($someObject);

Este no es el ejemplo más elegante, pero debería servir como un bloque de construcción decente. Puede conectarse a su capa de persistencia real para manejar la activación de estos eventos. Luego, los obtienes de inmediato (lo más en tiempo real posible) sin martillar tu servidor (ya que no tienes necesidad de consultar constantemente tu base de datos y ver si las cosas cambiaron).

Obviamente, no detectará los cambios manuales en la base de datos de esta manera, pero si está haciendo algo manualmente en su base de datos con cualquier frecuencia, debe:

  • Solucione el problema que requiere que tenga que realizar un cambio manual
  • Cree una herramienta para acelerar el proceso y active estos eventos

3
Colin, gracias por tu respuesta. Mi culpa, mi pregunta no está clara, pero esto no es realmente lo que estoy preguntando. Lo que quise preguntar es esto ... Si está utilizando PHP como su "servidor", ¿el script PHP que llama desde EventSource en su cliente debe ejecutarse todo el tiempo que el cliente está conectado a él? ¿Significa eso que, si tiene 1,000 usuarios concurrentes, tiene 1,000 subprocesos separados que ejecutan 1,000 instancias concurrentes de su script PHP? ¿Es eso factible? Y, ¿cómo sabría cuándo terminar el script php (asumiendo que está en bucle para mantenerse "vivo")?
Mattstuehler

-7

Básicamente, PHP no es una tecnología adecuada para este tipo de cosas. Sí, puede hacer que funcione, pero será un desastre con una gran carga. Ejecutamos servidores de stock que envían señales de cambio de existencias a través de websockets a decenas de miles de usuarios, y si usáramos php para eso ... Bueno, podríamos, pero esos ciclos caseros, es solo una pesadilla. Cada conexión hará un proceso separado en el servidor o tendrá que manejar las conexiones desde algún tipo de base de datos.

Simplemente use nodejs y socket.io. Le permitirá iniciar fácilmente y tener un servidor en funcionamiento en un par de días. Nodejs también tiene sus propias limitaciones, pero para las conexiones websockets (y SSE) ahora es la tecnología más poderosa.

Y también, SSE no es tan bueno como parece. La única ventaja de los websockets es que los paquetes se comprimen con gzip de forma nativa (ws no es gzip), pero la desventaja es que SSE es una conexión de un solo lado. Su usuario, si quiere agregar otro símbolo de acciones al subscripton, tendrá que realizar una solicitud ajax (incluidos todos los problemas con el control de origen y la solicitud será lenta). En websockets, el cliente y el servidor se comunican en ambos sentidos en una sola conexión abierta, por lo que si el usuario envía una señal comercial o se suscribe a cotizar, simplemente envía una cadena en la conexión ya abierta. Y es rapido.


2
Puede usar React.php básicamente de la misma manera que el bucle de eventos en node.js.
Matěj Koubík

3
Aunque es bueno decir que PHP no es la mejor opción, creo que al menos debería incluir algo que OP pidió.
Nishchal Gautam
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.