php - Como faço para implementar o “Long Polling” básico?



long polling chat php (12)

Eu posso encontrar muitas informações sobre como Long Polling funciona (por exemplo, this e this ), mas não há exemplos simples de como implementar isso no código.

Tudo o que posso encontrar é cometd , que conta com o framework Dojo JS, e um sistema de servidores bastante complexo.

Basicamente, como eu usaria o Apache para servir as requisições, e como eu escreveria um script simples (digamos, em PHP) que iria "long poll" no servidor para novas mensagens?

O exemplo não precisa ser escalonável, seguro ou completo, apenas precisa funcionar!

https://src-bin.com


Answer #1

É mais simples do que eu pensava inicialmente .. Basicamente, você tem uma página que não faz nada, até que os dados que você deseja enviar estejam disponíveis (digamos, uma nova mensagem chega).

Aqui está um exemplo muito básico, que envia uma string simples após 2-10 segundos. 1 em 3 chances de retornar um erro 404 (para mostrar o tratamento de erros no próximo exemplo 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: Com um site real, executá-lo em um servidor web regular como o Apache rapidamente amarra todos os "threads de trabalho" e deixa-o incapaz de responder a outros pedidos .. Existem maneiras de contornar isso, mas é recomendável escrever um "long-poll server" em algo como o twisted do Python, que não depende de um thread por solicitação. cometD é um cometD popular (que está disponível em várias línguas), e o Tornado é um novo framework feito especificamente para tais tarefas (foi construído para o código long-polling do FriendFeed) ... mas como um exemplo simples, o Apache é mais do que adequado ! Esse script pode ser facilmente escrito em qualquer idioma (eu escolhi o Apache / PHP, pois eles são muito comuns, e por acaso eu os estava rodando localmente)

Em seguida, em Javascript, você solicita o arquivo acima ( msg_srv.php ) e aguarda uma resposta. Quando você pega uma, você age de acordo com os dados. Então você solicita o arquivo e espera novamente, aja de acordo com os dados (e repita)

O que se segue é um exemplo de tal página. Quando a página é carregada, envia a solicitação inicial para o arquivo msgsrv.php . Se for bem-sucedida, #messages a mensagem ao #messages div, depois de 1 segundo, chamamos a função waitForMsg novamente, que aciona a espera.

O 1 segundo setTimeout() é um limitador de taxa muito básico, funciona bem sem isso, mas se o msgsrv.php sempre retornar instantaneamente (com um erro de sintaxe, por exemplo) - você inunda o navegador e pode congelar rapidamente. Isso seria melhor verificar se o arquivo contém uma resposta JSON válida e / ou manter um total de solicitações por minuto / segundo em execução e pausa adequada.

Se os erros da página, ele anexa o erro ao #messages div, aguarda 15 segundos e, em seguida, tenta novamente (idêntico a como esperamos 1 segundo após cada mensagem)

O bom dessa abordagem é que ela é muito resiliente. Se a conexão com a Internet do cliente for interrompida, o tempo limite será esgotado e, em seguida, tente reconectar - isso é inerente à duração da pesquisa, não é necessário nenhum tratamento de erros complicado.

De qualquer forma, o código long_poller.htm , usando o 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>

Answer #2

Abaixo está uma longa solução de pesquisa que desenvolvi para o Inform8 Web. Basicamente você sobrescreve a classe e implementa o método loadData. Quando o loadData retorna um valor ou a operação expira, ele imprime o resultado e retorna.

Se o processamento do seu script demorar mais de 30 segundos, talvez seja necessário alterar a chamada set_time_limit () para algo mais longo.

Licença do Apache 2.0. Versão mais recente no 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();

}


Answer #4

Este é um ótimo screencast de 5 minutos sobre como fazer pesquisas longas usando PHP e jQuery: http://screenr.com/SNH

Código é bem parecido com o exemplo de dbr acima.


Answer #5

Eu acho que o cliente se parece com um pedido AJAX assíncrono normal, mas você espera que demore "muito tempo" para voltar.

O servidor então se parece com isso.

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

outputNewData();

Assim, o pedido AJAX vai para o servidor, provavelmente incluindo um timestamp de quando foi a última atualização, para que o seu hasNewData() saiba quais dados você já possui. O servidor fica em um loop até que novos dados estejam disponíveis. O tempo todo, o seu pedido AJAX ainda está conectado, aguardando apenas por dados. Por fim, quando novos dados estão disponíveis, o servidor os fornece à sua solicitação AJAX e fecha a conexão.


Answer #6

Eu tenho um exemplo de chat muito simples como parte do slosh .

Edit : (já que todos estão colando o código aqui)

Este é o bate-papo com vários usuários baseado em JSON completo, usando sondagens longas e slosh . Esta é uma demonstração de como fazer as chamadas, portanto, ignore os problemas do XSS. Ninguém deve implantar isso sem sanitizá-lo primeiro.

Observe que o cliente sempre tem uma conexão com o servidor e, assim que alguém envia uma mensagem, todos devem vê-la mais ou menos instantaneamente.

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

Answer #7

NodeJS mais simples

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

Cenário de produção sábio no Express para exmaple você obteria response no middleware. Você precisa fazer o escopo de todos os longos métodos sondados para Mapear ou algo assim (que é visível para outros fluxos) e invocar <Response> response.end() sempre que estiver pronto. Não há nada especial sobre conexões com pollings longos. Descanso é como você normalmente estrutura seu aplicativo.

Se você não sabe o que eu quero dizer com escopo, isso deve lhe dar uma ideia

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 você vê, você poderia realmente responder a todas as conexões, uma, fazer o que quiser. Há id para cada solicitação, portanto, você deve poder usar o mapa e acessar dados específicos da chamada da API.




Answer #10

Por que não considerar os sockets da web em vez de uma longa pesquisa? Eles são muito eficientes e fáceis de configurar. No entanto, eles são suportados apenas em navegadores modernos. Aqui está uma referência rápida .


Answer #11

Tornado é projetado para pesquisas longas e inclui um aplicativo de bate-papo muito pequeno (poucas centenas de linhas do Python) em / examples / chatdemo , incluindo código do servidor e código do cliente JS. Funciona assim:

  • Os clientes usam JS para solicitar atualizações desde (número da última mensagem), o servidor URLHandler os recebe e adiciona um retorno de chamada para responder ao cliente em uma fila.

  • Quando o servidor recebe uma nova mensagem, o evento onmessage é acionado, percorre os retornos de chamada e envia as mensagens.

  • O JS do lado do cliente recebe a mensagem, adiciona-a à página e, em seguida, solicita atualizações desde o novo ID da mensagem.


Answer #12

Here estão algumas classes que uso para long polling em C #. Existem basicamente 6 classes (veja abaixo).

  1. Controlador : Processa as ações necessárias para criar uma resposta válida (operações de banco de dados etc.)
  2. Processador : gerencia a comunicação em sincronia com a página da web (ela mesma)
  3. IAsynchProcessor : O serviço processa instâncias que implementam essa interface
  4. Sevice : processos solicitar objetos que implementam IAsynchProcessor
  5. Request : O wrapper IAsynchProcessor contendo sua resposta (objeto)
  6. Resposta : contém objetos ou campos personalizados




comet