Serveur Comet ou Long polling avec Ajax

13/12/2009

Long polling en PHP

Le long polling est une méthode simple et puissante pour permettre une synchronisation de contenu d’une page web avec des évènements au serveur. La méthode que je vais vous proposer associe PHP et Javascript (via Ajax). Mais avant tout je vais vous expliquer la différence fondamentale entre le “ping ajax”, le service “comet” et le “long polling”.

Ajax Ping

Jusqu’à présent vous aviez une méthode sympathique pour envoyer des données à un serveur et récupérer une réponse à afficher dans la page web sans recharger la page: Ajax. Cette méthode utilise une méthode asynchrone en deux temps: envois d’un requête au serveur et traitement de la réponse.

Cette méthode est pratique, facile, et surtout elle est éprouvée. Sauf que sa capacité s’arrête à ça… c’est le navigateur qui envoit une requête pour avoir une réponse.

Imaginons que nous voulons coder une petit application web qui nous envois des messages du serveur, on va lire ces données depuis un fichier. Voilà comment faire en PHP, créez le script //messages.php//: <?php print file_get_contents('messages.txt');

Très bien. En appelant la page http://votre-server/messages.php on voit le contenu de messages.txt. Reste à le faire avec Ajax. Pour simplifier un peu le code, je passe par Mootools. Vous pourrez le faire avec JQuery si vous le souhaitez, cela ne changera rien.

On va placer un “div” dont l’id sera “handler-msg”, puis on placera ce script: new Request({ url: 'messages.php', update: 'handler-msg' }).send()

Jusque là pas de souci, la requête ajax est envoyé et on écrit bien le contenu du fichier dans notre div. On aimerai ne pas recharger la page pour voir le contenu modifié, donc on va regarder toutes les secondes ce que nous retourne le script. On va plutôt faire ceci: function checkConnected(){ new Request({ url: 'connected.php', update: 'handler-msg' }).send() } checkConnected.periodical(1000); </script>

Ici je fais appel à ma fonction toutes les secondes (1000 ms). Pas de soucis ça fonctionne. Mais voilà… c’est un véritable système de ping. Cela veut dire qu’il faut interroger régulièrement le serveur pour avoir une réponse… même si la réponse est la même que précédemment.

Et cela implique que même si le fichier n’a pas été modifié sur le serveur, on devra revérifier toutes les secondes. Soit 60 requêtes par secondes, donc 60 fois le chargement d’un thread Apache + modules (PHP, crypt, etc…)

Et encore ici je le fais toutes les secondes… imaginez si je veux avoir une réponse plus fiable, toutes les 100ms par exemple… 600 requête en une minutes…

Long Polling

Ok, c’est là qu’il faut penser à avoir des réponses en temps réél. Et heureusement nous avons des solutions plus puissantes. Le service “comet” et le “long polling”.

A la différence d’une requête périodique ajax nous allons en faire une seule, le serveur restera connecté avec le navigateur et nous aurons les résultats en temps réel (ou presque).

Que ce soit “comet” ou “long polling”, nous allons attendre que le serveur daigne nous envoyer une réponse, et si ce n’est pas le cas et bien on attend. Mais la connexion ne sera pas fermée.

La seule vraie grande différence réside dans l’action qui suit un évènement du serveur envoyé à nos navigateurs. La méthode “comet” ne relâche pas la connexion et continue d’attendre de nouvelles instructions, alors que la méthode de “long polling” va traiter l’évènement et relancer une connexion au serveur.

Il est bien difficile de monter un client comet en Javascript parce que les évènements Ajax ne sont valides qu’après finalisation de la requête HTTP. Par contre, un long polling est simple comme tout…

Commençons par le serveur. Un script PHP va permettre de se mettre en attente et de ne fournir une réponse que quand cela nous intéresse. Ici c’est quand le fichier a été modifié que nous devons prévenir nos clients. On utilisera “filemtime” qui nous donnera le timestamp de modification du fichier (l’heure depuis le 01-01-1970 en secondes). Il faudra aussi penser à vider le cache de stats avec //clearstatcache()//. Enfin, il faudra savoir quel est la dernière heure de lecture du client.

