Serveur WebSocket

Administration con centré . com !

WebSocket

WebSockets

Les WebSockets sont une technologie de communication bidirectionnelle en temps réel qui permet d'établir une connexion continue entre un client (navigateur web, application mobile) et un serveur. Contrairement au modèle classique de communication HTTP, où le client doit envoyer une requête pour obtenir une réponse du serveur, les WebSockets permettent au serveur d'envoyer des données au client à tout moment, sans attendre une requête.

Principe des WebSockets

Le fonctionnement des WebSockets repose sur les étapes suivantes :

  • Ouverture de la connexion : Lorsqu'un client souhaite communiquer avec le serveur, il initie une connexion WebSocket. Cette connexion commence par une requête HTTP spéciale, appelée "handshake", qui passe ensuite à un protocole de communication WebSocket une fois établie.
  • Communication bidirectionnelle : Une fois la connexion établie, les deux parties peuvent envoyer et recevoir des messages en temps réel. Contrairement aux requêtes HTTP classiques, il n'est pas nécessaire de rétablir la connexion pour chaque message, ce qui rend les WebSockets beaucoup plus performants pour les communications fréquentes.
  • Fermeture de la connexion : Lorsque le client ou le serveur décide de terminer la communication, la connexion WebSocket est fermée proprement, libérant les ressources associées.

Fonctionnalités possibles avec les WebSockets

Les WebSockets offrent un large éventail de possibilités pour des applications interactives et en temps réel.

  • Messagerie instantanée et chat en temps réel : Les WebSockets sont parfaits pour les applications de chat où les messages doivent être transmis et affichés instantanément aux utilisateurs.
  • Notifications en temps réel : Les notifications push peuvent être envoyées par le serveur aux utilisateurs pour les informer d'événements importants (mises à jour, alertes, nouveaux messages, etc.).
  • Jeux en ligne multijoueurs : Les jeux nécessitant une synchronisation rapide entre les actions des joueurs peuvent utiliser les WebSockets pour une communication efficace en temps réel.
  • Tableaux de bord et monitoring : Les WebSockets permettent d'afficher en direct les données d'un serveur, comme les métriques système ou les statistiques d'utilisation, avec des mises à jour en continu.
  • Collaborations en temps réel : Pour les applications de collaboration comme les éditeurs de texte partagés ou les outils de dessin en ligne, les WebSockets facilitent la synchronisation des modifications entre plusieurs utilisateurs.
  • Streaming de données : Les WebSockets sont idéaux pour les flux de données en temps réel, tels que les cours de la bourse, les résultats sportifs, ou les données de capteurs IoT.

Avantages

  • Communication efficace : Comme les connexions WebSocket restent ouvertes, il y a moins de surcharge par rapport aux requêtes HTTP répétées.
  • Réactivité : Les messages peuvent être envoyés et reçus instantanément
  • Économie de bande passante : Les données sont envoyées dans un format json

Exemples

  • Un chat en direct sans utiliser de mécanismes comme Ajax. (Surcharge des requêtes + Latence)
  • Notifications push

Streaming audio/vidéo avec WebSockets

Il est techniquement possible d'utiliser les WebSockets pour envoyer des flux, mais c'est pas optimisés. Les WebSockets n'offrent pas de mécanismes intégrés pour la compression ou l'encodage. temps de latence...

  • WebRTC : solution la plus populaire pour le streaming audio et vidéo en temps réel. Il est conçu pour les communications peer-to-peer et offre une faible latence.
  • HTTP Live Streaming (HLS) : HLS est un protocole de streaming développé par Apple,
  • MPEG-DASH : divise le contenu en segments et permet la diffusion adaptative
  • RTMP (Real-Time Messaging Protocol) : est un protocole traditionnellement utilisé pour le streaming en direct, notamment dans les plateformes de streaming comme YouTube ou Twitch

La recette de base :

3 ingrédiants pour faite la recette aux WebSockets :

APACHE

