Long polling

Long polling pozwala na „udawane” wysyłanie informacji z serwera do klienta. Klient żąda informacji z serwera w sposób podobny do normalnego żądania. Jednakże, jeżeli serwer nie posiada żadnych informacji dostępnych dla klienta, zamiast wysyłać puste odpowiedzi, czeka aż informacje staną się dostępne. Kiedy informacja staje się dostępna (lub po upływie odpowiedniego czasu), kompletna odpowiedź jest wysyłana do klienta. Klient natychmiast ponownie wysyła żądanie do serwera o nowe informacje. W ten sposób serwer może odpowiedzieć praktycznie od razu po ich otrzymaniu. Ideę long pollingu świetnie oddaje poniższy rysunek. Long Polling Wykorzystamy technikę long pollingu do stworzenia prostego chata. Stwórzmy bazę danych (w przykładzie została użyta baza MySQL), a w niej tabelę wiadomości. Wystarczy, że tabela będzie miała dwa pola: treść i datę wysłania wiadomości (inne pola np. autor są opcjonalne i nie będą nam potrzebne w tym przykładzie).

CREATE TABLE 'wiadomosci' (
 'tresc' text COLLATE utf8_polish_ci NOT NULL,
 'data' timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
);
Stwórzmy najpierw plik index.html. Będzie się on składał z listy wypunktowanej, w której będą się pojawiać wiadomości, pola tekstowego oraz przycisku do wysyłania wiadomości.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Long Polling</title>
<script src="http://code.jquery.com/jquery-1.4.3.min.js" type="text/javascript"></script>
<script src="custom.js" type="text/javascript"></script>
</head>
<body>
    <ul id="wiadomosci"></ul>
    <input type="text" id="add" name="tresc" />
    <input type="submit" value="dodaj" id="sub" />
</body>
</html>
Będziemy potrzebować trzech skryptów php: wiadomosci.php, long_polling.php, dodaj_wiadomosc.php. Pierwszy z nich będzie pobierał te wiadomości z bazy danych, które są nowsze od tych, które widzi klient. Poniżej kod tego skryptu.
$dbhost = "localhost";
$dbuser = "u";
$dbpass = "p";
$dbname = "long_polling";
$dbconn = @mysql_connect($dbhost,$dbuser,$dbpass);
if($dbconn)
{
    mysql_select_db($dbname,$dbconn);
    mysql_query("SET NAMES utf8");
    $data_klienta = intval($_GET['time']);
    $chat_items = mysql_query("SELECT * FROM wiadomosci WHERE data > FROM_UNIXTIME($data_klienta) ORDER BY data");

    $wiadomosci = array();
    while($wiadomosc = mysql_fetch_assoc($chat_items))
    {

        $wiadomosci[] = array('data'=>strtotime($wiadomosc['data']),'tresc'=>htmlspecialchars($wiadomosc['tresc']));
    }
    header("Content-Type: application/json");
    echo json_encode($wiadomosci);
}
Na początku łączymy się z bazą danych. Pobieramy datę najnowszej wiadomości, którą posiada klient ($_GET['time']). Datę tą będziemy wysyłać w javascripcie do skryptu wiadomosci.php. Pobrane dane zapisujemy w tablicy $wiadomosci, a następnie wyświetlamy w postaci JSON. Najważniejszym skryptem w tym przykładzie jest long_polling.php. Na początku pobieramy datę najnowszej wiadomości posiadanej przez użytkownika oraz łączymy się z bazą danych tak jak w poprzednim skrypcie. Najważniejszą częścią tego skryptu jest pętla nieskończona while. Wykonuje ona zapytanie o najnowszą wiadomość z bazy. Jeśli wystąpi błąd zwracamy false i przerywamy skrypt. W przeciwnym wypadku zapisujemy pod zmienną najnowszą wiadomość z bazy. strtotime() - zamienia ciąg znaków na czas unixowy czyli ilość sekund, która upłynęła od 1 stycznia 1970 roku.
Jeśli data najnowszej wiadomości z bazy jest nowsza od daty przesłanej od klienta przyrywamy pętlę instrukcją break. W przeciwnym razie "usypiamy" skrypt na 1 sekundę (sleep(1)). Można oczywiście podać inny czas. Skutkuje to podanym opóźnieniem w dostarczeniu nowej wiadomości. Po wyjściu z pętli wysyłamy true, co oznacza że w bazie pojawiła się nowa wiadomość.
    $data_klienta = intval($_GET['time']);
    $dbhost = "localhost";
    $dbuser = "u";
    $dbpass = "p";
    $dbname = "long_polling";
    $dbconn = @mysql_connect($dbhost,$dbuser,$dbpass);
    if($dbconn)
    {
        mysql_select_db($dbname,$dbconn);
        mysql_query("SET NAMES utf8");
        while(1)
        {
            $zap = mysql_query("SELECT MAX(data) FROM wiadomosci");
            if(empty($zap))
            {
                header("Content-Type: application/json");
                echo json_encode(false);
                exit;
            }
            $najnowsza_wiadomosc = strtotime(array_shift(mysql_fetch_array($zap)));
            if($najnowsza_wiadomosc > $data_klienta) break;
            sleep(1);
        }
        header("Content-Type: application/json");
        echo json_encode(true);
    }
    else
    {
        header("Content-Type: application/json");
        echo json_encode(false);
    }
