Tutoriel Linux : page html mise à jour en temps réel par un serveur websocket écrit en langage C

Une websocket est un protocole de communication qui permet d’établir une connexion permanente et bidirectionnelle entre un client (généralement un navigateur) et un serveur. Elle est normalisée par l’IETF sous la référence RFC 6455 et fonctionne au-dessus de TCP.

Contrairement au modèle classique du web, où le client envoie une requête et le serveur répond puis ferme la connexion, la websocket maintient un canal ouvert dans les deux sens. Une fois la connexion établie, le client et le serveur peuvent s’envoyer des données à tout moment, sans attendre une nouvelle requête.

Comment la connexion s’établit

La websocket démarre par une requête HTTP classique contenant un en-tête spécifique, Upgrade: websocket. Si le serveur accepte, il répond avec un code 101 (Switching Protocols). À partir de ce moment, la connexion HTTP est transformée en connexion websocket, et les échanges suivent un format de trames propre à ce protocole.

Ce mécanisme de poignée de main permet de réutiliser l’infrastructure existante du web : ports 80 et 443, proxys, TLS. Une websocket peut donc fonctionner en ws:// ou en wss:// pour la version chiffrée.

A quoi servent les websockets ?

L’intérêt principal d’une websocket est de permettre une communication en temps réel sans les inconvénients des solutions précédentes.

Avant les websockets, deux approches dominaient :

  • Le polling, où le client interroge le serveur à intervalles réguliers. Cela génère beaucoup de requêtes inutiles et introduit un délai entre l’événement et sa réception.
  • Le long polling, où le serveur garde une requête ouverte jusqu’à ce qu’une donnée soit disponible. Plus efficace, mais complexe à gérer et coûteux en ressources.

La websocket supprime ces contraintes. Le serveur peut pousser une information dès qu’elle est disponible, et le client peut envoyer des messages sans attendre. La latence est réduite et la surcharge réseau est limitée, car les en-têtes HTTP ne sont plus répétés à chaque échange.

Cas d’usage typiques

  • Messagerie instantanée : applications de chat, messagerie d’entreprise, support client.
  • Jeux en ligne multijoueurs : synchronisation des positions, des actions et des états en temps réel.
  • Outils collaboratifs : édition de documents à plusieurs, tableaux blancs partagés, suivi de curseurs.
  • Tableaux de bord et monitoring : affichage de métriques, de logs ou d’alertes qui se mettent à jour en continu.
  • Transactions financières : cours de bourse, cryptomonnaies, paris sportifs.
  • Notifications : alertes système, mises à jour d’état, présence en ligne.

Une websocket n’est pas adaptée à tous les besoins. Pour des échanges ponctuels ou peu fréquents, une simple requête HTTP reste plus simple et suffisante. La websocket ajoute de la complexité : gestion des reconnexions, des coupures réseau, de la montée en charge côté serveur, car chaque connexion ouverte mobilise des ressources.

Elle nécessite aussi que les deux extrémités parlent le même protocole applicatif. La websocket ne définit que le transport ; c’est au développeur de choisir comment structurer les messages (JSON, protobuf, texte brut, etc.).

Enfin, la sécurité doit être prise en compte : validation des origines, authentification, chiffrement via wss://, limitation du nombre de connexions par client.

Développer avec les websockets en langage C

Pour un développeur C qui souhaite implémenter un serveur WebSocket, le choix raisonnable est d’utiliser une bibliothèque dédiée. En effet, développer ses petites routines soi-même est très complexe.

Basiquement, il y a deux choix de bibliothèques possibles : libwebsockets ou mongoose.

libwebsockets (LWS)

C’est la bibliothèque de référence en C pur pour les WebSockets . Elle est légère, sous licence MIT, et conçue pour minimiser l’usage CPU et mémoire .