Imaginez un seul domaine, où vous pouvez installer plusieurs chats comme des salons différents dans un grand bâtiment. Chaque chat a sa propre pièce, mais ils sont tous accessibles depuis la même entrée principale, c’est-à-dire votre domaine.

Pour que les conversations restent privées et sécurisées, on utilise une communication chiffrée via wss (WebSocket sécurisé), comme un interphone crypté entre les pièces. Les messages circulent donc en toute discrétion.

Et pour rendre tout cela possible, on fait appel au ProxyEngine, qui joue le rôle d’un concierge très intelligent. Il s’assure que chaque message envoyé arrive au bon endroit en redirigeant les connexions vers notre bon vieux code PHP. Le tout, sans changer la façade du bâtiment : votre site reste en HTML classique, propre et élégant.

PHP

Pour notre serveur, nous allons utiliser la librairie Ratchet. Son rôle ? Gérer les conversations en temps réel entre les utilisateurs.

Notre serveur reçoit un paquet de données JSON envoyé par un utilisateur, puis le redistribue à tous les autres utilisateurs connectés. Ce JSON contient non seulement les messages des utilisateurs, mais aussi des données techniques comme des identifiants, des informations sur le canal de discussion, etc.

Pour exécuter notre fichier PHP chat1.php, il ne s'agit pas simplement de l'ouvrir dans un navigateur web comme un site traditionnel. Au lieu de cela, nous devons le lancer directement depuis la ligne de commande, c'est-à-dire en utilisant la console ou un cron (tâche planifiée).

JS + HTML

Le code JavaScript présenté implémente un client WebSocket qui permet à l'utilisateur de se connecter à un serveur de chat en temps réel, d'envoyer et de recevoir des messages.

