Eventos enviados pelo servidor e php - o que dispara eventos no servidor?

91

Tudo,

HTML5 Rocks tem um bom tutorial para iniciantes sobre eventos enviados pelo servidor (SSE):

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

Mas, não entendo um conceito importante - o que dispara o evento no servidor que faz com que uma mensagem seja enviada?

Em outras palavras - no exemplo HTML5 - o servidor simplesmente envia um timestamp uma 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()));

Se eu estivesse construindo um exemplo prático - por exemplo, uma "parede" no estilo do Facebook ou uma bolsa de valores, em que o servidor "enviaria" uma nova mensagem ao cliente sempre que algum dado fosse alterado, como isso funcionaria?

Em outras palavras ... O script PHP tem um loop que é executado continuamente, verificando se há uma alteração nos dados e enviando uma mensagem sempre que encontra um? Em caso afirmativo, como saber quando terminar esse processo?

Ou - o script PHP simplesmente envia a mensagem e, em seguida, termina (como parece ser o caso no exemplo HTML5Rocks)? Se sim - como você consegue atualizações contínuas? O navegador está simplesmente pesquisando a página PHP em intervalos regulares? Se sim - como isso é um "evento enviado pelo servidor"? Como isso é diferente de escrever uma função setInterval em JavaScript que usa AJAX para chamar uma página PHP em um intervalo regular?

Desculpe - esta provavelmente é uma pergunta incrivelmente ingênua. Mas nenhum dos exemplos que consegui encontrar deixa isso claro.

[ATUALIZAR]

Acho que minha pergunta foi mal formulada, então aqui estão alguns esclarecimentos.

Digamos que eu tenha uma página da web que deve exibir o preço mais recente das ações da Apple.

Quando o usuário abre a página pela primeira vez, ela cria um EventSource com a URL do meu "fluxo".

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

Minha pergunta é a seguinte - como deve funcionar "stream.php"?

Como isso? (pseudo-có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
    }
?>

Em outras palavras - "stream.php" permanece aberto enquanto o cliente está "conectado" a ele?

Em caso afirmativo - isso significa que você tem tantos threads em execução stream.phpquanto usuários simultâneos? Em caso afirmativo - isso é remotamente viável ou uma maneira apropriada de construir um aplicativo? E como você sabe quando pode ENCERRAR uma instância de stream.php?

Minha impressão ingênua é que, se for o caso, PHP não é uma tecnologia adequada para esse tipo de servidor. Mas todas as demos que vi até agora indicam que o PHP é adequado para isso, e é por isso que estou tão confuso ...

Mattstuehler
fonte
Essa é a parte que um desenvolvedor deve codificar por conta própria. Os meios de obter os dados são via websockets / long polling etc. entretanto, o truque é o que dispara o evento. Pessoalmente, experimentei algumas abordagens e uma abordagem que gostei (mas não era tão à prova de falhas) foi fazer o MySQL disparar um programa de console toda vez que algo fosse inserido em uma tabela específica. O programa de console receberá as informações sobre o registro alterado / inserido e enviará uma notificação ao usuário correspondente por meio de WebSockets. Basicamente, eu tinha um daemon PHP esperando para enviar mensagens.
NB
Um problema com isso, o SSE não é suportado pelo IE: - / Também gostaria de ler este prodigyproductionsllc.com/articles/programming/javascript/… Acho que ele está usando uma porta para evitar o problema de muitos filhos, mas no geral se parece com o dele recomendação é evitar SSE. Parece muito mais problema do que vale a pena, IMO.
PJ Brunet
Atualmente não compatível com IE11 ou navegador Android caniuse.com/eventsource
PJ Brunet
1
Se alguém precisar do código sse php: github.com/shahzadthathal/server-sent-events-php-example
Muhammad Shahzad
4
Eu tive a mesma pergunta e eu acho que compreender profundamente o que você quer dizer com o que desencadeia o evento no servidor ... . Quando você cria um objeto de EventSource('stream.php'), o cliente abre uma conexão com a stream.phpqual é como chamá-lo por ajax. ESTA conexão dispara o código do servidor e mantém a conexão aberta enquanto o código do servidor tiver algo a dizer. Em seguida, a conexão fecha e após um pequeno atraso (3 segundos no cromo, eu acho) o cliente reabre a conexão que aciona seu stream.phparquivo novamente.
Ahmad Maleki

Respostas:

28

"..." stream.php "permanece aberto enquanto o cliente está" conectado "a ele?"

Sim, e seu pseudo-código é uma abordagem razoável.

"E como você sabe quando pode ENDER uma instância de stream.php?"

