Mis à jour le vendredi 11 août 2017
  • 10 heures
  • Difficile

Ce cours est visible gratuitement en ligne.

Ce cours existe en livre papier.

Vous pouvez obtenir un certificat de réussite à l'issue de ce cours.

Vous pouvez être accompagné et mentoré par un professeur particulier par visioconférence sur ce cours.

J'ai tout compris !

socket.io : passez au temps réel !

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

socket.io est l'une des bibliothèques les plus prisées par ceux qui développent avec Node.js. Pourquoi ? Parce qu'elle permet de faire très simplement de la communication synchrone dans votre application, c'est-à-dire de la communication en temps réel !

Vous ne voyez pas ce que ça signifie ? Laissez-moi vous le dire autrement : socket.io vous permet par exemple de mettre en place très facilement un Chat sur votre site ! :)

Les possibilités que vous offre socket.io sont en réalité immenses et vont bien au-delà du Chat : tout ce qui nécessite une communication immédiate entre les visiteurs de votre site peut en bénéficier. Ca peut être par exemple une brique de base pour mettre en place un jeu où on voit en direct les personnages évoluer dans le navigateur, le tout sans avoir à recharger la page !

Ca donne envie, avouez ! :D

Que fait socket.io ?

Avant de commencer à coder, je voudrais vous présenter rapidement le principe de socket.io. C'est une bibliothèque qui nous simplifie beaucoup les choses, mais on pourrait croire à tort que c'est "de la magie". Or, socket.io se base sur plusieurs techniques différentes qui permettent la communication en temps réel (et qui pour certaines existent depuis des années !). La plus connue d'entre elles, et la plus récente, est WebSocket.

WebSocket ? Ce n'est pas une des nouveautés de HTML5 ?

C'est une nouveauté récente apparue plus ou moins en même temps que HTML5, mais ce n'est pas du HTML : c'est une API JavaScript.

WebSocket est une fonctionnalité supportée par l'ensemble des navigateurs récents. Elle permet un échange bilatéral synchrone entre le client et le serveur.

Comment ça je parle chinois ? Bon reprenons les bases alors ! Habituellement, sur le Web, la communication est asynchrone. Le Web a toujours été conçu comme ça : le client demande et le serveur répond.

Habituellement, la communication est asynchrone : le client demande, le serveur répond
Habituellement, la communication est asynchrone : le client demande, le serveur répond