Points forts :

  • Maturité et portabilité : développée depuis 2010, elle tourne sur un large éventail de plateformes, des systèmes embarqués aux serveurs cloud .
  • Écosystème riche : support de SSL/TLS, HTTP/2, MQTT, et plus de 100 exemples minimaux fournis .
  • Intégration flexible : peut s’interfacer avec différents boucles d’événements (libuv, libevent, libev, glib, etc.) .

    Mongoose

    Mongoose est une bibliothèque réseau multi-protocole qui inclut le support WebSocket .

    Points forts :

    • Simplicité d’intégration : tient dans un seul fichier C, ce qui facilite l’incorporation dans un projet existant .
    • Multi-protocole : gère aussi HTTP, MQTT, etc., utile si le serveur doit parler plusieurs protocoles .
    • Conçue pour l’embarqué : légère et adaptée aux environnements contraints .

    Points faibles :

    • Moins spécialisée : l’API WebSocket est moins riche que celle de LWS, et le support SSL/TLS dépend d’OpenSSL .

    Comparaison rapide

    CritèrelibwebsocketsMongoose
    Taille du codePlus volumineux, spécialiséCompact, multi-protocole
    APIBasée sur callbacksÉvénementielle
    FocusWebSocket-centricMulti-protocole
    SSL/TLSIntégréVia OpenSSL
    Boucle d’événementslws_servicemg_mgr_poll
    Complexité de configurationÉtendueMinimale

        Travaux pratiques

        Nous allons maintenant pratiquer. Voici ce que nous allons faire : nous allons programmer un exemple « simple » en utilisant mongoose.

        Nous allons créer une page html index.html qui se connecte seule au serveur websocket en C et récupère l’heure chaque seconde pour l’afficher. La page présente également une zone de saisie de texte permettant de d’envoyer du texte au serveur C et de récupérer la réponse (qui sera la même).

        Voir la vidéo du résultat à la fin de cet article.

        Du point de vue architecture, on crée un répertoire dédié dans lequel on va mettre index.html et serveur.c. La compilation créera l’exécutable serveur qui, une fois lancé, écoutera sur http://localhost:8000

        A la connexion du navigateur à http://localhost:8000, le serveur C enverra lui-même la page index.html puis récupérera la demande d’ouverture de connexion websocket et la gérera (envoi de l’heure toutes les secondes et renvoi des messages écrits reçus).

        Le serveur en C utilisant la librairie mongoose, il convient d’abord de récupérer mongoose.h et mongoose.c en tapant les commandes shell :

        wget https://raw.githubusercontent.com/cesanta/mongoose/master/mongoose.c
        wget https://raw.githubusercontent.com/cesanta/mongoose/master/mongoose.h
         

        Le fichier index.html

        Commençons par le code source de index.html. Il figure ci-dessous, est très bien commenté pour expliquer chaque ligne, et utilise les fonctions javascript nécessaires à la connexion et la gestion websocket.

        <!DOCTYPE html>
        <!-- ================================================================== -->
        <!--  index.html — Page cliente pour la démo WebSocket ↔ C             -->
        <!--                                                                    -->
        <!--  Rôle : se connecter au serveur C via WebSocket, afficher l'heure  -->
        <!--         poussée chaque seconde par le serveur, et permettre à      -->
        <!--         l'utilisateur d'envoyer des messages au C.                 -->
        <!--                                                                    -->
        <!--  Ce fichier est servi automatiquement par serveur.c quand on        -->
        <!--  ouvre http://localhost:8000 dans un navigateur.                   -->
        <!-- ================================================================== -->
        <html lang="fr">
        
        <head>
        <!-- Encodage UTF-8 pour que les accents s'affichent correctement. -->
        <meta charset="UTF-8">
        
        <!-- Titre affiché dans l'onglet du navigateur. -->
        <title>Horloge temps réel depuis le C</title>
        
        <!-- ================================================================== -->
        <!--  CSS : toute la mise en forme de la page                          -->
        <!-- ================================================================== -->
        <style>
          /* body : la page entière. Police système, largeur limitée à 600px, */
          /* centrée horizontalement (margin auto), texte centré.             */
          body { font-family: system-ui, sans-serif; max-width: 600px;
                 margin: 40px auto; text-align: center; }
        
          /* Titre principal. */
          h1 { color: #333; }
        
          /* La grande horloge. Police monospace pour que les chiffres ne      */
          /* "sautent" pas quand ils changent (chaque chiffre a la même        */
          /* largeur). Fond noir, chiffres verts façon terminal.              */
          #clock {
            font-family: 'SF Mono', Menlo, monospace;
            font-size: 4rem; font-weight: bold; color: #090;
            background: #111; padding: 20px; border-radius: 12px;
            margin: 20px 0; letter-spacing: 4px;
          }
        
          /* La pastille d'état (connecté / déconnecté). */
          #status { padding: 6px 12px; border-radius: 6px; display: inline-block;
                    font-weight: bold; font-size: 0.9rem; }
        
          /* Deux classes pour l'état : rouge clair (off) ou vert clair (on). */
          .off { background: #fdd; color: #900; }
          .on  { background: #dfd; color: #090; }
        
          /* Le journal des messages (sous la page). */
          #log { background: #f5f5f5; color: #333; font-family: monospace;
                 padding: 10px; border-radius: 6px; height: 150px;
                 overflow-y: auto; margin-top: 20px; text-align: left;
                 font-size: 0.85rem; }
          #log div { margin: 2px 0; }
        
          /* Couleurs des lignes du journal selon leur type. */
          .sent { color: #06c; }  /* messages envoyés par moi   */
          .recv { color: #090; }  /* messages reçus du serveur  */
          .sys  { color: #c60; }  /* messages système (connexion...) */
        
          /* Champ de saisie et bouton. */
          input, button { font-size: 1rem; padding: 8px 12px; margin-top: 10px; }
          input { width: 65%; }
        </style>
        </head>
        
        <body>
        
        <!-- ================================================================== -->
        <!--  Contenu visible de la page                                        -->
        <!-- ================================================================== -->
        
        <h1>⏰ Horloge temps réel</h1>
        <p>L'heure est envoyée <b>par le serveur C</b>, une fois par seconde.</p>
        
        <!-- La grande horloge. Au départ elle affiche --:--:-- en attendant  -->
        <!-- que le serveur envoie la première heure (au plus 1 seconde après  -->
        <!-- la connexion).                                                     -->
        <div id="clock">--:--:--</div>
        
        <!-- La pastille d'état. class="off" au départ (rouge). -->
        <p>État : <span id="status" class="off">déconnecté</span></p>
        
        <!-- Champ texte + bouton pour envoyer un message au C. -->
        <input id="msg" placeholder="Envoie un message au C...">
        <button onclick="send()">Envoyer</button>
        
        <!-- Le journal des événements (messages échangés). -->
        <div id="log"></div>
        
        
        <!-- ================================================================== -->
        <!--  JavaScript : toute la logique côté navigateur                     -->
        <!-- ================================================================== -->
        <script>
        
          /* ---------------------------------------------------------------- */
          /*  1) Récupération des éléments HTML dont on a besoin               */
          /* ---------------------------------------------------------------- */
          /* document.getElementById("...") cherche dans la page l'élément    */
          /* qui a l'attribut id="..." et renvoie une référence vers lui.     */
          /* On garde ces références dans des constantes pour ne pas avoir à  */
          /* les rechercher à chaque fois.                                     */
        
          const clockEl  = document.getElementById("clock");   /* la grande horloge  */
          const statusEl = document.getElementById("status");  /* la pastille d'état */
          const logEl    = document.getElementById("log");     /* le journal         */
        
        
          /* ---------------------------------------------------------------- */
          /*  2) Fonction utilitaire : ajouter une ligne au journal            */
          /* ---------------------------------------------------------------- */
          /* On l'appelle chaque fois qu'on veut écrire un événement dans le   */
          /* journal (connexion, message reçu, message envoyé...).             */
          /*                                                                   */
          /* Paramètres :                                                      */
          /*   text — le texte à afficher                                      */
          /*   cls  — la classe CSS à appliquer ("sent", "recv", "sys" ou "")  */
          /*          par défaut "" (= pas de classe, texte noir)              */
          function log(text, cls = "") {
        
            /* On ajoute une <div> à la fin du contenu HTML du journal.        */
            /* logEl.innerHTML représente le HTML interne de l'élément.        */
            /* "+=" ajoute à la fin au lieu de remplacer.                      */
            /* Les backticks ` ... ` permettent d'insérer des variables        */
            /* directement dans la chaîne avec ${...} (template literals).     */
            logEl.innerHTML += `<div class="${cls}">${text}</div>`;
        
            /* On fait défiler le journal vers le bas pour montrer la         */
            /* dernière ligne. scrollHeight = hauteur totale du contenu.       */
            logEl.scrollTop = logEl.scrollHeight;
          }
        
        
          /* ---------------------------------------------------------------- */
          /*  3) Connexion au serveur WebSocket                                */
          /* ---------------------------------------------------------------- */
          /* On crée un objet WebSocket qui va tenter de se connecter au       */
          /* serveur C. L'URL est construite dynamiquement :                   */
          /*   location.host contient "localhost:8000" (le host de la page)   */
          /*   donc l'URL finale est "ws://localhost:8000/"                    */
          /*                                                                   */
          /* On utilise le protocole "ws://" (non chiffré, équivalent HTTP)    */
          /* par opposition à "wss://" (chiffré, équivalent HTTPS).            */
          const ws = new WebSocket(`ws://${location.host}/`);
        
        
          /* ---------------------------------------------------------------- */
          /*  4) Gestion des événements WebSocket                              */
          /* ---------------------------------------------------------------- */
          /* L'objet ws expose 4 propriétés qu'on peut définir avec une        */
          /* fonction : elles sont appelées automatiquement quand l'événement  */
          /* correspondant se produit.                                         */
          /*                                                                   */
          /*   ws.onopen    — la connexion vient de s'établir                  */
          /*   ws.onmessage — un message est arrivé du serveur                 */
          /*   ws.onclose   — la connexion s'est fermée                        */
          /*   ws.onerror   — une erreur est survenue                          */
          /* ------------------------------------------------------------------ */
        
          /* --- 4a) La connexion s'ouvre --- */
          ws.onopen = () => {
        
            /* On met à jour la pastille d'état : texte "connecté",             */
            /* classe CSS "on" (vert).                                          */
            statusEl.textContent = "connecté";
            statusEl.className = "on";
        
            /* Et on écrit une ligne dans le journal. */
            log("Connecté au serveur C", "sys");
          };
        
        
          /* --- 4b) Un message arrive du serveur --- */
          ws.onmessage = (event) => {
        
            /* event.data contient le texte envoyé par le serveur (une string  */
            /* car on a utilisé WEBSOCKET_OP_TEXT côté C).                     */
            const data = event.data;
        
            /* Le serveur nous envoie DEUX types de messages :                 */
            /*   1. l'heure au format "HH:MM:SS" (une fois par seconde)        */
            /*   2. des réponses à nos messages (texte libre)                  */
            /*                                                                 */
            /* Pour les distinguer, on utilise une expression régulière :      */
            /*   ^\d{2}:\d{2}:\d{2}$                                           */
            /*   ^         : début de la chaîne                                */
            /*   \d{2}     : exactement 2 chiffres                             */
            /*   :         : le caractère ":"                                  */
            /*   $         : fin de la chaîne                                  */
            /* Si ça correspond, c'est l'heure.                                */
            if (/^\d{2}:\d{2}:\d{2}$/.test(data)) {
        
              /* On met à jour la grande horloge (et rien d'autre : pas de     */
              /* ligne dans le journal, sinon il serait saturé).              */
              clockEl.textContent = data;
        
            } else {
        
              /* Sinon, c'est un message "normal" (accueil, réponse echo...) : */
              /* on l'ajoute au journal avec la classe "recv" (vert).          */
              log("← C : " + data, "recv");
            }
          };
        
        
          /* --- 4c) La connexion se ferme --- */
          ws.onclose = () => {
        
            /* Pastille d'état en rouge. */
            statusEl.textContent = "déconnecté";
            statusEl.className = "off";
        
            /* Note dans le journal. */
            log("Déconnecté", "sys");
        
            /* Dans une vraie appli, on pourrait relancer une reconnexion     */
            /* automatique après quelques secondes avec setTimeout().          */
          };
        
        
          /* --- 4d) Une erreur survient --- */
          ws.onerror = () => {
            log("Erreur WebSocket", "sys");
          };
        
        
          /* ---------------------------------------------------------------- */
          /*  5) Fonction : envoyer un message au serveur C                    */
          /* ---------------------------------------------------------------- */
          /* Appelée quand on clique sur le bouton "Envoyer" ou qu'on appuie   */
          /* sur Entrée dans le champ de saisie.                               */
          function send() {
        
            /* On récupère le champ de saisie. */
            const input = document.getElementById("msg");
        
            /* ws.readyState indique l'état de la connexion :                  */
            /*   WebSocket.CONNECTING = 0  (en cours de connexion)             */
            /*   WebSocket.OPEN       = 1  (ouverte, prête à envoyer)          */
            /*   WebSocket.CLOSING    = 2  (en cours de fermeture)             */
            /*   WebSocket.CLOSED     = 3  (fermée)                            */
            /* On ne peut envoyer que si la connexion est OPEN.                */
            /* On vérifie aussi que le champ n'est pas vide (après trim()      */
            /* qui enlève les espaces au début et à la fin).                   */
            if (ws.readyState !== WebSocket.OPEN || !input.value.trim()) {
              return;   /* on abandonne silencieusement */
            }
        
            /* Envoi du message : ws.send() transmet la chaîne au serveur.     */
            ws.send(input.value);
        
            /* Trace dans le journal avec la classe "sent" (bleu). */
            log("→ Moi : " + input.value, "sent");
        
            /* On vide le champ pour permettre à l'utilisateur de taper un    */
            /* nouveau message immédiatement.                                 */
            input.value = "";
          }
        
        
          /* ---------------------------------------------------------------- */
          /*  6) Raccourci : appuyer sur Entrée envoie le message              */
          /* ---------------------------------------------------------------- */
          /* addEventListener() attache un gestionnaire d'événement à un      */
          /* élément HTML. Ici : sur le champ de saisie, on écoute les        */
          /* appuis de touches ("keydown").                                    */
          /*                                                                  */
          /* La flèche => est une fonction anonyme (arrow function).          */
          /*   e est l'objet "événement" contenant les infos sur la touche.   */
          /*   e.key contient le nom de la touche ("Enter", "a", "Escape"...).*/
          /* Si c'est "Enter", on appelle la fonction send().                 */
          document.getElementById("msg").addEventListener("keydown",
              e => { if (e.key === "Enter") send(); });
        
        </script>
        
        </body>
        </html>

        Le fichier serveur.c

        Le code source C utilise la librairie mongoose, en associant mongoose.c avec serveur.c dans la compilation.

        Le code source de serveur.c, ci-dessous, est également très documenté de sorte qu’on comprenne bien l’emploi des fonctions websocket offertes par la librairie mongoose :

        /* ==================================================================== */
        /*  serveur.c — Serveur WebSocket minimaliste en C                      */
        /*                                                                      */
        /*  Rôle : servir une page HTML + envoyer l'heure à tous les clients    */
        /*         WebSocket connectés, une fois par seconde.                   */
        /*                                                                      */
        /*  Dépendance : mongoose.c / mongoose.h (https://mongoose.ws)          */
        /*                                                                      */
        /*  Compilation :  gcc serveur.c mongoose.c -o serveur                  */
        /*  Lancement   :  ./serveur                                            */
        /*  Test        :  ouvrir http://localhost:8000 dans un navigateur      */
        /* ==================================================================== */
        
        #include "mongoose.h"   /* La bibliothèque qui gère réseau + HTTP + WS  */
        #include <stdio.h>      /* printf()                                      */
        #include <string.h>     /* (utilisé indirectement par snprintf, etc.)    */
        #include <time.h>       /* time(), localtime(), strftime()               */
        
        
        /* -------------------------------------------------------------------- */
        /*  TIMER : diffusion de l'heure à tous les clients WebSocket           */
        /*                                                                      */
        /*  Cette fonction est appelée automatiquement par Mongoose toutes      */
        /*  les secondes (on l'a programmée dans main() avec mg_timer_add).     */
        /*                                                                      */
        /*  Paramètre :                                                         */
        /*    arg — un pointeur passé à mg_timer_add. Ici c'est l'adresse de    */
        /*          la structure mgr (le "gestionnaire" de toutes les           */
        /*          connexions réseau). On en a besoin pour parcourir la        */
        /*          liste des clients connectés.                                */
        /* -------------------------------------------------------------------- */
        static void broadcast_time(void *arg)
        {
            /* On récupère l'adresse du gestionnaire de connexions.              */
            /* Le cast "(struct mg_mgr *)" est nécessaire car mg_timer_add       */
            /* nous a donné un void* (pointeur non typé).                        */
            struct mg_mgr *mgr = (struct mg_mgr *) arg;
        
            /* --- 1) Obtenir l'heure courante et la formater en "HH:MM:SS" --- */
        
            /* time(NULL) renvoie le nombre de secondes écoulées depuis          */
            /* le 1er janvier 1970 (le "timestamp Unix").                        */
            time_t now = time(NULL);
        
            /* localtime() convertit ce timestamp en structure lisible :         */
            /* année, mois, jour, heure, minute, seconde... selon le fuseau      */
            /* horaire local.                                                    */
            struct tm *lt = localtime(&now);
        
            /* strftime() formate la structure tm dans un buffer selon un motif. */
            /* "%H:%M:%S" → heure:minute:seconde (ex : "14:23:01").              */
            /* Retour : nombre de caractères écrits (sans le '\0' final).        */
            char buf[32];
            int n = strftime(buf, sizeof(buf), "%H:%M:%S", lt);
        
            /* --- 2) Envoyer ce texte à TOUS les clients WebSocket ouverts --- */
        
            /* mgr->conns est une liste chaînée de toutes les connexions        */
            /* réseau actives (qu'elles soient HTTP ou WebSocket).              */
            /* On la parcourt du premier élément jusqu'à NULL (fin de liste).   */
            for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) {
        
                /* On ne veut PAS envoyer l'heure à une connexion HTTP simple    */
                /* (par exemple, une requête pour charger index.html) : ça       */
                /* n'aurait aucun sens, HTTP n'est pas bidirectionnel et la      */
                /* connexion se ferme après la réponse.                          */
                /* c->is_websocket est un booléen positionné à 1 par Mongoose    */
                /* dès que l'upgrade WebSocket a réussi sur cette connexion.     */
                if (c->is_websocket) {
        
                    /* mg_ws_send() envoie un message WebSocket texte :           */
                    /*   - c      : la connexion destinataire                     */
                    /*   - buf    : les octets à envoyer                          */
                    /*   - n      : leur nombre                                   */
                    /*   - WEBSOCKET_OP_TEXT : c'est du texte UTF-8 (par opposi-  */
                    /*     tion à WEBSOCKET_OP_BINARY pour du binaire).           */
                    mg_ws_send(c, buf, n, WEBSOCKET_OP_TEXT);
                }
            }
        }
        
        
        /* -------------------------------------------------------------------- */
        /*  DÉTECTION : est-ce que cette requête HTTP est en fait une demande   */
        /*  d'upgrade WebSocket ?                                               */
        /*                                                                      */
        /*  Pourquoi cette fonction ?                                           */
        /*  Un client WebSocket commence toujours par une requête HTTP          */
        /*  normale, mais avec des en-têtes spéciaux :                           */
        /*      Connection: Upgrade                                             */
        /*      Upgrade: websocket                                              */
        /*      Sec-WebSocket-Key: ...                                          */
        /*                                                                      */
        /*  Mongoose nous livre cette requête comme un MG_EV_HTTP_MSG classique.*/
        /*  On doit donc inspecter ses en-têtes pour savoir si c'est une        */
        /*  connexion WS à promouvoir ou une simple requête de page HTML.       */
        /*                                                                      */
        /*  Retour : 1 si c'est une demande WS, 0 sinon.                        */
        /* -------------------------------------------------------------------- */
        static int is_websocket_upgrade(struct mg_http_message *hm)
        {
            /* hm->headers est un tableau de paires (nom, valeur).                */
            /* Il est terminé par un élément dont name.len vaut 0.                */
            /* MG_MAX_HTTP_HEADERS est la taille max du tableau (constante de     */
            /* Mongoose) — on s'arrête au plus tôt des deux conditions.           */
        
            for (int i = 0; i < MG_MAX_HTTP_HEADERS && hm->headers[i].name.len; i++) {
        
                /* mg_strcasecmp() compare deux mg_str sans tenir compte de la    */
                /* casse. Un en-tête HTTP peut être "Upgrade", "upgrade",         */
                /* "UPGRADE"... donc la comparaison insensible à la casse est     */
                /* obligatoire pour être conforme au protocole HTTP.              */
                /*                                                                */
                /* mg_str("...") est une macro qui construit une mg_str à partir  */
                /* d'une chaîne C classique (elle stocke le pointeur ET la        */
                /* longueur, contrairement à strlen qui recalcule à chaque fois). */
        
                if (mg_strcasecmp(hm->headers[i].name, mg_str("Upgrade")) == 0 &&
                    mg_strcasecmp(hm->headers[i].value, mg_str("websocket")) == 0) {
                    return 1;   /* Les deux en-têtes correspondent → c'est du WS */
                }
            }
            return 0;   /* Aucun en-tête "Upgrade: websocket" trouvé */
        }
        
        
        /* -------------------------------------------------------------------- */
        /*  CALLBACK PRINCIPAL                                                  */
        /*                                                                      */
        /*  C'est LE cœur du serveur. Mongoose appelle cette fonction à chaque  */
        /*  fois qu'il se passe quelque chose sur une connexion :                */
        /*    - une requête HTTP arrive                                         */
        /*    - une connexion WebSocket s'ouvre                                 */
        /*    - un message WebSocket est reçu                                   */
        /*    - une connexion se ferme                                          */
        /*                                                                      */
        /*  Paramètres :                                                        */
        /*    c        — la connexion concernée par l'événement                 */
        /*    ev       — un code numérique identifiant le type d'événement      */
        /*               (MG_EV_HTTP_MSG, MG_EV_WS_OPEN, etc.)                  */
        /*    ev_data  — des données spécifiques à l'événement (pointeur)       */
        /*               qu'il faut caster vers le bon type selon ev            */
        /* -------------------------------------------------------------------- */
        static void fn(struct mg_connection *c, int ev, void *ev_data)
        {
            /* ================================================================ */
            /*  CAS 1 : une requête HTTP vient d'arriver                         */
            /* ================================================================ */
            /* C'est le point d'entrée de TOUTE connexion. Un client qui veut   */
            /* charger la page HTML ou qui veut ouvrir un WebSocket commence    */
            /* toujours par une requête HTTP.                                    */
            if (ev == MG_EV_HTTP_MSG) {
        
                /* ev_data pointe vers une struct mg_http_message qui contient  */
                /* tout ce qu'il faut savoir sur la requête :                   */
                /*   hm->method → "GET", "POST"...                              */
                /*   hm->uri    → "/", "/index.html", "/api/..."                */
                /*   hm->headers→ tableau d'en-têtes                            */
                /*   hm->body   → corps de la requête                           */
                struct mg_http_message *hm = (struct mg_http_message *) ev_data;
        
                /* ------------------------------------------------------------ */
                /*  Sous-cas 1a : c'est une demande d'upgrade WebSocket ?       */
                /* ------------------------------------------------------------ */
                if (is_websocket_upgrade(hm)) {
        
                    /* mg_ws_upgrade() réalise le "handshake" WebSocket :        */
                    /*   - calcule la clé de réponse à partir de la clé envoyée  */
                    /*     par le client (Sec-WebSocket-Key)                     */
                    /*   - envoie la réponse HTTP 101 Switching Protocols        */
                    /*   - marque la connexion comme WebSocket                   */
                    /*                                                           */
                    /* Le 3e argument (NULL) sert à passer une structure         */
                    /* d'options avancées (sous-protocoles, etc.). NULL = défaut.*/
                    /*                                                           */
                    /* Après cet appel, Mongoose déclenchera MG_EV_WS_OPEN       */
                    /* sur la même connexion.                                    */
                    mg_ws_upgrade(c, hm, NULL);
                    printf("[C] Client WebSocket connecté\n");
                }
        
                /* ------------------------------------------------------------ */
                /*  Sous-cas 1b : requête HTTP normale sur "/"                   */
                /* ------------------------------------------------------------ */
                /* C'est le navigateur qui demande la page HTML.                 */
                /*                                                               */
                /* mg_match() compare une mg_str à un motif. Ici on teste si    */
                /* l'URI est exactement "/". Le 3e argument NULL sert à          */
                /* l'extraction de variables (non utilisé).                     */
                else if (mg_match(hm->uri, mg_str("/"), NULL)) {
        
                    /* Options de service de fichier. {0} initialise tous les   */
                    /* champs à zéro : Mongoose utilisera les valeurs par       */
                    /* défaut (dossier courant, MIME déduit de l'extension...). */
                    struct mg_http_serve_opts opts = { 0 };
        
                    /* Lit le fichier "index.html" sur le disque et l'envoie    */
                    /* au client en réponse HTTP.                               */
                    mg_http_serve_file(c, hm, "index.html", &opts);
                }
        
                /* ------------------------------------------------------------ */
                /*  Sous-cas 1c : toute autre URL → 404                          */
                /* ------------------------------------------------------------ */
                else {
                    /* mg_http_reply() construit une réponse HTTP minimale :     */
                    /*   - code 404                                              */
                    /*   - pas d'en-tête spécial                                 */
                    /*   - corps "Not found\n"                                   */
                    mg_http_reply(c, 404, "", "Not found\n");
                }
            }
        
            /* ================================================================ */
            /*  CAS 2 : une connexion WebSocket vient de s'ouvrir               */
            /* ================================================================ */
            /* Déclenché juste après un mg_ws_upgrade() réussi.                 */
            /* Bon moment pour envoyer un message d'accueil au nouveau client.  */
            else if (ev == MG_EV_WS_OPEN) {
        
                /* On envoie une chaîne de bienvenue à ce client précis.        */
                /* Le "42" est la longueur de la chaîne en octets (sans '\0').  */
                mg_ws_send(c, "Bienvenue ! L'heure arrive chaque seconde...",
                           42, WEBSOCKET_OP_TEXT);
            }
        
            /* ================================================================ */
            /*  CAS 3 : un message WebSocket a été reçu du client               */
            /* ================================================================ */
            else if (ev == MG_EV_WS_MSG) {
        
                /* ev_data pointe vers une struct mg_ws_message.                */
                /* wm->data est une mg_str contenant les octets reçus.          */
                struct mg_ws_message *wm = (struct mg_ws_message *) ev_data;
        
                /* On loggue le message reçu (%.*s permet d'afficher une mg_str */
                /* sans qu'elle soit terminée par '\0' : on précise la longueur */
                /* avant le pointeur).                                          */
                printf("[C] Reçu du client : %.*s\n",
                       (int)wm->data.len, wm->data.buf);
        
                /* On prépare une réponse personnalisée qui renvoie au client   */
                /* le message qu'il vient d'envoyer, façon echo.                */
                char response[256];
                int n = snprintf(response, sizeof(response),
                                 "Le serveur C a bien reçu : \"%.*s\"",
                                 (int)wm->data.len, wm->data.buf);
        
                /* Et on l'envoie sur la même connexion.                        */
                mg_ws_send(c, response, n, WEBSOCKET_OP_TEXT);
            }
        
            /* ================================================================ */
            /*  CAS 4 : la connexion se ferme (client parti, erreur réseau...)  */
            /* ================================================================ */
            else if (ev == MG_EV_CLOSE) {
        
                /* À ce stade, Mongoose va bientôt libérer la connexion.        */
                /* On loggue simplement pour info (et seulement s'il s'agissait */
                /* d'un client WebSocket, pour ne pas polluer avec les          */
                /* fermetures de simples requêtes HTTP).                        */
                if (c->is_websocket) {
                    printf("[C] Client WebSocket déconnecté\n");
                }
            }
        }
        
        
        /* -------------------------------------------------------------------- */
        /*  POINT D'ENTRÉE DU PROGRAMME                                         */
        /* -------------------------------------------------------------------- */
        int main(void)
        {
            /* mgr est le "gestionnaire" central de Mongoose.                   */
            /* Il contient :                                                    */
            /*   - la liste de toutes les connexions ouvertes                   */
            /*   - la liste des timers actifs                                   */
            /*   - la liste des sockets en écoute                               */
            /*   - divers paramètres internes                                   */
            struct mg_mgr mgr;
        
            /* Initialisation à zéro + configuration par défaut de Mongoose.    */
            /* À appeler obligatoirement avant toute autre opération.           */
            mg_mgr_init(&mgr);
        
            /* ---------------------------------------------------------------- */
            /*  Programmation du timer qui enverra l'heure chaque seconde       */
            /* ---------------------------------------------------------------- */
            /* Arguments :                                                      */
            /*   &mgr               → le gestionnaire auquel rattacher le timer */
            /*   1000                → intervalle en millisecondes (1 seconde)  */
            /*   MG_TIMER_REPEAT     → le timer se répète indéfiniment          */
            /*                         (sinon il ne se déclenche qu'une fois)   */
            /*   broadcast_time      → la fonction à appeler                    */
            /*   &mgr                → argument passé à broadcast_time          */
            /*                         (on lui donne mgr pour qu'il puisse      */
            /*                         parcourir les connexions)                */
            mg_timer_add(&mgr, 1000, MG_TIMER_REPEAT, broadcast_time, &mgr);
        
            /* ---------------------------------------------------------------- */
            /*  Ouverture du port d'écoute HTTP                                 */
            /* ---------------------------------------------------------------- */
            /* "http://0.0.0.0:8000" signifie :                                 */
            /*   - écouter sur TOUTES les interfaces réseau de la machine       */
            /*     (0.0.0.0) et pas seulement localhost                         */
            /*   - sur le port 8000                                             */
            /*   - en HTTP (Mongoose acceptera aussi les upgrades WS dessus)    */
            /*                                                                  */
            /* fn est le callback appelé pour chaque événement sur ce port.     */
            /* Le dernier NULL est un argument utilisateur optionnel (non       */
            /* utilisé ici).                                                    */
            mg_http_listen(&mgr, "http://0.0.0.0:8000", fn, NULL);
            printf("[C] Serveur sur http://localhost:8000\n");
        
            /* ---------------------------------------------------------------- */
            /*  Boucle infinie : c'est le moteur du serveur                     */
            /* ---------------------------------------------------------------- */
            /* mg_mgr_poll() fait tout le travail :                             */
            /*   - surveille tous les sockets (écoute + connexions ouvertes)    */
            /*   - lit les données arrivées et appelle fn() avec le bon         */
            /*     événement (MG_EV_HTTP_MSG, MG_EV_WS_MSG...)                  */
            /*   - écrit les données en attente d'envoi                         */
            /*   - vérifie les timers échus et appelle leurs callbacks          */
            /*                                                                  */
            /* L'argument 100 est un timeout en millisecondes : mg_mgr_poll()   */
            /* rend la main au plus tard après 100 ms, même s'il ne s'est rien  */
            /* passé. Ça permet à la boucle de rester réactive sans consommer   */
            /* 100% du CPU.                                                     */
            while (1) {
                mg_mgr_poll(&mgr, 100);
            }
        
            /* Jamais atteint dans cette démo (boucle infinie), mais propre     */
            /* à écrire : libère toutes les ressources allouées par Mongoose.   */
            mg_mgr_free(&mgr);
            return 0;
        }

        Compilons maintenant le serveur par la commande :

        gcc serveur.c mongoose.c -o serveur

        Voici le résultat :

        Laisser un commentaire

        Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *