Passez à l'échange de données en temps réel avec WebSockets

Passez à l'échange de données en temps réel avec WebSockets WebSocket permet une communication bidirectionnelle full-duplex entre client et serveur. Voici comment l'implémenter. Des bonnes feuilles issues de l'ouvrage "HTML5 et PHP 5" chez Eni.

Avec l'avènement des RAI et plus généralement de par le besoin croissant d'interactivité dans les applications et les sites web, le mode de communication HTTP classique a montré ses limites en termes d'interactions client-serveur. AJAX a permis quelques améliorations en ce qui concerne l'interactivité, en permettant des requêtes et un rafraîchissement "à la volée" du contenu d'une page, mais en s'appuyant sur un mode de requêtage traditionnel. Les échanges se font toujours de manière unidirectionnelle : la requête est initiée par le client, le serveur fournit une réponse, le client l'affiche.

Depuis 2011, l'IETF a publié le protocole WebSocket et le W3C travaille sur la standardisation de son API, publiée pour l'instant à l'état de brouillon, mais d'ores et déjà implémentée par tous les navigateurs récents. Le protocole TCP WebSocket permet une communication bidirectionnelle full-duplex entre un client et un serveur, c'est-à-dire qu'un client connecté à un serveur WebSocket peut non seulement lui envoyer des messages, mais aussi en recevoir sans qu'il n'ait eu à envoyer une requête (c'est ce qu'on appelle le "push de données"). La connexion n'est plus établie à chaque requête comme dans un échange HTTP classique, mais de manière persistante, au cours de laquelle un certain nombre de messages sont échangés.

Principe de fonctionnement

La connexion est initiée à la demande du client par une demande de handshake en HTTP, en demandant un upgrade de la connexion HTTP en WebSocket. Le serveur, s'il le supporte et s'il l'accepte, répond à la demande de handshake.

Exemple de requête HTTP client :

Request URL:ws://127.0.0.1:12345/ws.php
Request Method:GET
Status Code:101 Websocket Protocol Handshake
Connection:Upgrade
Host:127.0.0.1:12345
Origin:http://127.0.0.1
Sec-WebSocket-Extensions:permessage-deflate;client_max_window_bits,
x-webkit-deflate-frame
Sec-WebSocket-Key:1TKpkaSlXtzrBQTWX72AWg==
Sec-WebSocket-Version:13
Upgrade:websocket

Exemple de réponse :

HTTP/1.1 101 WebSocket Protocol Handshake
Connection:Upgrade
Sec-WebSocket-Accept:RJUpcCEEcOr+Cqxhg3mcxgXxo/g=
Sec-WebSocket-Origin:http://127.0.0.1
Upgrade:WebSocket


À partir de là, la communication entre le client et le serveur se fera en full-duplex sur le protocole WebSocket.


4.1 Implémentation du client

L'implémentation en JavaScript pour la prise en charge d'une connexion WebSocket est assez simple. Elle se base sur l'objet WebSocket (ou MozWebSocket).

Instanciation de l'objet WebSocket

Le constructeur de l'objet WebSocket prend deux paramètres :

- L'URL du service, composée de :
- Le protocole : ws:// ou wss:// pour une connexion sécurisée.
- L'IP ou le nom de l'hôte.
- Le port d'écoute du serveur.
- La page qui prend en charge le service WebSocket.
- Le sous-protocole utilisé (facultatif).

Exemple :

var ws = new WebSocket("ws://127.0.0.1:8004/ws.php");


Méthodes de l'objet WebSocket

- send (msg) : envoie un message textuel au serveur. Pour envoyer des données structurées au serveur, on les encodera au préalable en JSON par exemple.

- close : ferme la connexion au serveur.

Attributs de l'objet WebSocket
- readyState : indique l'état de la connexion au serveur :
- 0 : connexion en cours.
- 1 : connexion établie.
- 2 : en cours de fermeture.
- 3 : connexion fermée.
- bufferedAmount : indique le nombre d'octets passés par la méthode send qui n'ont pas encore été envoyés au serveur.
- protocol : indique le sous-protocole employé pour l'échange de données.

Évènements de l'objet WebSocket

- onopen : déclenché une fois la connexion établie.
- onmessage : déclenché à la réception d'un message.
- onerror : déclenché lors de l'apparition d'une erreur.
- onclose : déclenché une fois la connexion fermée.

Exemple simple :

L'exemple suivant illustre une connexion WebSocket. Un div affichera l'état de la connexion et les éventuelles erreurs, et un autre affichera les messages provenant du serveur. Enfin, une zone de texte permettra de saisir un message et de l'envoyer au serveur.

Code HTML

<div id="etat"></div>
<div id="messageIn"></div>

<input type="text" id="messageOut">
<button id="btnSend">Envoyer</button

Initialisation de la connexion

var ws;

function initConnection(){
  if (window.MozWebSocket){
    window.WebSocket = window.MozWebSocket;
  }
  if (!window.WebSocket){
    alert("Votre navigateur ne prend pas en charge les websocket");
    return false;
  }
 
  ws = new WebSocket("ws://127.0.0.1:8004/ws.php"); 
  return true;
}