No caso mais comum, isso acontece quando o usuário sai do seu site. (O Apache reconhece o soquete fechado e elimina a instância do PHP.) O principal momento em que você pode fechar o soquete do lado do servidor é se você sabe que não haverá dados por um tempo; a última mensagem que você envia ao cliente é para dizer a ele para voltar em um determinado horário. Por exemplo, em seu caso de streaming de estoque, você poderia fechar a conexão às 20h e dizer aos clientes para voltarem em 8 horas (assumindo que o NASDAQ esteja aberto para cotações das 4h às 20h). Sexta à noite você diz a eles para voltarem na segunda de manhã. (Tenho um livro a ser publicado sobre ESS e dedico algumas seções a esse assunto.)

"... se for esse o caso, PHP não é uma tecnologia adequada para este tipo de servidor. Mas todas as demos que vi até agora indicam que PHP é adequado para isso, e é por isso que estou tão confuso..."

Bem, as pessoas argumentam que o PHP não é uma tecnologia adequada para sites normais, e estão certas: você poderia fazer isso com muito menos memória e ciclos de CPU se substituísse toda a pilha LAMP por C ++. No entanto, apesar disso, o PHP capacita a maioria dos sites muito bem. É uma linguagem muito produtiva para o trabalho na web, devido a uma combinação de uma sintaxe semelhante ao C familiar e tantas bibliotecas, e uma linguagem reconfortante para os gerentes, pois muitos programadores de PHP podem ser contratados, muitos livros e outros recursos, e alguns grandes casos de uso (por exemplo, Facebook e Wikipedia). Esses são basicamente os mesmos motivos pelos quais você pode escolher o PHP como sua tecnologia de streaming.

A configuração típica não será uma conexão ao NASDAQ por instância do PHP. Em vez disso, você terá outro processo com uma única conexão ao NASDAQ, ou talvez uma única conexão de cada máquina em seu cluster ao NASDAQ. Isso então empurra os preços para um servidor SQL / NoSQL ou para a memória compartilhada. Então o PHP apenas pesquisa essa memória compartilhada (ou banco de dados) e empurra os dados para fora. Ou tenha um servidor de coleta de dados e cada instância do PHP abre uma conexão de soquete para esse servidor. O servidor de coleta de dados envia atualizações para cada um de seus clientes PHP, conforme as recebe, e eles, por sua vez, enviam esses dados para seu cliente.

O principal problema de escalabilidade com o uso de Apache + PHP para streaming é a memória para cada processo Apache. Quando você atingir o limite de memória do hardware, tome a decisão de negócios de adicionar outra máquina ao cluster ou cortar o Apache do loop e escrever um servidor HTTP dedicado. O último pode ser feito em PHP para que todo o seu conhecimento e código existentes possam ser reutilizados, ou você pode reescrever todo o aplicativo em outra linguagem. O desenvolvedor puro em mim escreveria um servidor HTTP dedicado e simplificado em C ++. O gerente em mim acrescentaria outra caixa.

Darren Cook
fonte
Como cada processo de conexão do Apache consome memória, será melhor usar o Nginx em vez disso?
Zhang Buzz
@ZhangBuzz Onde eu disse Apache + PHP, realmente significa "servidor web + processo PHP", então basicamente não há diferença em usar um servidor web diferente.
Darren Cook
Talvez servidores como esses? github.com/hoaproject/Eventsource ou github.com/hhxsv5/php-sse
Enrique
Observe também que o Nginx poderia ser muito mais eficiente para isso porque usa menos memória: blog.webfaction.com/2008/12/…
Enrique
31

Os eventos enviados pelo servidor são para atualização em tempo real do lado do servidor para o lado do cliente. No primeiro exemplo, a conexão do servidor não é mantida e o cliente tenta se conectar novamente a cada 3 segundos e faz com que os eventos enviados pelo servidor não diferenciem o polling do Ajax.

Portanto, para fazer a conexão persistir, você precisa envolver seu código em um loop e verificar se há atualizações constantemente.

O PHP é baseado em thread e mais usuários conectados farão com que o servidor fique sem recursos. Isso pode ser resolvido controlando o tempo de execução do script e encerrando o script quando ele exceder um período de tempo (ou seja, 10 minutos). A EventSourceAPI se conectará automaticamente novamente para que o atraso esteja em um intervalo aceitável.

Além disso, verifique minha biblioteca PHP para eventos enviados pelo servidor , você pode entender mais sobre como fazer eventos enviados pelo servidor em PHP e torná-lo mais fácil de codificar.

Licson
fonte
5
Você poderia explicar "Isso pode ser resolvido controlando o tempo de execução do script e encerrando o script quando ele ultrapassar um determinado período de tempo"? Se você tiver um número excessivo de usuários, isso realmente melhoraria muito o uso de recursos fechando a conexão, já que o usuário se conectaria novamente em 3 segundos?
Lucas de
4

Percebi que o sse techink envia todos os dados de atraso para o cliente (algo como reverter o pool de dados techink da página do cliente ex Ajax pooling data.) Portanto, para superar esse problema, fiz isso em uma 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'] );
?>

e o sse.php é:

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

Observe que no sseSerer.php eu inicio uma sessão e usando uma variável de sessão! para superar o problema.

Também chamo o sseServer.php via Ajax (postar e definir o valor para variable message) toda vez que quero "atualizar" a mensagem.

Agora no jQuery (javascript) eu faço algo assim: 1º) Eu declaro uma variável global var timeStamp = 0; 2ª) eu uso o próximo 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 */

Na linha de: $.notify("Please refresh "+event.data, "info"); é aí que você pode lidar com a mensagem.

No meu caso, costumava enviar uma notificação jQuery.

Você pode usar POSIX PIPES ou uma tabela de banco de dados ao invés para passar a "mensagem" via POST, já que o sseServer.php faz algo como um "loop infinito".

Meu problema no momento é que o código acima NÃO ENVIA A "mensagem" para todos os clientes, mas apenas para o par (o cliente que chamou o sseServer.php funciona como individual para cada par), então vou mudar a técnica e para um Atualização do banco de dados da página que eu quero acionar a "mensagem" e, em seguida, o sseServer.php em vez de obter a mensagem via POST, ele irá obtê-lo da tabela do banco de dados.

Espero ter ajuda!

c.chasapis
fonte
3

Esta é realmente uma questão estrutural sobre seu aplicativo. Eventos em tempo real são algo em que você deseja pensar desde o início, para que possa projetar seu aplicativo em torno disso. Se você escreveu um aplicativo que apenas executa um monte de mysql(i)_querymétodos aleatórios usando consultas de string e não os passa por nenhum tipo de intermediário, então muitas vezes você não terá escolha a não ser reescrever grande parte de seu aplicativo ou sondagem constante do lado do servidor.

Se, no entanto, você gerencia suas entidades como objetos e as passa por algum tipo de classe intermediária, pode se conectar a esse processo. Veja este exemplo:

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

Em seu aplicativo, quando você estiver pronto para salvar:

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

Este não é o exemplo mais elegante, mas deve servir como um bloco de construção decente. Você pode conectar-se à sua camada de persistência real para lidar com o acionamento desses eventos. Então, você os obtém imediatamente (o mais em tempo real possível) sem sobrecarregar seu servidor (já que você não precisa consultar constantemente seu banco de dados para ver se as coisas mudaram).

Obviamente, você não detectará alterações manuais no banco de dados dessa maneira - mas se estiver fazendo algo manualmente em seu banco de dados com qualquer frequência, você deve:

  • Corrija o problema que exige que você faça uma alteração manual
  • Crie uma ferramenta para agilizar o processo e acionar esses eventos
Colin M
fonte
3
Colin - obrigado pela sua resposta. Minha culpa - minha pergunta não é clara - mas não é isso que estou perguntando. O que eu queria perguntar é o seguinte ... Se você estiver usando PHP como seu "servidor" - o script PHP que você chama do EventSource em seu cliente precisa ser executado o tempo todo em que o cliente está conectado a ele? Isso significaria que, se você tiver 1.000 usuários simultâneos, terá 1.000 threads separados executando 1.000 instâncias simultâneas de seu script PHP? Isso é viável? E, como você saberia quando encerrar o script php (assumindo que ele está em loop para permanecer "vivo")?
mattstuehler
-7

Basicamente, o PHP não é uma tecnologia adequada para esse tipo de coisas. Sim, você pode fazer funcionar, mas será um desastre em alta carga. Nós rodamos stockservers que enviam sinais de mudança de ações via websockets para dezenas de milhares de usuários - e se usássemos php para isso ... Bem, poderíamos, mas aqueles ciclos caseiros - é apenas um pesadelo. Cada conexão fará um processo separado no servidor ou você terá que lidar com conexões de algum tipo de banco de dados.

Basta usar nodejs e socket.io. Ele permitirá que você inicie facilmente e tenha um servidor em execução em alguns dias. O Nodejs também tem limitações próprias, mas para conexões de websockets (e SSE) agora é a tecnologia mais poderosa.

E também - SSE não é tão bom quanto parece. A única vantagem dos websockets - é que os pacotes estão sendo compactados com gzip nativamente (ws não é gzipado), mas a desvantagem é que o SSE é uma conexão de um lado. Seu usuário, se quiser adicionar outro símbolo de ação ao subscrito, terá que fazer uma solicitação ajax (incluindo todos os problemas com o controle de origem e a solicitação será lenta). Em websockets, o cliente e o servidor se comunicam de ambas as maneiras em uma única conexão aberta, portanto, se o usuário enviar um sinal de negociação ou assinar uma cotação, ele apenas enviará uma string na conexão já aberta. E é rápido.

Prosto Trader
fonte
2
Você pode usar React.php basicamente da mesma maneira que o loop de evento em node.js.
Matěj Koubík
3
Embora seja bom dizer que PHP não é a melhor escolha, acho que você deveria pelo menos incluir algo que o OP pediu.
Nishchal Gautam