Après de nombreuses années à répondre à des tickets de support, les mêmes quelques problèmes représentent la grande majorité des rapports « ma connexion WebSocket agit bizarrement ». Aucun n'est un bug dans la bibliothèque — tous sont des erreurs de configuration qui prennent cinq minutes à corriger une fois que vous savez qu'elles existent. Ce billet liste les dix plus courantes, avec le symptôme, la cause et la ligne unique qui résout chacune.
Si vous ne lisez qu'une seule erreur de cette liste, prenez la #2. Bloquer l'événement OnMessage est responsable de plus de tickets « la bibliothèque est lente » que toutes les autres catégories combinées, et c'est invisible jusqu'à ce que vous passiez à plus de quelques centaines de clients concurrents. Épargnez-vous la session de débogage en production tard le soir et corrigez-le avant de livrer.
Erreur 1 : reconnexion WatchDog désactivée
Symptôme : le client se connecte au démarrage, fonctionne bien pendant des heures, puis arrête silencieusement de recevoir des messages après un grésillement Wi-Fi, une reconnexion VPN ou un redémarrage serveur.
Cause : la propriété WatchDog est désactivée par défaut. La bibliothèque ne suppose pas que vous voulez des reconnexions automatiques — certaines applications veulent un comportement fail-fast. La plupart non.
oClient.WatchDog.Enabled := True; oClient.WatchDog.Interval := 10; // try every 10 seconds oClient.WatchDog.Attempts := 0; // 0 = unlimited
Activez et oubliez. Combinez-le avec la journalisation OnDisconnect pour pouvoir voir sur le terrain à quelle fréquence les reconnexions se déclenchent. Pour les déploiements mobile ou laptop où l'utilisateur change de réseau, câblez aussi la notification OS de changement de réseau (SystemEvents.NetworkAvailabilityChanged sur .NET, la route WMI sur Windows-Delphi) pour déclencher une reconnexion immédiate plutôt que d'attendre le prochain tick d'intervalle.
Erreur 2 : bloquer l'événement OnMessage
Symptôme : le débit s'effondre sous charge. Le serveur semble « bloqué ». Le CPU est bas, mais les messages s'accumulent.
Cause : OnMessage tourne sur le thread worker d'E/S. Si vous appelez une base de données lente, une API HTTP externe ou un parseur de longue durée dedans, vous affamez toutes les autres connexions partageant ce worker.
// BAD: blocks the worker
procedure TForm1.ServerMessage(Connection: TsgcWSConnection; const Text: string);
begin
oDb.Query('INSERT INTO events VALUES(?)', [Text]); // 50 ms
oHttp.Get('https://billing/track?event=' + Text); // 200 ms
end;
// GOOD: hand off to a worker queue
procedure TForm1.ServerMessage(Connection: TsgcWSConnection; const Text: string);
begin
oWorkQueue.Push(TWorkItem.Create(Connection.Guid, Text));
end;
Erreur 3 : ignorer HeartBeat
Symptôme : les connexions fantômes s'accumulent. Connections.Count continue de croître même si vous savez que des clients sont morts.
Cause : sans heartbeats au niveau applicatif, le timer keepalive TCP de l'OS se déclenche après ~2 heures. Entre-temps vous avez des dizaines de milliers de sockets zombies qui mangent de la mémoire.
oServer.HeartBeat.Enabled := True; oServer.HeartBeat.Interval := 30; oServer.HeartBeat.Timeout := 90;
Erreur 4 : versions TLS désaccordées
Symptôme : le client obtient « handshake failed » ou « SSL_ERROR_SYSCALL ». La connexion fonctionne sur une machine de dev mais échoue en production derrière un proxy d'entreprise.
Cause : les options SSL du client par défaut sont TLS 1.0–1.2 pour rétrocompatibilité ; les serveurs modernes requièrent uniquement TLS 1.2/1.3. Ou les DLL OpenSSL sont vieilles.
oClient.TLSOptions.Version := tlsTLSv1_2; // or tlsTLSv1_3 oClient.TLSOptions.OpenSSL_Options.LibPath := ExtractFilePath(ParamStr(0)); // Bundle the latest libcrypto-3.dll / libssl-3.dll with your installer.
Erreur 5 : ne pas gérer les messages fragmentés
Symptôme : le serveur reçoit la première partie d'un grand document JSON, votre parseur échoue, et la connexion est fermée.
Cause : par défaut, sgcWebSockets réassemble les fragments pour vous et ne déclenche OnMessage qu'avec le payload complet. Mais si vous avez défini ReadOptions.FragmentMode := frgPartial pour des raisons de mémoire, vous devez réassembler.
// Default (recommended for most apps)
oServer.ReadOptions.FragmentMode := frgComplete;
// If you opted into partial delivery, reassemble manually
procedure TForm1.OnFragment(Connection: TsgcWSConnection;
const aPartial: TBytes; aIsFinal: Boolean);
begin
Buffers[Connection.Guid].Append(aPartial);
if aIsFinal then
Process(Buffers[Connection.Guid].ToBytes);
end;
Erreur 6 : utiliser l'API synchrone dans le thread GUI
Symptôme : l'UI gèle pendant plusieurs secondes lors de la connexion, ou lors de l'appel à WriteData sur un lien lent.
Cause : appels bloquants sur le thread principal. Utilisez toujours le pattern async dans les applications VCL/FMX, ou appelez depuis un thread worker.
// BAD: blocks the UI
oClient.Active := True;
// GOOD: connect asynchronously
TTask.Run(procedure
begin
oClient.Active := True;
end);
// Or use the event-driven API
oClient.OnConnect := DoConnected;
oClient.Connect; // returns immediately
Erreur 7 : oublier de sélectionner un sous-protocole
Symptôme : la connexion réussit mais le pair rejette chaque frame, ou répond avec un format différent de celui attendu.
Cause : de nombreux serveurs WebSocket (MQTT-over-WS, STOMP, GraphQL-WS, Phoenix) requièrent un sous-protocole spécifique dans le handshake. Sans lui, le serveur prend par défaut un protocole différent ou vous éjecte.
// MQTT over WebSocket
oClient.Specifications.Hixie76 := False;
oClient.Specifications.RFC6455 := True;
oClient.HeadersRequest.Add('Sec-WebSocket-Protocol: mqttv3.1');
// GraphQL over WS
oClient.HeadersRequest.Add('Sec-WebSocket-Protocol: graphql-ws');
Erreur 8 : tailles de buffer trop petites (ou trop grandes)
Symptôme : CPU élevé sur un serveur poussant beaucoup de petits messages, ou hors-mémoire sur un serveur poussant des gros.
Cause : les buffers d'envoi/réception par défaut sont dimensionnés pour un usage général. Optimisez-les selon la forme de votre trafic.
// Small messages (chat, telemetry): smaller buffers reduce per-conn memory oServer.IOHandler.RecvBufferSize := 4096; oServer.IOHandler.SendBufferSize := 4096; // Big messages (file transfer, media): larger buffers reduce syscalls oServer.IOHandler.RecvBufferSize := 65536; oServer.IOHandler.SendBufferSize := 65536;
Erreur 9 : pas de vérification d'Origin sur les serveurs publics
Symptôme : l'audit de sécurité signale « n'importe quel site web peut se connecter à votre WebSocket via le navigateur d'un utilisateur ».
Cause : le protocole WebSocket n'impose pas la même origine. Votre serveur doit valider l'en-tête Origin.
procedure TForm1.ServerBeforeConnect(Connection: TsgcWSConnection;
var Continue: Boolean);
var
vOrigin: string;
begin
vOrigin := Connection.Headers.Values['Origin'];
Continue := (vOrigin = 'https://app.example.com')
or (vOrigin = 'https://admin.example.com');
end;
Erreur 10 : journaliser des données sensibles dans OnMessage
Symptôme : l'auditeur trouve des clés API, des JWT ou des PII dans les fichiers de log. Problème de conformité.
Cause : le facile LogMemo.Lines.Add(Text) dans OnMessage écrit chaque payload sur le disque pour toujours.
procedure TForm1.ServerMessage(Connection: TsgcWSConnection; const Text: string);
begin
// Hash / redact before logging
LogMemo.Lines.Add(Format('[%s] %d bytes (sha=%s)',
[Connection.Guid, Length(Text), ShortHash(Text)]));
Process(Text);
end;
Bonus : ne pas lire l'history.txt
Chaque release est livrée avec un history.txt listant chaque changement, correction et note de rupture depuis 2013. Cinq minutes passées à le parcourir après chaque mise à niveau économisent des heures de débogage « pourquoi ça a arrêté de marcher » plus tard.
Bonus 2 : mélanger des versions de composants entre projets
Les développeurs Delphi copient parfois un seul .pas d'une release sgcWebSockets plus récente dans un projet plus ancien, « juste ce fichier ». Cela marche jusqu'à ce que ça ne marche plus — le fichier dépend de types qui ont changé il y a deux releases, et le linker échoue mystérieusement ou, pire, link mais crashe à l'exécution. Mettez toujours à niveau toute la bibliothèque ensemble. Les 30 secondes économisées en copiant un fichier ne valent pas les quatre heures de débogage quand quelque chose en aval casse.
Bonus 3 : traiter WebSocket comme du fire-and-forget
WebSocket n'est pas une file de messages. C'est un flux d'octets bidirectionnel sur TCP. Si le réseau tombe en milieu de message, la frame est perdue et n'est jamais re-délivrée automatiquement. Pour les messages critiques pour le business, vous devez ajouter votre propre protocole d'acquittement par-dessus — habituellement un UUID par message, un ACK explicite du récepteur, et un renvoi côté émetteur après un timeout. Sauter cette couche convient pour les notifications « l'utilisateur tape » et est fatal pour « l'utilisateur a payé pour le panier ».
Bonus 4 : laisser les Memos saigner la mémoire
Symptôme : le memo de diagnostic sur votre fiche de debug est la première chose à faire OOM sur votre client après quelques heures de trafic. Vous blâmez sgcWebSockets ; sgcWebSockets est innocent.
Cause : un TMemo retient chaque ligne jamais ajoutée. À 100 lignes par seconde c'est 360 000 lignes par heure. Chaque ligne alloue une chaîne. La VCL rend des milliers de lignes invisibles à chaque WM_PAINT. Votre processus s'arrête tandis que la bibliothèque ne fait rien de mal.
// Cap the diagnostic memo
procedure TForm1.LogLine(const aText: string);
const
cMaxLines = 500;
begin
TThread.Queue(nil, procedure
begin
while Memo1.Lines.Count > cMaxLines do
Memo1.Lines.Delete(0);
Memo1.Lines.Add(aText);
end);
end;
Mieux encore : journalisez vers un fichier rotatif via LoggerPro et ne reflétez que les 200 dernières lignes dans le memo pour le débogage visuel. Le code de production ne devrait jamais écrire dans un contrôle UI depuis un thread réseau.
Bonus 5 : ne pas fermer le serveur avant Application.Terminate
Symptôme : à l'arrêt de l'application le processus pend pendant 30 secondes, ou l'OS rapporte des exceptions non gérées dans les logs clients parce que les connexions ont été coupées sans grâce.
Cause : le destructeur du serveur envoie une frame de fermeture à chaque connexion et attend que l'OS libère le port d'écoute. Si vous appelez Application.Terminate avant oServer.Active := False, les connexions meurent en plein handshake et le port OS reste en TIME_WAIT, bloquant un redémarrage rapide.
procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
if oServer.Active then
begin
oServer.Broadcast('{"event":"shutdown","reconnect_after":5}');
Sleep(200); // let frames flush
oServer.Active := False; // graceful close
end;
end;
Pour les serveurs console, accrochez SetConsoleCtrlHandler sous Windows ou SIGTERM sous Linux et exécutez la même séquence d'arrêt. Couplez cela avec une boucle HUP/restart dans votre gestionnaire de service et vous obtenez des déploiements zéro-connexion-perdue.
Le pattern derrière les patterns
La plupart de ces erreurs ont une racine commune : supposer que le réseau est fiable. Il ne l'est pas. Les connexions TCP semi-ouvertes arrivent. Les réseaux mobiles tombent. Les proxies d'entreprise cassent TLS. Le Wi-Fi change. Les serveurs redémarrent. Les load balancers cloud terminent les connexions inactives après 60 secondes. Une application WebSocket qui ne survit pas à ces conditions n'est pas terminée — c'est une démo happy-path. La bonne nouvelle : la bibliothèque expose un contrôle pour chacun de ces scénarios. La mauvaise nouvelle : la plupart sont désactivés par défaut pour rétrocompatibilité. Deux heures passées à lire les pages d'option WatchDog, HeartBeat, Reconnect et TLS de la documentation sont l'assurance la moins chère que vous achèterez jamais.
Pattern de second ordre : respectez le thread d'E/S. Tout ce qui prend plus d'une milliseconde — requête base de données, E/S fichier, appel HTTP externe, regex sur une longue chaîne, parse JSON sur un payload de 100 Ko — appartient à un thread worker, pas à OnMessage. Faites de cette règle un absolu. Les développeurs juniors la violeront dans trois mois ; une checklist de code-review qui inclut « pas de blocage dans OnMessage » l'attrape avant qu'elle ne soit livrée.
Pour aller plus loin
Si vous optimisez un serveur à haut trafic, lisez ensuite Optimisation des performances sgcWebSockets. Nouveau sur la bibliothèque ? Commencez par le hub Premiers pas et connectez votre premier WebSocket en cinq minutes.