¿Cómo envía Facebook, Gmail la notificación en tiempo real?

269

He leído algunas publicaciones sobre este tema y las respuestas son cometa, reverse ajax, http streaming, servidor push, etc.

¿Cómo funciona la notificación de correo entrante en Gmail?

¿Cómo puede GMail Chat hacer solicitudes AJAX sin interacción del cliente?

Me gustaría saber si hay referencias de código que pueda seguir para escribir un ejemplo muy simple. Muchas publicaciones o sitios web solo hablan sobre la tecnología. Es difícil encontrar un código de muestra completo. Además, parece que se pueden usar muchos métodos para implementar el cometa, por ejemplo, Hidra IFrame, XMLHttpRequest. En mi opinión, usar XMLHttpRequest es una mejor opción. ¿Qué opinas de los pros y los contras de los diferentes métodos? ¿Cuál usa Gmail?

Sé que debe hacerlo tanto en el lado del servidor como en el lado del cliente. ¿Hay algún código de muestra PHP y Javascript?

Porra
fuente

Respuestas:

428

La forma en que Facebook hace esto es bastante interesante.

Un método común para hacer tales notificaciones es sondear un script en el servidor (usando AJAX) en un intervalo dado (tal vez cada pocos segundos), para verificar si algo ha sucedido. Sin embargo, esto puede ser bastante intensivo en la red, y a menudo haces solicitudes sin sentido, porque no ha pasado nada.

La forma en que Facebook lo hace es usar el enfoque del cometa, en lugar de sondear en un intervalo, tan pronto como se completa una encuesta, emite otra. Sin embargo, cada solicitud al script en el servidor tiene un tiempo de espera extremadamente largo, y el servidor solo responde a la solicitud una vez que algo ha sucedido. Puede ver que esto sucede si abre la pestaña Consola de Firebug mientras está en Facebook, y las solicitudes de un script posiblemente demoren minutos. Realmente es bastante ingenioso, ya que este método reduce inmediatamente tanto el número de solicitudes como la frecuencia con la que tiene que enviarlas. Efectivamente, ahora tiene un marco de eventos que permite al servidor 'disparar' eventos.

Detrás de esto, en términos del contenido real devuelto por esas encuestas, es una respuesta JSON, con lo que parece ser una lista de eventos e información sobre ellos. Sin embargo, está minimizado, por lo que es un poco difícil de leer.

En términos de la tecnología real, AJAX es el camino a seguir aquí, porque puede controlar los tiempos de espera de solicitudes y muchas otras cosas. Recomiendo (haga clic aquí en el cliché de desbordamiento) usar jQuery para hacer el AJAX, eliminará muchos de los problemas de compatibilidad cruzada. En términos de PHP, ¿podría simplemente sondear una tabla de base de datos de registro de eventos en su script PHP y solo regresar al cliente cuando sucede algo? Hay, espero, muchas formas de implementar esto.

Implementar:

Lado del servidor:

Parece que hay algunas implementaciones de bibliotecas de cometas en PHP, pero para ser sincero, realmente es muy simple, algo así como el siguiente pseudocódigo:

while(!has_event_happened()) {
   sleep(5);
}

echo json_encode(get_events());
  • La función has_event_happened solo verificaría si algo sucedió en una tabla de eventos o algo así, y luego la función get_events devolvería una lista de las nuevas filas en la tabla. Depende del contexto del problema realmente.

  • ¡No olvides cambiar el tiempo máximo de ejecución de PHP, de lo contrario, se agotará pronto!

Lado del cliente:

Eche un vistazo al complemento jQuery para realizar la interacción Comet:

Dicho esto, el complemento parece agregar un poco de complejidad, realmente es muy simple para el cliente, tal vez (con jQuery) algo así como:

function doPoll() {
   $.get("events.php", {}, function(result) {
      $.each(result.events, function(event) { //iterate over the events
          //do something with your event
      });
      doPoll(); 
      //this effectively causes the poll to run again as
      //soon as the response comes back
   }, 'json'); 
}

$(document).ready(function() {
    $.ajaxSetup({
       timeout: 1000*60//set a global AJAX timeout of a minute
    });
    doPoll(); // do the first poll
});

Todo depende mucho de cómo se arma su arquitectura existente.