La fonction initConnection vérifie la présence de l'objet WebSocket (pour prendre en charge le navigateur Firefox, on doit également tester MozWebSocket). Dans le cas où le navigateur le supporte, on crée une connexion WebSocket.

Prise en charge des évènements de l'objet WebSocket

ws.onerror = function() { $('#etat').html('Une erreur est
survenue'); }
ws.onopen = function() { $('#etat').html('Connexion établie'); }
ws.onclose = function() { $('#etat').html('Connexion fermée'); }
ws.onmessage = function(msg){$('#messageIn').html(msg.data);}

Selon l'évènement, on affiche le message soit dans le bloc etat, soit dans le bloc messageIn.

Envoi de messages

Enfin, on implémente l'envoi de messages au serveur sur le clic du bouton :

$('#btnSend').click(function(){
   ws.send(document.querySelector('#messageOut').value);
})


Au clic du bouton, un message sera envoyé au serveur. En retour et s'il l'a compris, il renverra à son tour un message qui sera affiché dans le bloc #messageIn.


4.2 Implémentation d'un serveur WebSocket en PHP

4.2.1 Principe général

L'implémentation d'un serveur WebSocket en PHP est un peu plus complexe que celle d'un client. Le serveur WebSocket doit assurer les tâches suivantes :

- initialiser un socket maître avec lequel dialogueront les clients.
- écouter un port particulier de manière continue (daemon).
- prendre en charge et répondre aux demandes de handshake.
- prendre en charge les messages des clients.
- assurer un traitement particulier en fonction du message reçu.
- envoyer des messages aux clients.

4.2.2 Processus d'écoute et d'échange avec les clients

La mise en place d'une écoute suit un principe général dont voici les fonctions essentielles.

Au préalable, initialiser un socket maître : socket_create

Le socket maître doit être créé avant toute autre chose car c'est lui qui va écouter et recevoir les demandes de connexion des clients. Il est créé par la méthode socket_create puis associé à une adresse et un port par la méthode socket_bind.

Exemple :

$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_bind($socket, "127.0.0.1", 12345);

La mise en écoute : socket_listen

La méthode socket_listen permet de mettre le socket maître en attente de connexions entrantes. Elle prend en paramètre :

- socket : le socket à mettre en attente de connexions.
- backlog : nombre maximum de connexions à empiler dans la queue d'attente.

Dès qu'une demande de connexion est formulée sur le socket en écoute, celui-ci sera retourné par la fonction socket_select (ci-dessous).

Surveiller les sockets : socket_select

La méthode socket_select est à la base du processus d'écoute. Elle surveille un tableau de sockets en lecture, écriture ou exception et renvoie la liste des sockets mis à jour en cas de changement d'état d'un ou plusieurs d'entre eux. Elle prend en paramètre (références) les tableaux des sockets à surveiller (lecture/écriture/exception) et renvoie ces tableaux mis à jour.

Exemple :

$changed = $mesSockets;
$write = $except = [];
socket_select($changed, $write, $except, null);

On fournit à la méthode socket_select la liste des sockets à surveiller (en lecture seulement, les autres paramètres sont vides). Si l'un (ou plusieurs) d'entre eux change d'état, la fonction retournera le tableau $changed contenant le socket en question. De là, on récupérera les données de ce socket par la méthode socket_recv.

Établissement d'une nouvelle connexion : socket_accept

La méthode socket_accept permet d'accepter une demande de connexion d'un client formulée sur le socket maître passé en paramètre. Elle crée un nouveau socket (client) et le retourne.

Le handshake

Il n'existe pas de méthode particulière de gestion du handshake car il s'agit d'un simple échange HTTP. Le client envoie une requête de demande d'upgrade que reçoit le démon. Le serveur doit analyser l'en-tête HTTP et renvoyer l'acceptation du handshake par le biais de socket_write (voir exemples suivants).

Réception des messages : socket_recv

La méthode socket_recv lit les dernières données envoyées par un socket donné en paramètre. Cette fonction prend en paramètres :

- socket : le socket duquel lire les données.
- buffer (référence) : buffer dans lequel seront placées les données.
- length : nombre d'octets à placer dans le buffer.
- flags : options.

Exemple :

$bytes = socket_recv($socket, $buffer, 2048, 0);

Envoi d'un message : socket_write

La méthode socket_write permet d'écrire un message dans un socket. Elle prend en paramètre :

- socket : socket à utiliser.
- buffer : message à envoyer.
- length (optionnel) : longueur du message à envoyer.

Exemple :

$msg = "Hello";
socket_write($client, $msg, strlen($msg);

Fermeture d'un socket : socket_close

La méthode socket_close ferme un socket ouvert. Elle prend en paramètre le socket à fermer.

Exemple :

socket_close($socket);


Cet article est constitué à partir de bonnes feuilles issues de l'ouvrage "HTML5 et PHP 5 - Développez des applications web performantes" d'Yves Gautheron, publié chez Eni.