Como o facebook, o gmail envia a notificação em tempo real?

269

Eu li alguns posts sobre este tópico e as respostas são cometa, ajax reverso, streaming de http, envio de servidor, etc.

Como funciona a notificação de mensagens recebidas no Gmail?

Como o GMail Chat pode fazer solicitações AJAX sem a interação do cliente?

Gostaria de saber se há alguma referência de código que eu possa seguir para escrever um exemplo muito simples. Muitos posts ou sites apenas falam sobre a tecnologia. É difícil encontrar um código de exemplo completo. Além disso, parece que muitos métodos podem ser usados ​​para implementar o cometa, por exemplo, Hidden IFrame, XMLHttpRequest. Na minha opinião, usar XMLHttpRequest é uma escolha melhor. O que você acha dos prós e contras dos diferentes métodos? Qual o Gmail usa?

Eu sei que ele precisa fazer isso no lado do servidor e no lado do cliente. Existe algum código de exemplo PHP e Javascript?

Billy
fonte

Respostas:

428

A maneira como o Facebook faz isso é bastante interessante.

Um método comum de fazer essas notificações é pesquisar um script no servidor (usando AJAX) em um determinado intervalo (talvez a cada poucos segundos), para verificar se algo aconteceu. No entanto, isso pode consumir bastante a rede e muitas vezes você faz solicitações inúteis, porque nada aconteceu.

A maneira como o Facebook faz isso é usar a abordagem do cometa, em vez de pesquisar em um intervalo, assim que uma pesquisa é concluída, ela emite outra. No entanto, cada solicitação para o script no servidor tem um tempo limite extremamente longo, e o servidor só responde à solicitação depois que algo acontece. Você pode ver isso acontecendo se você abrir a guia Console do Firebug enquanto estiver no Facebook, com solicitações para um script que podem levar alguns minutos. Na verdade, é bastante engenhoso, pois esse método reduz imediatamente o número de solicitações e a frequência com que você precisa enviá-las. Agora você efetivamente possui uma estrutura de eventos que permite ao servidor 'acionar' eventos.

Por trás disso, em termos do conteúdo real retornado dessas pesquisas, é uma resposta JSON, com o que parece ser uma lista de eventos e informações sobre eles. É reduzido, porém, é um pouco difícil de ler.

Em termos de tecnologia atual, o AJAX é o caminho a seguir aqui, porque você pode controlar o tempo limite da solicitação e muitas outras coisas. Eu recomendaria (clichê de estouro de pilha aqui) usando o jQuery para fazer o AJAX, isso levará muitos problemas de compatibilidade cruzada. Em termos de PHP, você pode simplesmente pesquisar uma tabela de banco de dados de log de eventos em seu script PHP e retornar ao cliente apenas quando algo acontecer? Espero, existem muitas maneiras de implementar isso.

Implementando:

Lado do servidor:

Parece haver algumas implementações de bibliotecas de cometas no PHP, mas, para ser honesto, é realmente muito simples, algo talvez como o seguinte pseudocódigo:

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

echo json_encode(get_events());
  • A função has_event_happened apenas verificaria se algo havia acontecido em uma tabela de eventos ou algo assim, e a função get_events retornaria uma lista das novas linhas da tabela? Depende do contexto do problema realmente.

  • Não se esqueça de alterar o tempo máximo de execução do PHP, caso contrário o tempo limite será excedido!

Lado do Cliente:

Dê uma olhada no plugin jQuery para fazer a interação do cometa:

Dito isto, o plugin parece adicionar um pouco de complexidade, é realmente muito simples para o cliente, talvez (com jQuery) algo 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
});

A coisa toda depende muito de como a arquitetura existente é montada.

Alistair Evans
fonte
2
É uma explicação muito agradável e detalhada. Obrigado. Você tem algum código de exemplo para uma das muitas maneiras de implementar isso?
226 Billy
45
Eu acho que rotular o PHP como uma linguagem / plataforma que não escala bem não é necessariamente verdade. Pode ser usado para desenvolver sistemas de escala extremamente grande. Veja o facebook. Se o desenvolvedor fizer certo, ele será escalado, se não, não será. O uso de uma plataforma Web específica não é garantia de escalabilidade. Ah, e também, a pergunta pediu PHP.
Alistair Evans
5
@Kazar: "O Facebook usa PHP" é um pouco enganador - pela última vez que ouvi falar, eles desenvolveram o HipHop com o objetivo expresso de converter PHP em C ++, pois o PHP não estava funcionando bem o suficiente.
cHao 16/10
14
@cHao: É um ponto justo, no entanto, essa resposta foi escrita em 2009, antes do facebook começar a usar o hiphop. Naquela época, o facebook ainda era um sistema de grande escala usando php por si próprio.
Alistair Evans
6
Portanto, a técnica é manter uma conexão constantemente aberta, o que manterá um servidor em constante estresse. Uma quantidade típica de conexões simultâneas para um servidor Web médio é de cerca de 200, mas o número de usuários do Facebook que estão online simultaneamente é muito maior. Como eles fizeram isso?
Paul
43

Atualizar

Enquanto continuo recebendo votos positivos, acho razoável lembrar que essa resposta tem 4 anos. A Web cresceu em um ritmo muito rápido, portanto, esteja atento a esta resposta.


Eu tive o mesmo problema recentemente e pesquisei sobre o assunto.

A solução fornecida é chamada de sondagem longa e, para usá-la corretamente, você deve ter certeza de que sua solicitação AJAX possui um tempo limite "grande" e sempre fazer essa solicitação após o término da corrente (tempo limite, erro ou êxito).

Pesquisa Longa - Cliente

Aqui, para manter o código curto, usarei o 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);

}

É importante lembrar que (nos documentos do jQuery ):

No jQuery 1.4.xe abaixo, o objeto XMLHttpRequest estará em um estado inválido se a solicitação atingir o tempo limite; acessar qualquer membro do objeto pode gerar uma exceção. Somente no Firefox 3.0+, solicitações de script e JSONP não podem ser canceladas com um tempo limite; o script será executado mesmo que chegue após o período de tempo limite.

Pesquisa Longa - Servidor

Não está em nenhum idioma específico, mas seria algo como isto:

function handleRequest () {  

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

     return events();

} 

Aqui, hasTimedOutgarantirá que seu código não espere para sempre e anythingHappenedverificará se algum evento ocorreu. O sleepobjetivo é liberar seu thread para fazer outras coisas enquanto nada acontece. O eventsretornará um dicionário de eventos (ou qualquer outra estrutura de dados você pode preferir) no formato JSON (ou qualquer outro que você preferir).

Certamente resolve o problema, mas, se você estiver preocupado com escalabilidade e desempenho como eu estava pesquisando, considere outra solução que encontrei.

Solução

Use soquetes!

No lado do cliente, para evitar problemas de compatibilidade, use o socket.io . Ele tenta usar o soquete diretamente e possui fallbacks para outras soluções quando os soquetes não estão disponíveis.

No lado do servidor, crie um servidor usando o NodeJS (exemplo aqui ). O cliente se inscreverá neste canal (observador) criado com o servidor. Sempre que uma notificação precisa ser enviada, ela é publicada neste canal e o assinante (cliente) é notificado.

Se você não gostar desta solução, tente o APE ( Ajax Push Engine ).

Espero ter ajudado.

Walter Macambira
fonte
você acha que 1 é um substituto para o outro ou há necessidade de ambas as tecnologias no mesmo projeto?
tq
Se você quer dizer APE e NodeJS, pode escolher um deles. se você quer dizer solicitações periódicas de AJAX e a que eu sugeri, minha solução pode recorrer à ajax quando não houver suporte para soquete (consulte a documentação do socket.io). Nos dois casos, você precisa de apenas uma solução.
Walter Macambira
Olá Walter, gostaria de usar sua sugestão em um dos meus sites. Você sabe onde posso obter um servidor Sockets? Obrigado!
Progo 28/04
1
Você pode implementá-lo. Node torna realmente simples.
Walter Macambira
Como detectar hasTimedOut()?
Mobasher Fasihy
18

De acordo com uma apresentação de slides sobre o sistema de mensagens do Facebook, o Facebook usa a tecnologia do cometa para "enviar" mensagens aos navegadores da web. O servidor cometa do Facebook é construído no mochiweb de servidor da Web Erlang, de código aberto.

Na figura abaixo, a frase "agrupamentos de canais" significa "servidores cometas".

Visão geral do sistema

Muitos outros sites grandes constroem seu próprio servidor cometa, porque existem diferenças entre as necessidades de cada empresa. Mas construir seu próprio servidor de cometa em um servidor de cometa de código aberto é uma boa abordagem.

Você pode experimentar o icomet , um servidor cometa C1000K C ++ construído com libevent. O icomet também fornece uma biblioteca JavaScript, é fácil de usar tão simples quanto:

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

O icomet suporta uma ampla variedade de navegadores e sistemas operacionais, incluindo Safari (iOS, Mac), IEs (Windows), Firefox, Chrome etc.

ideawu
fonte
Esta imagem descreve muito bem o cenário. Teria sido ótimo se um exemplo em ação fosse dado. Por exemplo, o que acontece quando uma pessoa abre (inicia) uma caixa de bate-papo com um amigo? Como o Facebook sintoniza essa conversa específica e leva as mensagens para os dois lados? (apenas um palpite: Eu só posso imaginar que o programa de aplicação abre um socket e ligam ambos os endereços de clientes e em seguida, basta continuar a ouvir e escrever sempre que mensagem está escrito na caixa)
edam
5

O Facebook usa MQTT em vez de HTTP. O envio é melhor que a pesquisa. Por meio do HTTP, precisamos pesquisar o servidor continuamente, mas por meio do servidor MQTT envia a mensagem aos clientes.

Comparação entre MQTT e HTTP: http://www.youtube.com/watch?v=-KNPXPmx88E

Nota: minhas respostas são mais adequadas para dispositivos móveis.

abhi
fonte
3
Além disso, o Google usa o serviço GCM para Android. Ele pode ser usado pelos desenvolvedores para implementar o serviço de mensagens push. developer.android.com/google/gcm/index.html Aceite se achar a resposta útil.
217 abhi
5

Uma questão importante com pesquisas longas é o tratamento de erros. Existem dois tipos de erros:

  1. A solicitação pode atingir o tempo limite. Nesse caso, o cliente deve restabelecer a conexão imediatamente. Este é um evento normal em pesquisas longas, quando nenhuma mensagem chegou.

  2. Um erro de rede ou um erro de execução. Este é um erro real que o cliente deve aceitar e aguardar normalmente pelo servidor voltar on-line.

O principal problema é que, se o manipulador de erros restabelecer a conexão imediatamente também para um erro do tipo 2, os clientes farão o DOS no servidor.

Ambas as respostas com amostra de código perdem isso.

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
fonte