Là où PHP nous aide, c’est d’une part par sa gestion de session, d’autre part par ses fonctions d’attente et enfin par ses fonctions de tampon (buffer).

Voici donc un service rapidement fait: ` session_start(); set_time_limit(0); //hop, plus de timeout après 60 secondes de travail if (!isset($_SESSION[‘lasttime’])) { $_SESSION[‘lasttime’]=0; } //on fait en sorte que le navigateur ne gère pas le cache header(‘Cache-Control: no-cache, must-revalidate’); header(‘Expires: Mon, 01 Jul 1999 00:00:00 GMT’);

//on ne bufferise pas, ou du moins on s’assure d’écrire //directement dans la sortie ob_implicit_flush(true);

//boucle quasi infinie while (1){ clearstatcache(); $last_modif = filemtime(‘messages;txt’); if($_SESSION[‘lasttime’] != $last_modif) {

    //le fichier a été modifié depuis la dernière heure retenu du client,
    //on enregistre la nouvelle heure en session
    $_SESSION['lasttime'] = $last_modif;

    //on lui envoit le nouveau contenu
    print file_get_contents('messages.txt');
    ob_flush();
    flush();

    //et on coupe
    exit;
}
//pas de modification ? on va attendre 10ms et on reteste
usleep(10000);

}

`

Très bien, maintenant coté client, il faut: -ouvrir une connexion au serveur sur messages.php -attendre une réponse -si réponse, -on change le contenu de la div handler-msg -on recharge une connexion au serveur

` function checkConnected(){ new Request({ url: ‘connected.php’, onSuccess: function (response) { //ici la connexion est coupée, on a reçut une réponse $(‘handler-msg’).set(‘html’,response); //on envoit le contenu de la réponse dans la div checkConnected(); //et on se reconnecte } }).send() }

//on appelle une première fois notre script checkConnected() `

Et voilà ! Reste à tester, ouvrez la page qui contient la div + le script. Puis amusez vous à modifier le fichier message.txt sur le serveur. Vous allez voir en temps réél (sitôt que message.txt est enregistré) sur la page Web.

Petite observation, webkit est un peu capricieux et ne commence à traiter des données que si vous avez reçut au moins 256 octets de données. Ainsi, vous devez envoyer 256 espaces (parce que c’est pas gênant), depuis messages.php. Voici le code:

` session_start(); set_time_limit(0); //hop, plus de timeout après 60 secondes de travail if (!isset($_SESSION[‘lasttime’])) { $_SESSION[‘lasttime’]=0; } //on fait en sorte que le navigateur ne gère pas le cache header(‘Cache-Control: no-cache, must-revalidate’); header(‘Expires: Mon, 01 Jul 1999 00:00:00 GMT’);

//pour webkit, on envoit un minimum de données print str_repeat(’ ‘,256);

//on ne bufferise pas, ou du moins on s’assure d’écrire //directement dans la sortie ob_implicit_flush(true);

//boucle quasi infinie while (1){ clearstatcache(); $last_modif = filemtime(‘messages;txt’); if($_SESSION[‘lasttime’] != $last_modif) {

    //le fichier a été modifié depuis la dernière heure retenu du client,
    //on enregistre la nouvelle heure en session
    $_SESSION['lasttime'] = $last_modif;

    //on lui envoit le nouveau contenu
    print file_get_contents('messages.txt');
    ob_flush();
    flush();

    //et on coupe
    exit;
}
//pas de modification ? on va attendre 10ms et on reteste
usleep(10000);

}

`

Il se peut aussi que le temps de relâchement Ajax fasse planter la boucle de reconnexion. Vous pouvez laisser un delais de reconnexion au serveur (ce qui en plus soulagera un peu le processus) de cette manière (encore avec Mootools):