Zajmijmy się kodem jQuery. W pliku custom.js stwórzmy funkcję long_poll() odpowiedzialną za mechanizm long pollingu po stronie javascriptu. Utwórzmy globalną zmienną, a następnie definicję funkcji.
var najnowsza_wiadomosc = 0;
function long_poll()
{
    $.ajax({
        type: "GET",
        url: "long_polling.php?time=" + najnowsza_wiadomosc,
        async: true,
        cache: false,
        timeout: 50000,
        success: function (data) {
            if (data === true)
            {
                aktualizuj_wiadomosci(long_poll);
            }
		else
		{
            long_poll();
		}
        },
        error: function (XMLHttpRequest, textStatus, errorThrown) {
            long_poll();
        }
    });
}
Cała funkcja jest żądaniem ajaxowym typu GET do pliku long_polling.php. Do tego pliku przekazujemy zmienną time ustawioną na najnowsza_wiadomosc. Ważne jest, aby ustawić w funkcji ajax cache: false oraz timeout. Jeśli żądanie zakończy się sukcesem i przekazane dane będą równe true aktualizujemy wiadomości (o tej funkcji za chwilę), w przeciwnym razie ponownie wywołujemy funkcję long_poll() - czekamy na kolejną wiadomość ;). Funkcją obsługującą błąd jest również long_poll(). Przedstawię teraz funkcję aktualizującą wiadomości. Składa się ona z ajaxowego zapytania get do pliku wiadomosci.php z przekazanym parametrem time (najnowsza_wiadomosc). Funkcja odbierająca dane najpierw sprawdza czy istnieją (instrukcja if), a następnie przetwarza otrzymane dane pętlą for. W pętli aktualizujemy zmienną najnowsza_wiadomosc jeśli jest ona mniejsza od daty otrzymanej (dane[w].data). Na koniec dodajemy do listy nowy element li składający się z treści wiadomości i odpowiednio przetworzonej daty wiadomości.
function aktualizuj_wiadomosci(callback)
{
    $.ajax({
        type: "GET",
        url: "wiadomosci.php?time=" + najnowsza_wiadomosc,
        async: true,
        success: function (data) {
            if (dane)
            {
                var l = dane.length, w, wiadomosci = $(document.getElementById('wiadomosci'));
                for (w = 0; w < l; w += 1)
                {
                    if (najnowsza_wiadomosc < dane[w].data)
                    {
                        najnowsza_wiadomosc = dane[w].data;
                    }
                    if (dane[w].tresc.length)
                    {
                        data = new Date(dane[w].data * 1000);
                        wiadomosci.append('<li>' + data.getDate() + '-' + (data.getMonth() + 1) + '-' + data.getFullYear() + ' ' + data.getHours() + ':' + data.getMinutes() + ', ' + dane[w].tresc + '</li>');
                    }
                }
            }
            if(callback) callback();
        },
        error: function() {
            if(callback) callback();
        }
    });
}
Wystarczy wywołać teraz funkcję long_poll() po załadowaniu strony. Gdy dodamy jakąś nową wiadomość do bazy pojawi się ona na stronie bez jej odświeżania.
$(document).ready(function () {
    long_poll();
});
Pozostało nam zaimplementowanie możliwości dodawania nowych wiadomości za pomocą przygotwanego wcześniej inputa i przycisku submit. Skrypt dodaj_wiadomosc.php jest bardzo prosty. Łączymy się z bazą i wstawiamy nowy wiersz - treść wiadomości ($_POST['tresc']) i aktualny czas (funkcją mysql NOW()).
$dbhost = "localhost";
$dbuser = "u";
$dbpass = "p";
$dbname = "long_polling";
$dbconn = @mysql_connect($dbhost,$dbuser,$dbpass);
if($dbconn)
{
    mysql_select_db($dbname,$dbconn);
    mysql_query("SET NAMES utf8");
    $t = mysql_real_escape_string($_POST['tresc']);
    mysql_query("INSERT INTO wiadomosci (data, tresc) VALUES (NOW(), '$t')");
}
Kod jQuery jest jeszcze prostszy. Funkcja dodaj_wiadomosc() wysyła do skryptu php, który utworzyliśmy przed chwilą treść wpisaną w polu input.
var add;
function dodaj_wiadomosc()
{
    $.post('dodaj_wiadomosc.php', { tresc: add.val() });
    add.val('');
}
Do kodu uruchamianego po załadowaniu strony należy dodać dwie linijki: określenie, że zmienna add przechowuje obiekt jQuery pola tekstowego oraz wykonanie funkcji dodaj_wiadomosc() po kliknięciu na przycisk submit.
$(document).ready(function () {
    add = $('#add');
    long_poll();
    $('#sub').click(dodaj_wiadomosc);
});