Alistair Evans
fuente
2
Es una explicación muy bonita y detallada. Gracias. ¿Tiene algún código de muestra para una de las muchas formas de implementarlo?
Billy
45
Creo que etiquetar PHP como un lenguaje / plataforma que no escala bien no es necesariamente cierto. Se puede utilizar para desarrollar sistemas a gran escala. Mira facebook. Si el desarrollador lo hace bien, entonces escalará, si no, entonces no lo hará. Usar una plataforma web específica no es garantía de escalabilidad. Ah, y también, la pregunta pedía PHP.
Alistair Evans
55
@Kazar: "Facebook usa PHP" es un poco engañoso: lo último que escuché fue que desarrollaron HipHop con el expreso propósito de convertir PHP a C ++, ya que PHP no funcionaba lo suficientemente bien.
cHao
14
@ cHao: Es un punto justo, sin embargo, esta respuesta fue escrita en 2009, antes de que Facebook comenzara a usar hiphop. En ese momento, Facebook todavía era un sistema a gran escala que usaba php por sí mismo.
Alistair Evans
66
Entonces, la técnica es mantener una conexión constantemente abierta, lo que mantendrá a un servidor en un estrés constante. Una cantidad típica de conexiones concurrentes para un servidor web promedio es de aproximadamente 200, pero la cantidad de usuarios de Facebook que están en línea simultáneamente es mucho mayor. ¿Cómo lo hacen?
Paul
43

Actualizar

A medida que sigo recibiendo votos a favor sobre esto, creo que es razonable recordar que esta respuesta tiene 4 años. La web ha crecido a un ritmo realmente rápido, así que tenga en cuenta esta respuesta.


Tuve el mismo problema recientemente e investigué sobre el tema.

La solución dada se llama sondeo largo y, para usarla correctamente, debe asegurarse de que su solicitud de AJAX tenga un tiempo de espera "grande" y hacer siempre esta solicitud después de que finalice el tiempo actual (tiempo de espera, error o éxito).

Encuesta larga - Cliente

Aquí, para mantener el código corto, usaré jQuery:

function pollTask() { 

    $.ajax({

        url: '/api/Polling',
        async: true,            // by default, it's async, but...
        dataType: 'json',       // or the dataType you are working with
        timeout: 10000,          // IMPORTANT! this is a 10 seconds timeout
        cache: false

    }).done(function (eventList) {  

       // Handle your data here
       var data;
       for (var eventName in eventList) {

            data = eventList[eventName];
            dispatcher.handle(eventName, data); // handle the `eventName` with `data`

       }

    }).always(pollTask);

}

Es importante recordar que (de los documentos de jQuery ):

En jQuery 1.4.xy versiones posteriores, el objeto XMLHttpRequest estará en un estado no válido si la solicitud agota el tiempo de espera; Acceder a cualquier miembro del objeto puede generar una excepción. Solo en Firefox 3.0+, las solicitudes de script y JSONP no pueden cancelarse por un tiempo de espera; el script se ejecutará incluso si llega después del tiempo de espera.

Sondeo largo: servidor

No está en ningún idioma específico, pero sería algo como esto:

function handleRequest () {  

     while (!anythingHappened() || hasTimedOut()) { sleep(2); }

     return events();

} 

Aquí, hasTimedOutse asegurará de que su código no espere para siempre y anythingHappenedverificará si sucedió algún evento. El sleepes para liberar su hilo para hacer otras cosas mientras no pasa nada. El eventsvolverá un diccionario de eventos (o cualquier otra estructura de datos es posible que prefiera) en formato JSON (o cualquier otro que prefiera).

Seguramente resuelve el problema, pero, si está preocupado por la escalabilidad y el rendimiento como lo estaba yo al investigar, podría considerar otra solución que encontré.

Solución

Use enchufes!

En el lado del cliente, para evitar problemas de compatibilidad, use socket.io . Intenta usar socket directamente y tiene retrocesos a otras soluciones cuando no hay sockets disponibles.

En el lado del servidor, cree un servidor usando NodeJS (ejemplo aquí ). El cliente se suscribirá a este canal (observador) creado con el servidor. Cada vez que se debe enviar una notificación, se publica en este canal y se notifica al suscriptor (cliente).

