Come posso implementare il "Long Polling" di base?


776

Posso trovare molte informazioni su come funziona il polling lungo (ad esempio, questo e questo ), ma non semplici esempi di come implementarlo nel codice.

Tutto quello che posso trovare è cometd , che si basa sul framework Dojo JS e un sistema server abbastanza complesso.

Fondamentalmente, come avrei usato Apache per soddisfare le richieste e come avrei scritto un semplice script (diciamo, in PHP) che "eseguisse il polling lungo" del server per nuovi messaggi?

L'esempio non deve essere scalabile, sicuro o completo, deve solo funzionare!

Risposte:


512

È più semplice di quanto pensassi inizialmente .. Fondamentalmente hai una pagina che non fa nulla, fino a quando i dati che vuoi inviare non sono disponibili (diciamo, arriva un nuovo messaggio).

Ecco un esempio davvero semplice, che invia una semplice stringa dopo 2-10 secondi. 1 possibilità su 3 di restituire un errore 404 (per mostrare la gestione degli errori nel prossimo esempio di 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: con un sito reale, eseguirlo su un normale server Web come Apache collegherà rapidamente tutti i "thread di lavoro" e non sarà in grado di rispondere ad altre richieste. Esistono modi per aggirare questo problema, ma si consiglia di scrivere un "server di polling lungo" in qualcosa come Python's twisted , che non si basa su un thread per richiesta. cometD è popolare (disponibile in diverse lingue) e Tornado è un nuovo framework creato appositamente per tali attività (è stato creato per il codice di polling lungo di FriendFeed) ... ma come esempio semplice, Apache è più che adeguato ! Questo script potrebbe essere facilmente scritto in qualsiasi lingua (ho scelto Apache / PHP in quanto sono molto comuni e mi è capitato di eseguirli localmente)

Quindi, in Javascript, richiedi il file sopra ( msg_srv.php) e attendi una risposta. Quando ne ottieni uno, agisci sui dati. Quindi richiedi il file e attendi di nuovo, agisci sui dati (e ripeti)

Quello che segue è un esempio di tale pagina .. Quando la pagina viene caricata, invia la richiesta iniziale per il msgsrv.phpfile .. Se riesce, aggiungiamo il messaggio al #messagesdiv, quindi dopo 1 secondo chiamiamo di nuovo la funzione waitForMsg, che fa scattare l'attesa.

Il 1 secondo setTimeout()è un limitatore di velocità davvero di base, funziona bene senza questo, ma se ritorna msgsrv.php sempre all'istante (con un errore di sintassi, ad esempio) - si inonda il browser e può congelarsi rapidamente. Ciò sarebbe meglio verificare se il file contiene una risposta JSON valida e / o mantenere un totale in esecuzione di richieste al minuto / secondo e fare una pausa appropriata.

Se la pagina si guasta, aggiunge l'errore al #messagesdiv, attende 15 secondi e quindi riprova (identico a come aspettiamo 1 secondo dopo ogni messaggio)

La cosa bella di questo approccio è che è molto resistente. Se la connessione Internet dei client si interrompe, si verificherà il timeout, quindi prova a riconnetterti: questo è inerente alla durata del polling, non è richiesta una gestione complicata degli errori

Ad ogni modo, il long_poller.htmcodice, usando il 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>

7
Alcuni messaggi non potrebbero passare attraverso questa idea? In quel secondo timeout, diciamo che sono stati inviati 1000 messaggi di chat, come farebbe il server a inviare i 1000 messaggi specificamente a quel client?
DevDevDev,

15
Probabilmente. Questo è un esempio molto semplificato, per dimostrare il concetto. Per farlo meglio, avresti bisogno di un codice lato server più elaborato, in cui archiviare quei 1000 messaggi per quel client specifico e inviarli in un blocco. È inoltre possibile ridurre in sicurezza il timeout
waitForMsg

21
nodejs è un'altra eccellente soluzione lato server per lunghe richieste di polling, con l'ulteriore vantaggio (oltre Twisted) di poter scrivere codice server anche in Javascript.
Husky,

8
Questa è solo una semplice connessione AJAX ricorrente al server con intervallo di 1 secondo. Questo non ha nulla a che fare con il "polling lungo". Il polling lungo dovrebbe mantenere attiva la connessione, purché si verifichi il timeout del client.
Deele,

6
la domanda è cosa fa invece un vero script PHP sleep(rand(2,10));? per non fare nulla, eseguire il polling del database ogni 100 milisecs? quando decide di morire?
Luis Siquot,

41

Ho un esempio di chat davvero semplice come parte di slosh .

Modifica : (dal momento che tutti stanno incollando il loro codice qui)

Questa è la chat multiutente completa basata su JSON che utilizza polling lungo e slosh . Questa è una demo di come effettuare le chiamate, quindi ti preghiamo di ignorare i problemi XSS. Nessuno dovrebbe distribuire questo senza prima igienizzarlo.

Si noti che il client ha sempre una connessione al server e non appena qualcuno invia un messaggio, tutti dovrebbero vederlo all'istante.

<?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 <dustin+html@spy.net> -->
<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>

1
Posso sapere come questo è sempre collegato? Scusate se sto chiedendo qualcosa di stupido, ma voglio saperlo.
Rocky Singh,

4
Fa un HTTP GET e il server blocca il GET fino a quando non ci sono dati disponibili. Quando i dati arrivano al server, il server restituisce i dati al client, mette in coda tutto ciò che potrebbe arrivare e quindi il client si riconnette e raccoglie eventuali messaggi mancanti, altrimenti si blocca di nuovo.
Dustin,

4
All'inizio potrebbe non essere ovvio, ma la cosa è che il responsabile dello "stato sempre connesso" è ajaxStop con getNewCommentscallback lì, quindi lo spara alla fine di ogni richiesta ajax all'infinito
baldrs

32

Tornado è progettato per il polling lungo e include un'app di chat minima (poche centinaia di righe di Python) in / esempi / chatdemo , incluso il codice server e il codice client JS. Funziona così:

  • I client utilizzano JS per richiedere aggiornamenti dal (numero dell'ultimo messaggio), il server URLHandler li riceve e aggiunge un callback per rispondere al client a una coda.

  • Quando il server riceve un nuovo messaggio, l'evento onmessage si attiva, scorre i callback e invia i messaggi.

  • Il JS sul lato client riceve il messaggio, lo aggiunge alla pagina, quindi chiede aggiornamenti dal nuovo ID messaggio.


25

Penso che il client sembri una normale richiesta AJAX asincrona, ma ci si aspetta che torni "a lungo" per tornare.

Il server si presenta così.

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

outputNewData();

Quindi, la richiesta AJAX va al server, probabilmente includendo un timestamp di quando è stato l'ultimo aggiornamento in modo che tu hasNewData()sappia quali dati hai già ottenuto. Il server si trova quindi in un ciclo di sospensione fino a quando non sono disponibili nuovi dati. Per tutto il tempo, la tua richiesta AJAX è ancora connessa, semplicemente in attesa di dati. Infine, quando sono disponibili nuovi dati, il server li fornisce alla tua richiesta AJAX e chiude la connessione.


10
Questa è un'attesa impegnativa che blocca il thread corrente. Questo non si ridimensiona affatto.
Wouter Lievens,

10
No, dormire non è un'attesa impegnativa. E il punto centrale dell '"attesa" è bloccare il thread per un po'. Probabilmente intendeva 50 millisecondi (usleep (50000)), non 50 microsecondi! Ma comunque, con una tipica configurazione di Apache / PHP, c'è un altro modo per farlo?
Matt,

Bene, dal principio, non è possibile effettuare una funzione di blocco per il messaggio di chat senza attendere.
Tomáš Zato - Ripristina Monica il

Fantastico davvero! Ho creato una funzione ricorsiva nel server per verificare la presenza di nuovi dati. Ma qual è il prodotto migliore per utilizzare in modo efficiente il polling lungo? Uso Apache normale e il server non risponde quando apro più di 4/5 schede del browser :( Alla ricerca di qualcosa da usare con PHP
moderns

17

Ecco alcune classi che utilizzo per il polling lungo in C #. Ci sono sostanzialmente 6 classi (vedi sotto).

  1. Controller : elabora le azioni necessarie per creare una risposta valida (operazioni db ecc.)
  2. Processore : gestisce la comunicazione asincrona con la pagina Web (stessa)
  3. IAsynchProcessor : il servizio elabora le istanze che implementano questa interfaccia
  4. Sevice : i processi richiedono oggetti che implementano IAsynchProcessor
  5. Richiesta : wrapper IAsynchProcessor contenente la risposta (oggetto)
  6. Risposta : contiene oggetti o campi personalizzati

2
Okay ... quindi PERCHÉ questo è stato votato? Queste classi sono in effetti esempi validi di polling lungo.
Prigioniero ZERO,

Il vero polling lungo non è (semplicemente) la pratica di aumentare l'intervallo in cui si esegue un sondaggio normale (su una risorsa). Fa parte di un modello più ampio ... che è "in qualche modo" soggetto a interpretazione ... ma solo in alcune aree dell'implementazione complessiva. Detto questo ... queste classi seguono questo schema! Quindi, se hai un motivo per votare questo ... Sarei davvero interessato al motivo.
Prigioniero ZERO,

Forse è stato votato verso il basso in quanto non affronta direttamente la questione di un semplice esempio di codice. Ovviamente non l'ho votato, quindi posso solo indovinarlo.
Andrew,

16

Questo è un bel screencast di 5 minuti su come eseguire polling lunghi usando PHP e jQuery: http://screenr.com/SNH

Il codice è abbastanza simile all'esempio di dbr sopra.


3
Penso che dovresti vedere questo solo come un'introduzione al polling lungo perché questa implementazione ucciderà sicuramente il tuo server con molti utenti simultanei.
Alfred,

sto solo imparando tutto questo ... quanto è affidabile o no, con pochi utenti ... diciamo 10 chiacchierando avanti e indietro?
Somdow

12

Ecco un semplice esempio di polling lungo in PHP di Erik Dubbelboer usando l' Content-type: multipart/x-mixed-replaceintestazione:

<?

header('Content-type: multipart/x-mixed-replace; boundary=endofsection');

// Keep in mind that the empty line is important to separate the headers
// from the content.
echo 'Content-type: text/plain

After 5 seconds this will go away and a cat will appear...
--endofsection
';
flush(); // Don't forget to flush the content to the browser.


sleep(5);


echo 'Content-type: image/jpg

';

$stream = fopen('cat.jpg', 'rb');
fpassthru($stream);
fclose($stream);

echo '
--endofsection
';

Ed ecco una demo:

http://dubbelboer.com/multipart.php


11

Ho usato questo per fare i conti con Comet, ho anche impostato Comet usando il server Java Glassfish e ho trovato molti altri esempi iscrivendomi a cometdaily.com



9

Di seguito è una lunga soluzione di polling che ho sviluppato per Inform8 Web. Fondamentalmente si ignora la classe e si implementa il metodo loadData. Quando loadData restituisce un valore o l'operazione scade, stampa il risultato e ritorna.

Se l'elaborazione dello script può richiedere più di 30 secondi, potrebbe essere necessario modificare la chiamata set_time_limit () in qualcosa di più lungo.

Licenza Apache 2.0. Ultima versione su 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();

}

8

Grazie per il codice, dbr . Solo un piccolo errore di battitura in long_poller.htm attorno alla linea

1000 /* ..after 1 seconds */

Penso che dovrebbe essere

"1000"); /* ..after 1 seconds */

per farlo funzionare.

Per quelli interessati, ho provato un equivalente Django. Inizia un nuovo progetto Django, ad esempio lp per lunghi sondaggi:

django-admin.py startproject lp

Chiama l'app msgsrv per il server dei messaggi:

python manage.py startapp msgsrv

Aggiungi le seguenti righe a settings.py per avere una directory dei template :

import os.path
PROJECT_DIR = os.path.dirname(__file__)
TEMPLATE_DIRS = (
    os.path.join(PROJECT_DIR, 'templates'),
)

Definisci i tuoi pattern URL in urls.py in quanto tale:

from django.views.generic.simple import direct_to_template
from lp.msgsrv.views import retmsg

urlpatterns = patterns('',
    (r'^msgsrv\.php$', retmsg),
    (r'^long_poller\.htm$', direct_to_template, {'template': 'long_poller.htm'}),
)

E msgsrv / views.py dovrebbe apparire come:

from random import randint
from time import sleep
from django.http import HttpResponse, HttpResponseNotFound

def retmsg(request):
    if randint(1,3) == 1:
        return HttpResponseNotFound('<h1>Page not found</h1>')
    else:
        sleep(randint(2,10))
        return HttpResponse('Hi! Have a random number: %s' % str(randint(1,10)))

Infine, templates / long_poller.htm dovrebbe essere uguale a quello sopra con errore di battitura corretto. Spero che sia di aiuto.


In realtà, "15000"è l'errore di sintassi. setTimeout accetta un numero intero come secondo parametro.
Andrew Hedges,

Questa risposta ha bisogno di lavoro. È il culmine di uno o più commenti e una o più risposte separate.
Brian Webster,

8

Questo è uno degli scenari per cui PHP è una pessima scelta. Come accennato in precedenza, puoi legare rapidamente tutti i tuoi dipendenti Apache facendo qualcosa del genere. PHP è creato per l'avvio, l'esecuzione, l'arresto. Non è stato creato per l'avvio, attendere ... eseguire, interrompere. Impedirai il tuo server molto rapidamente e scoprirai che hai incredibili problemi di ridimensionamento.

Detto questo, puoi ancora farlo con PHP e non farlo uccidere il tuo server usando il nginx HttpPushStreamModule: http://wiki.nginx.org/HttpPushStreamModule

Installi nginx davanti ad Apache (o qualsiasi altra cosa) e si occuperà di tenere aperte le connessioni simultanee. Devi solo rispondere con il payload inviando i dati a un indirizzo interno che potresti fare con un lavoro in background o semplicemente far sparire i messaggi alle persone che stavano aspettando ogni volta che arrivano le nuove richieste. Ciò impedisce ai processi PHP di rimanere aperti durante un lungo polling.

Questo non è esclusivo di PHP e può essere fatto usando nginx con qualsiasi linguaggio di backend. Il carico di connessioni aperte simultanee è uguale a Node.js, quindi il vantaggio più grande è che ti fa uscire dal Nodo NEEDING per qualcosa del genere.

Vedi molte altre persone che menzionano librerie di altre lingue per aver realizzato lunghi sondaggi e questo è un buon motivo. PHP non è semplicemente ben costruito per questo tipo di comportamento naturalmente.


È un problema di Apache o PHP? Avrei problemi con il polling lungo se il mio codice PHP fosse eseguito direttamente su nginx o lighttpd?
David,

È meno un problema PHP e più un uso improprio di PHP. Ad ogni richiesta PHP esegue lo script da zero, caricando le librerie secondo necessità, eseguendo il suo codice e quindi spegnendo mentre la spazzatura raccoglie tutto ciò che è iniziato nella richiesta. Nel corso degli anni sono state apportate molte modifiche a PHP per ridurre al minimo l'impatto come collegamenti statici tardivi, caricamento lento, cache dei bytecode in memoria per rimuovere l'I / O del disco, ecc. Il problema rimane che PHP è destinato ad avviarsi e arrestarsi più rapidamente il più possibile. Le lingue che verranno caricate una volta / avviate e apriranno un thread per la richiesta sono molto più adatte per il polling lungo.
Brightball,

Ma per rispondere alla domanda, sì, potresti riscontrare il problema indipendentemente dal fatto che tu stia usando Apache o qualcos'altro. È proprio come funziona PHP. Dovrei modificare questo per dire che, se hai un carico di traffico massimo noto, PHP andrà bene. Ho visto i sistemi embedded usando PHP che non hanno problemi perché ci sono solo un paio di connessioni. Potenzialmente su una intranet aziendale questo potrebbe anche essere passabile. Tuttavia, per le applicazioni rivolte al pubblico, ucciderai i tuoi server man mano che il traffico aumenta.
Brightball,

4

Perché non prendere in considerazione le prese Web invece del polling lungo? Sono molto efficienti e facili da configurare. Tuttavia sono supportati solo nei browser moderni. Ecco un rapido riferimento .


Penso che una volta implementati i websocket ovunque (probabilmente non per gli anni a venire) saranno lo standard per questo tipo di applicazione. Purtroppo per ora, non possiamo fare affidamento su di essi per le app di produzione.
Richard,

3
@Richard Puoi comunque usare qualcosa come Socket.IO che fornisce trasporti di fallback automatici, fornendo funzionalità simili a web-socket fino a IE 6.
Brad



2

Puoi provare icomet ( https://github.com/ideawu/icomet ), un server cometa C ++ C1000K creato con libevent. icomet fornisce anche una libreria JavaScript, è facile da usare come semplice

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 supporta una vasta gamma di browser e sistemi operativi, tra cui Safari (iOS, Mac), IE (Windows), Firefox, Chrome, ecc.


0

NodeJS più semplice

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

Scenario saggio di produzione in Express per exmaple che otterresti responsenel middleware. Fai quello che devi fare, puoi estendere tutti i metodi di polling lungo a Map o qualcosa (che è visibile ad altri flussi) e invocare<Response> response.end() quando sei pronto. Non c'è niente di speciale nelle connessioni a lungo polling. Il resto è come strutturi normalmente la tua applicazione.

Se non sai cosa intendo per scoprire, questo dovrebbe darti un'idea

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

Come vedi, potresti davvero rispondere a tutte le connessioni, una, fare quello che vuoi. C'èid per ogni richiesta, quindi dovresti essere in grado di utilizzare la mappa e accedere a una specifica chiamata fuori API.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.