Le script JavaScript agit comme un client de chat en ligne. Il se connecte à un serveur WebSocket (via une URL sécurisée wss://), envoie des données et reçoit des messages en temps réel. Les WebSockets permettent une communication bidirectionnelle et continue entre le client et le serveur, ce qui signifie que le serveur peut envoyer des messages au client sans que celui-ci ait besoin d'envoyer une requête d'abord.

APACHE VirtualHost

<VirtualHost *:80>
    ServerAdmin moi@mon_orange.fr
    ServerName monsupersite.fr
    ServerAlias www.monsupersite.fr
    redirect    / https://monsupersite.fr
    RewriteEngine On
    DocumentRoot /var/www/vhosts/monsupersite.fr/public
</VirtualHost>

<IfModule mod_ssl.c>
    <VirtualHost *:443>
        ServerName              monsupersite.fr
        ServerAlias             www.monsupersite.fr
        DocumentRoot            /var/www/vhosts/monsupersite.fr/public

        # Optimisation du proxy WebSocket
        ProxyRequests Off
        SSLProxyEngine On

        ProxyPass "/chat1" "ws://localhost:8443/"
        ProxyPassReverse "/chat1" "ws://localhost:8443/"

        ProxyPass "/chat2" "ws://localhost:8453/"
        ProxyPassReverse "/chat2" "ws://localhost:8453/"

        ProxyPass "/chat3" "ws://localhost:8463/"
        ProxyPassReverse "/chat3" "ws://localhost:8463/"


        # Configuration du répertoire
        <Directory "/var/www/vhosts/monsupersite.fr">
            Options -Indexes +FollowSymLinks +ExecCGI
            AllowOverride All
            Require all granted
        </Directory>

        # Configuration SSL
        SSLEngine on
        SSLCertificateFile      /etc/letsencrypt/live/monsupersite.fr/cert.pem
        SSLCertificateKeyFile   /etc/letsencrypt/live/monsupersite.fr/privkey.pem
        SSLCertificateChainFile   /etc/letsencrypt/live/monsupersite.fr/chain.pem


        # SSL : désactiver les protocoles obsolètes et sécuriser les suites de chiffrement
        SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
        SSLCipherSuite HIGH:!aNULL:!MD5:!3DES
        SSLHonorCipherOrder on
        SSLCompression off
        SSLOptions +StrictRequire

        ErrorLog ${APACHE_LOG_DIR}/error.log
        CustomLog ${APACHE_LOG_DIR}/access.log combined

    </VirtualHost>
</IfModule>

PHP : chat1.php

Pour démarrer le fichier, on se place dans le répertoire où il se trouve. Le script chat1.php est conçu pour fonctionner en continu. Une fois lancé depuis la console avec la commande :

php chat1.php

le script restera en cours d'exécution tant que la console ou le serveur est en marche. Il agit comme un serveur en arrière-plan, écoutant les connexions WebSocket et traitant les messages en temps réel. Le script ne s'arrête pas automatiquement, il doit donc être arrêté manuellement ou redémarré en cas de besoin.

Si le script est lancé automatiquement via une tâche cron, il ne sera pas aussi simple de l'arrêter que Ctrl + C.

Vous devez d'abord trouver l'ID du processus (PID) : ps aux | grep chat1.php

kill PID

kill -9 PID

Utiliser un fichier de verrouillage (lock file) : l'idée est ajouter à votre script une vérification d'un fichier de verrouillage pour savoir s'il doit continuer à s'exécuter ou s'arrêter. Par exemple, le script pourrait vérifier l'existence d'un fichier comme /tmp/chat1.lock, et s'il est supprimé, le script s'arrêterait.

rm /tmp/chat1.lock

via cron pour arrêter le script : * * * * * pkill -f chat1.php

  https://packagist.org/packages/cboden/ratchet
  composer require cboden/ratchet
<?php 
require dirname( __FILE__ ) . '/vendor/autoload.php';
use Ratchet\Server\IoServer; 
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;

class MySocket implements MessageComponentInterface {
    protected $sockets;

    public function __construct() {
        $this->sockets = new \SplObjectStorage;
        date_default_timezone_set('Europe/Paris');
    }

    public function onOpen(ConnectionInterface $conn) {
        $this->sockets->attach($conn);
        error_log("Connection opened: {$conn->resourceId}");
    }

    public function onMessage(ConnectionInterface $conn, $msg) {
        $decodedMsg = json_decode($msg, true);
        if ($decodedMsg === null) {
            error_log("Invalid JSON message received from {$conn->resourceId}");
            return;
        }
        
        foreach ($this->sockets as $client) {
            if ($client !== $conn) { // Eviter d'envoyer le message à l'expéditeur
                $client->send(json_encode($decodedMsg));
            }
        }
        error_log("Message from {$conn->resourceId}: " . json_encode($decodedMsg));
    }

    public function onClose(ConnectionInterface $conn) {
        $this->sockets->detach($conn);
        error_log("Connection closed: {$conn->resourceId}");
        }

    public function onError(ConnectionInterface $conn, \Exception $e) {
        error_log("An error occurred with connection {$conn->resourceId}: {$e->getMessage()}");
        $conn->close();        
    }

    public static function startServer() {
        error_log("Starting WebSocket server...");
        $server = IoServer::factory(
            new HttpServer(
                new WsServer(
                    new MySocket()
                )
            ),
            8443
        );
        $server->run();
    }
}
MySocket::startServer();  

JS & HTML
chat.js
const MySocket=(function(){
  "use strict";
  let socket=null;
  let msg={
    header:{
      host:window.location.host,
      pathname:window.location.pathname,
      user:'nomuser'
      },      
    chat:{
      channel :"all",
      message :null
    }
  };
  const init=()=>{ 
    try {
      socket=new WebSocket(`wss://monsupersite.fr/chat1?token=${conf.session.compte}`);
      if (socket) {
        socket.onopen=(e)=>{
            msg.object='onopen';
            socket.send(JSON.stringify(msg));
            socket.onmessage=(e)=>{
              const result=JSON.parse(e.data);
              console.log(result)
          }
        };
      }
      socket.addEventListener("error", (event) => {
        event.preventDefault();
      });
    } catch (error) {

    }
  }
  const sendChat=(message)=>{ 
    msg.chat.message=message;
    socket.send(JSON.stringify(msg));
  }

  return {
    init:init,
  };
})();

document.addEventListener('DOMContentLoaded', ()=>MySocket.init());