` function checkConnected(){ new Request({ url: ‘connected.php’, onSuccess: function (response) { //ici la connexion est coupée, on a reçut une réponse $(‘handler-msg’).set(‘html’,response); //on envoit le contenu de la réponse dans la div checkConnected.delay(100); //et on se reconnecte après 100ms } }).send() }

//on appelle une première fois notre script checkConnected() `

Cela laisse 100ms de battement avant reconnexion. Sur mon Firefox 3.5.5 ce délai est obligatoire sous peine de crash de script. Par contre Chromium et konqueror n’ont pas rechigné…

Ce que vous allez pouvoir faire avec le long polling est un t’chat, surveillance de RSS, d’état de serveur… cette technique est simple et permet pas mal de folies.

Par contre, pensez bien que comme la connexion est restée active, vous allez avoir un nombre important de connexions ouvertes en même temps. C’est l’un des inconvénients de cette méthode. Par contre, paradoxalement, c’est peut-être dans votre cas plus intéressant de laisser des connexion ouverte plutôt que de recevoir trop de requêtes par secondes… tout dépend de l’application et du besoin.

Enfin voilà, je vous ai montré un peu le chemin mais il est évident que vous devrez travailler un peu la méthode pour la rendre plus propre, par exemple en utilisant JSON pour les requêtes et en utilisant non pas un fichier texte mais une base de données…

Si vraiment vous avez besoin d’un service plus poussée, allez voir du coté de “APE - Ajax Push Engine” qui fonctionne relativement bien.

Ça peut vous intéresser aussi


Jquery vs Mootools

Je suis en proie à Jquery depuis quelques semaines que ...


JQuery toujours pas pour moi

Suite à un vieux post qui s’est terminé par ...


Graph avec Mootools

J’ai passé des plombes dessus, mais j’y suis ...


Streaming, mais pourquoi

Lorsque l’on parle de “streaming” arrive toujours l’...

Merci de m'aider à financer mes services

Si vous avez apprécié cet article, je vous serai reconnaissant de m'aider à me payer une petite bière :)

Si vous voulez en savoir plus sur l'utilisation de flattr sur mon blog, lisez cette page: Ayez pitié de moi

Commentaires

Ajouter un commentaire

frinux - 14/12/2009

Est-ce que c’ets la même méthode qui est utilisée pour Google Wave ?

Metal3d - 14/12/2009

Tout à fait, c’est la même méthode. A savoir que bientôt on utilisera plutôt les websockets HTML 5 (dixit mes collègues aux taff)

Bref, l’article présente un moyen pas trop long à mettre en place, et rapidement utilisable. Pour des cas plus complexes et avec des demandes de ressources plus poussées, on utilisera plutôt des technos prévues à cet effet.

georgette - 07/10/2010

Bonjour,

Merci pour ce tutoriel fort intéressant, néanmoins, je n’ai pas tout compris sur l’intérêt de cette fonction : ob_implicit_flush(true) ainsi que de celles ci pour vider le tampon : ob_flush(); flush();

si on les enlève, le script marche quand même ?

Cordialement

ben - 10/07/2011

Bonjour, merci pour ces explications.

Encore plus simple que : print file_get_contents(‘messages.txt’); il y a : readfile(‘messages.txt’);

Cordialement

jizrell - 15/05/2013

bonjour et deja un grand merci pour ce tuto qui me sera d’une tres grande aide. Mais je rencontre un pb dans la mise en oeuvre. je suis sous ubuntu j’ai xampp donc j’ai crée trois fichiers dons mon repertoire test (connected.php, messages.php , et messages.txt ) mais quand je lance connected.php j’ai une erreure php:<> donc je ne comprend pas. merci de m’éclairer.

Ajouter un commentaire

(*) Votre e-mail ne sera ni revendu, ni rendu public, ni utilisé pour vous proposer des mails commerciaux. Il n'est utilisé que pour vous contacter en cas de souci avec le contenu du commentaire, ou pour vous prévenir d'un nouveau commentaire si vous avez coché la case prévue à cet effet.