C'était suffisant aux débuts du Web, mais c'est devenu trop limitant ces derniers temps. On a besoin d'une communication plus réactive et immédiate. Dans ce schéma par exemple, le serveur ne peut pas décider de lui-même d'envoyer quelque chose au client (par exemple pour l'avertir "eh il y a un nouveau message !"). Il faut que le client recharge la page ou fasse une action pour solliciter le serveur, car celui-ci n'a pas le droit de s'adresser au client tout seul.

WebSocket est une nouveauté du Web qui permet de laisser une sorte de "tuyau" de communication ouvert entre le client et le serveur. Le navigateur et le serveur restent connectés entre eux et peuvent s'échanger des messages dans un sens comme dans l'autre dans ce tuyau. Désormais, le serveur peut donc lui-même décider d'envoyer un message au client comme un grand !

Avec les WebSocket, la communication est synchrone : un tuyau de communication reste ouvert entre client et serveur
Avec les WebSocket, la communication est synchrone : un tuyau de communication reste ouvert entre client et serveur

socket.io nous permet d'utiliser les WebSockets très facilement. Et, comme tous les navigateurs ne gèrent pas WebSocket, il est capable d'utiliser d'autres techniques de communication synchrones si elles sont gérées par le navigateur du client. Quand on va sur le site de socket.io, section "Browser support", on voit que socket.io détermine pour chaque client quelle est la méthode de communication temps réel la plus adaptée pour le client :

  • WebSocket

  • Adobe Flash Socket

  • AJAX long polling

  • AJAX multipart streaming

  • Forever Iframe

  • JSONP Polling

Grâce à toutes ces différentes techniques de communication, socket.io supporte un très grand nombre de navigateurs, même anciens :

  • Internet Explorer 5.5+ (oui oui, vous avez bien lu !)

  • Safari 3+

  • Google Chrome 4+

  • Firefox 3+

  • Opera 10.61+

  • Safari sur iPhone et iPad

  • Le navigateur Android

Bon, maintenant qu'on sait un petit peu plus comment fonctionne socket.io, si on commençait à l'utiliser ? :D

Emettre et recevoir des messages avec socket.io

Parlons peu, mais parlons bien. Comment utiliser socket.io ?

Installer socket.io

La première étape, aussi évidente soit-elle, est d'installer socket.io. Ne rigolez pas, la première fois que j'ai voulu l'utiliser j'ai bien perdu 15 minutes avant de comprendre que j'avais oublié de faire un simple :

npm install socket.io

Voilà, je vous ai fait gagner 15 minutes de votre vie. Ne me remerciez pas c'est tout naturel.

Premier code : un client se connecte

Quand on utilise socket.io, on doit toujours s'occuper de deux fichiers en même temps :

  • Le fichier serveur (ex : app.js) : c'est lui qui centralise et gère les connexions des différents clients connectés au site.

  • Le fichier client (ex : index.html) : c'est lui qui se connecte au serveur et qui affiche les résultats dans le navigateur.

Le serveur (app.js)

J'ai volontairement séparé le code du serveur en deux parties : au début, on charge le serveur comme d'habitude (et on récupère et renvoie le contenu de la page index.html) ; ensuite, on charge socket.io et on gère les évènements de socket.io.

var http = require('http');
var fs = require('fs');

// Chargement du fichier index.html affiché au client
var server = http.createServer(function(req, res) {
    fs.readFile('./index.html', 'utf-8', function(error, content) {
        res.writeHead(200, {"Content-Type": "text/html"});
        res.end(content);
    });
});

// Chargement de socket.io
var io = require('socket.io').listen(server);

// Quand un client se connecte, on le note dans la console
io.sockets.on('connection', function (socket) {
    console.log('Un client est connecté !');
});


server.listen(8080);

Ce code fait 2 choses :

  • Il renvoie le fichier index.html quand un client demande à charger la page dans son navigateur.

  • Il se prépare à recevoir des requêtes via socket.io. Ici, on s'attend à recevoir un seul type de message : la connexion. Lorsqu'on se connecte via socket.io, on logge ici l'information dans la console.

Imaginez-vous en tant que visiteur. Vous ouvrez votre navigateur à l'adresse où se trouve votre application (http://localhost:8080 ici). On vous envoie le fichier index.html, la page se charge. Dans ce fichier, que nous allons voir juste après, un code JavaScript se connecte au serveur, cette fois pas en http mais via socket.io (donc via les WebSockets en général). Le client effectue donc 2 types de connexion :

  • Une connexion "classique" au serveur en HTTP pour charger la page index.html

  • Une connexion "temps réel" pour ouvrir un tunnel via les WebSockets grâce à socket.io

Le client (index.html)

Intéressons-nous maintenant au client. Le fichier index.html est envoyé par le serveur node.js. C'est un fichier HTML tout ce qu'il y a de plus classique, si ce n'est qu'il contient un peu de code JavaScript qui permettra ensuite de communiquer avec le serveur en temps réel via socket.io :

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>Socket.io</title>
    </head>
 
    <body>
        <h1>Communication avec socket.io !</h1>

        <script src="/socket.io/socket.io.js"></script>
        <script>
            var socket = io.connect('http://localhost:8080');
        </script>
    </body>
</html>

Dans un premier temps, on fait récupérer au client le fichier socket.io.js. Celui-ci est automatiquement fourni par le serveur node.js via le module socket.io (le chemin vers le fichier n'est donc pas choisi au hasard) :

<script src="/socket.io/socket.io.js"></script>

Le code qu'il contient permet de gérer la communication avec le serveur du côté du client, soit avec les WebSockets, soit avec l'une des autres méthodes dont je vous ai parlé si le navigateur ne les supporte pas.

Ensuite, nous pouvons effectuer des actions du côté du client pour communiquer avec le serveur. Pour le moment j'ai fait quelque chose de très simple : je me suis contenté de me connecter au serveur. Celui-ci se trouve sur ma machine, d'où l'adresse http://localhost:8080. Evidemment, sur le Web, il faudra adapter ce chemin pour indiquer l'adresse de votre site (ex : http://monsite.com).

var socket = io.connect('http://localhost:8080');
Testons le code !

Il nous suffit de lancer l'application :

node app.js

On peut alors se rendre avec notre navigateur à l'adresse où écoute Node.js : http://localhost:8080 dans mon cas.

Une page basique va se charger. Votre ordinateur va ensuite ouvrir une connexion avec socket.io et le serveur devrait afficher des informations de débogage dans la console :

$ node app.js
   info  - socket.io started
   debug - client authorized
   info  - handshake authorized Z2E7aqIvOPPqv_XBn421
   debug - setting request GET /socket.io/1/websocket/Z2E7aqIvOPPqv_XBn421
   debug - set heartbeat interval for client Z2E7aqIvOPPqv_XBn421
   debug - client authorized for 
   debug - websocket writing 1::
Un client est connecté !

Super ! Ca veut dire que notre code fonctionne. :)

Pour le moment il ne fait rien de bien extraordinaire, mais nous avons les bases. Ca va être le moment de s'amuser à échanger des messages avec le serveur !

Envoi et réception de messages

Maintenant que le client est connecté, on peut échanger des messages entre le client et le serveur. Il y a 2 cas de figure :

  • Le serveur veut envoyer un message au client

  • Le client veut envoyer un message au serveur

Le serveur veut envoyer un message au client

Je propose que le serveur envoie un message au client lorsqu'il vient de se connecter, pour lui confirmer que la connexion a bien fonctionné. Rajoutez ceci au fichier app.js :

io.sockets.on('connection', function (socket) {
        socket.emit('message', 'Vous êtes bien connecté !');
});

Lorsqu'on détecte une connexion, on émet un message au client avec socket.emit(). La fonction prend 2 paramètres :

  • Le type de message qu'on veut transmettre. Ici, mon message est de type "message" (je ne suis pas très original, je sais). Cela vous permettra de distinguer les différents types de message. Par exemple dans un jeu, on pourrait envoyer des messages de type "deplacement_joueur", "attaque_joueur"...

  • Le contenu du message. Là vous pouvez transmettre ce que vous voulez.

Du côté du fichier index.html (le client), on va écouter l'arrivée de messages de type "message" :

<script>
    var socket = io.connect('http://localhost:8080');
    socket.on('message', function(message) {
        alert('Le serveur a un message pour vous : ' + message);
    })
</script>

Avec socket.on(), on écoute les messages de type "message". Lorsque des messages arrivent, on appelle la fonction de callback qui, ici, affiche simplement une boîte de dialogue.

Essayez, vous verrez que lorsque vous chargez la page index.html, une boîte de dialogue s'affiche indiquant que la connexion a réussi !

Le client affiche le message du serveur dans une boîte de dialogue
Le client affiche le message du serveur dans une boîte de dialogue
Le client veut envoyer un message au serveur

Maintenant, faisons l'inverse. Je vous propose d'ajouter un bouton dans la page web et d'envoyer un message au serveur lorsqu'on clique dessus.

Du côté du client (index.html), je vais rajouter un bouton "Embêter le serveur". Lorsqu'on cliquera dessus, j'émettrai un message au serveur. Voici le code complet :

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>Socket.io</title>
    </head>
 
    <body>
        <h1>Communication avec socket.io !</h1>

        <p><input type="button" value="Embêter le serveur" id="poke" /></p>


        <script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
        <script src="/socket.io/socket.io.js"></script>
        <script>
            var socket = io.connect('http://localhost:8080');
            socket.on('message', function(message) {
                alert('Le serveur a un message pour vous : ' + message);
            })

            $('#poke').click(function () {
                socket.emit('message', 'Salut serveur, ça va ?');
            })
        </script>
    </body>
</html>

Finalement, le seul code nouveau et intéressant ici est :

$('#poke').click(function () {
    socket.emit('message', 'Salut serveur, ça va ?');
})

Il est très simple. Lorsqu'on clique sur le bouton, on envoie un message de type "message" au serveur, assorti d'un contenu.

Si on veut récupérer ça du côté du serveur maintenant, il va nous falloir ajouter l'écoute des messages de type "message" dans la fonction de callback de la connexion :

io.sockets.on('connection', function (socket) {
    socket.emit('message', 'Vous êtes bien connecté !');

    // Quand le serveur reçoit un signal de type "message" du client    
    socket.on('message', function (message) {
        console.log('Un client me parle ! Il me dit : ' + message);
    });	
});

Lancez le code ! Cliquez sur le bouton "Embêter le serveur" dans la page et regardez dans la console du serveur. Vous devriez voir apparaître :

Un client me parle ! Il me dit : Salut serveur, ça va ?
Lorsque le client clique sur le bouton, le serveur réagit immédiatement dans la console
Lorsque le client clique sur le bouton, le serveur réagit immédiatement dans la console

Communiquer avec plusieurs clients

Dans tous nos exemples précédents, on a travaillé avec un serveur et un seul client. Dans la pratique, vous aurez sûrement plusieurs clients connectés à votre application Node.js (en tout cas je vous le souhaite !). Pour simuler ça en local, c'est très simple : il suffit d'ouvrir deux onglets, chacun sur votre page http://localhost:8080. Le serveur verra 2 clients différents connectés.

Quand on a plusieurs clients, il faut être capable :

  • D'envoyer des messages à tout le monde d'un seul coup. On appelle ça les broadcasts.

  • De se souvenir d'informations sur chaque client (comme son pseudo par exemple). On a besoin pour ça de variables de session.

Ô surprise, c'est justement ce que je comptais vous expliquer maintenant !

Envoyer un message à tous les clients (broadcast)

Quand vous faites un socket.emit() du côté du serveur, vous envoyez uniquement un message au client avec qui vous êtes en train de discuter. Mais vous pouvez faire plus fort : vous pouvez envoyer un broadcast, c'est-à-dire un message destiné à tous les autres clients (excepté celui qui vient de solliciter le serveur).

Prenons un cas :

  1. Le client A envoie un message au serveur

  2. Le serveur l'analyse

  3. Il décide de broadcaster ce message pour l'envoyer aux autres clients connectés : B et C

Dans un broadcast, le serveur envoie un message à tous les autres clients connectés
Dans un broadcast, le serveur envoie un message à tous les autres clients connectés

Imaginez par exemple un Chat. Le client A écrit un message et l'envoie au serveur. Pour que les autres clients voient ce message, il doit le leur broadcaster.

... quoi, comment on fait ce truc magique ? Oh, rien de plus simple !

socket.broadcast.emit('message', 'Message à toutes les unités. Je répète, message à toutes les unités.');

Il suffit de faire un socket.broadcast.emit() et le message partira à tous les autres clients connectés. Ajoutez par exemple un broadcast dans app.js lors de la connexion d'un client :

io.sockets.on('connection', function (socket) {
	socket.emit('message', 'Vous êtes bien connecté !');
	socket.broadcast.emit('message', 'Un autre client vient de se connecter !');

	socket.on('message', function (message) {
		console.log('Un client me parle ! Il me dit : ' + message);
	});	
});

Essayez maintenant d'ouvrir 2 onglets (ou plus) sur votre page http://localhost:8080. Vous verrez que lorsqu'un nouveau client arrive, les autres pages réagissent instantanément pour dire : "Un autre client vient de se connecter" !

Les variables de session

Lorsque vous aurez plusieurs clients connectés, vous allez vite vous rendre compte qu'il est délicat de les reconnaître. L'idéal serait de pouvoir mémoriser des informations sur chaque client sous forme de variables de session... mais par défaut socket.io ne propose pas cette fonctionnalité.

En fait, les variables de sessions doivent être gérées par une bibliothèque supplémentaire sous forme de middleware comme session.socket.io (c'est le même principe de middlewares qu'Express, on peut voir ça comme des plugins).

Si je devais vous expliquer maintenant comment utiliser un middleware de gestion des sessions, il me faudrait probablement un bon chapitre entier. Afin de vous épargner ça pour le moment, je vais vous proposer une astuce de sioux : enregistrer directement l'information sous forme de variable dans l'objet socket de chaque client. Ca aura le mérite d'être simple à mettre en place.

On veut donc demander au serveur de retenir des informations sur chaque client connecté. Comme ça, le client n'aura pas besoin de rappeler qui il est à chaque fois qu'il envoie un message !

Pour stocker une variable de session côté serveur, on va écrire :

socket.mavariable = mavariable;

Dans cet exemple, on stocke les données sous forme de variable dans l'objet socket correspondant au client (je vous rappelle qu'il y a 1 objet socket en mémoire sur le serveur pour chaque client).

Pour récupérer cette information ensuite, il suffira de demander ce que contient socket.mavariable :

console.log(socket.mavariable);

Super simple non ? Alors essayons d'imaginer un cas pratique. Lorsqu'un client se connecte, la page web va lui demander son pseudo. Le serveur stockera le pseudo en variable de session pour s'en souvenir lorsque le client cliquera sur "Embêter le serveur".

Voyons les modifications que nous devons faire...

La page web (index.html) émet un signal contenant le pseudo

Au chargement de la page web, on va demander le pseudo du visiteur. On envoie ce pseudo au serveur via un signal de type "petit_nouveau" (je l'ai appelé comme ça pour le différencier des signaux de type "message"). Ce signal contient le pseudo du visiteur :

var pseudo = prompt('Quel est votre pseudo ?');
socket.emit('petit_nouveau', pseudo);
Le serveur (app.js) stocke le pseudo

Le serveur doit récupérer ce signal. On écoute les signaux de type "petit_nouveau" et, quand on en reçoit, on sauvegarde le pseudo en variable de session :

socket.on('petit_nouveau', function(pseudo) {
    socket.pseudo = pseudo;
});
Le serveur (app.js) se rappelle du pseudo quand on lui envoie un message

Maintenant, on veut que le serveur se souvienne de nous lorsqu'on l'embête en cliquant sur "Embêter le serveur" (ce qui provoque l'envoi d'un signal de type "message"). On va compléter la fonction de callback qui est appelée quand le serveur reçoit un "message" :

socket.on('message', function (message) {
    console.log(socket.pseudo + ' me parle ! Il me dit : ' + message);
});

Dès qu'on reçoit un "message", on récupère simplement la variable de session "pseudo" dans la socket du client.

Tester le code

Essayez d'ouvrir 2 fenêtres en donnant un pseudo différent à chaque fois. Cliquez ensuite sur "Embêter le serveur". Vous verrez dans la console que le pseudo de la personne qui a cliqué sur le bouton apparaît !

J'ai fait un essai chez moi avec deux fenêtres, l'une avec le pseudo "mateo21" et l'autre avec le pseudo "robert". Vous voyez en bas de la console que celle-ci reconnaît bien qui vient de cliquer !

Plusieurs clients connectés : le serveur se souvient de leurs noms !
Plusieurs clients connectés : le serveur se souvient de leurs noms !
Le code complet

Je vous ai volontairement montré des bouts de code très courts pour vous expliquer le principe, mais je suis sûr que vous mourez d'envie d'avoir le code complet pour faire vos essais. ;)

Alors allons-y ! Voici index.html :

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>Socket.io</title>
    </head>
 
    <body>
        <h1>Communication avec socket.io !</h1>

        <p><input type="button" value="Embêter le serveur" id="poke" /></p>


        <script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
        <script src="/socket.io/socket.io.js"></script>
        <script>
            var socket = io.connect('http://localhost:8080');

            // On demande le pseudo au visiteur...
            var pseudo = prompt('Quel est votre pseudo ?');
            // Et on l'envoie avec le signal "petit_nouveau" (pour le différencier de "message")
            socket.emit('petit_nouveau', pseudo);

            // On affiche une boîte de dialogue quand le serveur nous envoie un "message"
            socket.on('message', function(message) {
                alert('Le serveur a un message pour vous : ' + message);
            })

            // Lorsqu'on clique sur le bouton, on envoie un "message" au serveur
            $('#poke').click(function () {
                socket.emit('message', 'Salut serveur, ça va ?');
            })
        </script>
    </body>
</html>

... et voici l'application serveur app.js :

var http = require('http');
var fs = require('fs');

// Chargement du fichier index.html affiché au client
var server = http.createServer(function(req, res) {
    fs.readFile('./index.html', 'utf-8', function(error, content) {
        res.writeHead(200, {"Content-Type": "text/html"});
        res.end(content);
    });
});

// Chargement de socket.io
var io = require('socket.io').listen(server);

io.sockets.on('connection', function (socket, pseudo) {
    // Quand un client se connecte, on lui envoie un message
    socket.emit('message', 'Vous êtes bien connecté !');
    // On signale aux autres clients qu'il y a un nouveau venu
    socket.broadcast.emit('message', 'Un autre client vient de se connecter ! ');

    // Dès qu'on nous donne un pseudo, on le stocke en variable de session
    socket.on('petit_nouveau', function(pseudo) {
        socket.pseudo = pseudo;
    });

    // Dès qu'on reçoit un "message" (clic sur le bouton), on le note dans la console
    socket.on('message', function (message) {
        // On récupère le pseudo de celui qui a cliqué dans les variables de session
        console.log(socket.pseudo + ' me parle ! Il me dit : ' + message);
    }); 
});


server.listen(8080);

J'espère avoir suffisamment commenté pour que vous puissiez vous y retrouver. ;)

Exemple de certificat de réussite
Exemple de certificat de réussite