Si no le gusta esta solución, pruebe APE ( Ajax Push Engine ).

Espero haber ayudado.

Walter Macambira
fuente
¿Crees que 1 es un reemplazo para el otro o hay una necesidad de ambas tecnologías en el mismo proyecto?
tq
Si te refieres a APE y NodeJS, puedes elegir uno de ellos. si te refieres a solicitudes periódicas de AJAX y la que sugerí, mi solución puede recurrir a la de ajax cuando carece de soporte de socket (consulte los documentos socket.io). En ambos casos, solo necesita una solución.
Walter Macambira
Hola Walter, me gustaría utilizar tu sugerencia en uno de mis sitios. ¿Sabes dónde puedo obtener un servidor Sockets? ¡Gracias!
Progo
1
Puedes implementarlo. El nodo lo hace realmente simple.
Walter Macambira
¿Cómo detectar hasTimedOut()?
Mobasher Fasihy
18

Según una presentación de diapositivas sobre el sistema de mensajería de Facebook, Facebook utiliza la tecnología cometa para "enviar" mensajes a los navegadores web. El servidor cometa de Facebook se basa en el servidor web Erlang de código abierto mochiweb.

En la imagen a continuación, la frase "grupos de canales" significa "servidores de cometas".

Resumen del sistema

Muchos otros sitios web grandes construyen su propio servidor cometa, porque hay diferencias entre las necesidades de cada empresa. Pero construir su propio servidor cometa en un servidor cometa de código abierto es un buen enfoque.

Puede probar icomet , un servidor cometa C1000K C ++ construido 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), IE (Windows), Firefox, Chrome, etc.

ideawu
fuente
Esta imagen describe muy bien el escenario. Hubiera sido genial si se diera un ejemplo en acción. Por ejemplo, ¿qué sucede cuando una persona abre (inicia) un chatbox con un amigo? ¿Cómo sintoniza Facebook esta conversación específica y empuja los mensajes a ambos extremos? (solo una suposición: solo puedo imaginar que el programa de aplicación abre un socket y vincula ambas direcciones de cliente y luego sigue escuchando y escribiendo cada vez que se escribe un mensaje en el cuadro)
Edam
5

Facebook usa MQTT en lugar de HTTP. Empujar es mejor que votar. A través de HTTP necesitamos sondear el servidor continuamente, pero a través del servidor MQTT envía el mensaje a los clientes.

Comparación entre MQTT y HTTP: http://www.youtube.com/watch?v=-KNPXPmx88E

Nota: mis respuestas se ajustan mejor a dispositivos móviles.

abhi
fuente
3
Además, Google utiliza el servicio GCM para Android, puede ser utilizado por los desarrolladores para implementar el servicio de mensajes push. developer.android.com/google/gcm/index.html Acepte si encuentra útil la respuesta.
abhi
5

Un problema importante con las encuestas largas es el manejo de errores. Hay dos tipos de errores:

  1. La solicitud puede expirar, en cuyo caso el cliente debe restablecer la conexión de inmediato. Este es un evento normal en sondeos largos cuando no ha llegado ningún mensaje.

  2. Un error de red o un error de ejecución. Este es un error real que el cliente debe aceptar con gracia y esperar a que el servidor vuelva a estar en línea.

El problema principal es que si su controlador de errores restablece la conexión inmediatamente también para un error de tipo 2, los clientes pondrían DOS en el servidor.

Ambas respuestas con código de muestra pierden esto.

function longPoll() { 
        var shouldDelay = false;

        $.ajax({
            url: 'poll.php',
            async: true,            // by default, it's async, but...
            dataType: 'json',       // or the dataType you are working with
            timeout: 10000,          // IMPORTANT! this is a 10 seconds timeout
            cache: false

        }).done(function (data, textStatus, jqXHR) {
             // do something with data...

        }).fail(function (jqXHR, textStatus, errorThrown ) {
            shouldDelay = textStatus !== "timeout";

        }).always(function() {
            // in case of network error. throttle otherwise we DOS ourselves. If it was a timeout, its normal operation. go again.
            var delay = shouldDelay ? 10000: 0;
            window.setTimeout(longPoll, delay);
        });
}
longPoll(); //fire first handler
Ronenz
fuente