Net-Base Maġazin

01.06.2026

Delphi Klijent WebSocket: tgħaqqad b'mod robust, tieqaf b'mod nadif, iddebbuga b'mod affidabbli

Klijent WebSocket Delphi jista’ jkun malajr “xi mod konness” — imma fl-operazzjoni jimportaw ir-riconnessjoni, il-heartbeats, it-twaqqif nadif u l-facilità tal-debug. B’wrapper prattiku ibbażat fuq System.Net.WebSockets (bi fallback) u b’snippet tas-sors għall-threading u...

01.06.2026

Minn suġġett tar-rivista għall-prattika tal-proġett

Paġni ta' servizz u paġni tekniċi relevanti għall-artiklu

Għaliex ein Delphi WebSocket Client fil-prattika huwa aktar als „Connect“

Ein Delphi WebSocket Client huwa magħmul fi ftit minuti: URL, Connect, SendText, lest. Fil-softwer individwali tal-kumpanija u f’soluzzjonijiet qrib il-proċess, it-tema normalment tinbidel waqt il-ħidma: il-Reverse Proxy jaqta‘ l-konnessjonijiet idle, trajjetti mobbli jew VPN għandhom NAT-Timeouts qosra, ċertifikati jinbidlu, u waqt it-terminazzjoni l-proċess jista‘ jinqabad għax Receive-Loop għadu blokkjat. Barra minn hekk: WebSocket huwa kanal ta‘ ħajja twila u b’stajt – għalhekk japplikaw regoli differenti minn HTTP klassiku/REST (Request/Response, qasir il-ħajja).

F’dan is-snippet tas-sors mhux qed nitkellmu dwar „Hello WebSocket“, iżda dwar wrapper tal-client adattat għall-prattika b‘:

  • start/stop nadif (mingħajr stall waqt shutdown),
  • Receive-Loop bi Cancellation (segnal ta‘ terminazzjoni) minflok „Thread kill“,
  • Reconnect b’Backoff (ri-konnessjoni kkontrollata),
  • Heartbeat bħala mudell ta‘ applikazzjoni (għaliex Ping/Pong mhux disponibbli kullimkien),
  • Debug- u Trace-Hooks li tassew jgħinu f’każijiet ta‘ appoġġ.

L-implimentazzjoni hija bbażata fuq System.Net.WebSockets (Delphi RTL; WebSocket-Client-API bi TClientWebSocket). Fejn din is-saff tal-RTL mhix disponibbli jew hija ristretta wisq f’verżjonijiet aktar antiki, fallback permezz ta‘ librerija (eż. ICS) spiss ikun sensibbli – dwar dan hawn taħt hemm klassifikazzjoni.

Skissja ta‘ Arkitettura: ein Wrapper statt verstreuter WebSocket-Aufrufe

Żball frekwenti f’applikazzjonijiet Delphi żviluppati: formoli UI jew moduli ta‘ servizz „jinteraġixxu direttament ma‘ WebSocket“ u allura jkollhom timers, threads u gestjonijiet ta‘ eċċezzjoni mifruxa kullimkien. Aħjar huwa komponent ċar b’events definiti sew u maŝina żgħira ta‘ stati.

Termini f’sintesi: Backoff ifisser perjodu ta‘ stennija li jżid gradwalment wara żballi (eż. 1s, 2s, 4s …), biex ma tgħaġġelx is-server u n-netwerk. CancellationToken huwa segnali ta‘ terminazzjoni mill-kuntest .NET; f’Delphi m’hemm l-ebda mudell identiku, imma nistgħu nimmodellawh b‘TEvent u flag ‚StopRequested‘. TThread.Queue jippjana kodu biex jinħadem fil-main thread (UI), mingħajr ma jibbllokka l-worker; Synchronize jibbllokka u spiss huwa s-sors ta‘ deadlocks fil-paths ta‘ shutdown.

Source-Schnipsel: Delphi WebSocket Client mit Stop, Reconnect und Message-Dispatch

Il-kodiċi li ġej huwa maħsub apposta bħala „Betriebs-Baustein“: klassi li tista‘ tuża fil-VCL/FMX jew f‘Windows- und Linux-Services (skont il-verżjoni/pjattaforma Delphi). Il-kern huwa worker-thread li jżomm il-receive-loop u jirrapporta lill-applikazzjoni permezz ta‘ events.

Delphi
unit Net.WebSocketClientEx;

interface

uses
  System.SysUtils,
  System.Classes,
  System.SyncObjs,
  System.Generics.Collections,
  System.Net.URLClient,
  System.Net.WebSockets;

type
  TWsLogLevel = (llDebug, llInfo, llWarn, llError);

  TWsLogEvent = reference to procedure(Level: TWsLogLevel; const Msg: string);
  TWsTextEvent = reference to procedure(const Text: string);
  TWsStateEvent = reference to procedure(const State: string);

  TDelphiWebSocketClient = class
  private
    FUrl: string;
    FOnLog: TWsLogEvent;
    FOnText: TWsTextEvent;
    FOnState: TWsStateEvent;

    FStopEvent: TEvent;
    FWorker: TThread;

    FMinBackoffMs: Integer;
    FMaxBackoffMs: Integer;
    FHeartbeatSec: Integer;

    procedure Log(Level: TWsLogLevel; const Msg: string);
    procedure State(const S: string);

    procedure Run;
    function NextBackoffMs(const Prev: Integer): Integer;
    function NowUtcStr: string;
  public
    constructor Create(const AUrl: string);
    destructor Destroy; override;

    procedure Start;
    procedure Stop(TimeoutMs: Cardinal = 5000);

    property OnLog: TWsLogEvent read FOnLog write FOnLog;
    property OnText: TWsTextEvent read FOnText write FOnText;
    property OnState: TWsStateEvent read FOnState write FOnState;

    property MinBackoffMs: Integer read FMinBackoffMs write FMinBackoffMs;
    property MaxBackoffMs: Integer read FMaxBackoffMs write FMaxBackoffMs;
    property HeartbeatSec: Integer read FHeartbeatSec write FHeartbeatSec;
  end;

implementation

type
  TBytesBuffer = record
    Data: TBytes;
    Len: Integer;
  end;

{ TDelphiWebSocketClient }

constructor TDelphiWebSocketClient.Create(const AUrl: string);
begin
  inherited Create;
  FUrl := AUrl;
  FStopEvent := TEvent.Create(nil, True, False, '');

  FMinBackoffMs := 500;
  FMaxBackoffMs := 15000;
  FHeartbeatSec := 20; // App-Heartbeat: utli kontra idle-timeouts wara proxies
end;

destructor TDelphiWebSocketClient.Destroy;
begin
  Stop;
  FStopEvent.Free;
  inherited;
end;

procedure TDelphiWebSocketClient.Start;
begin
  if Assigned(FWorker) then
    Exit;

  FStopEvent.ResetEvent;
  FWorker := TThread.CreateAnonymousThread(
    procedure
    begin
      Run;
    end);
  FWorker.FreeOnTerminate := False;
  FWorker.Start;
end;

procedure TDelphiWebSocketClient.Stop(TimeoutMs: Cardinal);
var
  W: TThread;
begin
  FStopEvent.SetEvent;

  W := FWorker;
  if Assigned(W) then
  begin
    if W.WaitFor(TimeoutMs) = wrTimeout then
      Log(llWarn, 'Stop: il-worker ma wieġibx fi żmien il-timeout; possibbli blokk fis-stack tan-netwerk');
    FreeAndNil(FWorker);
  end;
end;

procedure TDelphiWebSocketClient.Log(Level: TWsLogLevel; const Msg: string);
begin
  if Assigned(FOnLog) then
    TThread.Queue(nil,
      procedure
      begin
        FOnLog(Level, NowUtcStr + ' ' + Msg);
      end);
end;

procedure TDelphiWebSocketClient.State(const S: string);
begin
  if Assigned(FOnState) then
    TThread.Queue(nil,
      procedure
      begin
        FOnState(S);
      end);
end;

function TDelphiWebSocketClient.NowUtcStr: string;
begin
  Result := FormatDateTime('yyyy-mm-dd"T"hh:nn:ss.zzz"Z"', TTimeZone.Local.ToUniversalTime(Now));
end;

function TDelphiWebSocketClient.NextBackoffMs(const Prev: Integer): Integer;
var
  N: Integer;
begin
  if Prev <= 0 then
    Exit(FMinBackoffMs);

  N := Prev * 2;
  if N < FMinBackoffMs then N := FMinBackoffMs;
  if N > FMaxBackoffMs then N := FMaxBackoffMs;
  Result := N;
end;

procedure TDelphiWebSocketClient.Run;
var
  WS: TClientWebSocket;
  Backoff: Integer;
  LastHeartbeat: UInt64;
  Msg: string;
  Buf: TBytes;
  Received: TWebSocketReceiveResult;
  SB: TStringBuilder;
  WaitRes: TWaitResult;

  function StopRequested: Boolean;
  begin
    Result := (FStopEvent.WaitFor(0) = wrSignaled);
  end;

  procedure SafeClose;
  begin
    try
      if WS.State = TWebSocketState.Open then
        WS.Close(TWebSocketCloseStatus.NormalClosure, 'client shutdown');
    except
      on E: Exception do
        Log(llDebug, 'Close: ' + E.ClassName + ': ' + E.Message);
    end;
  end;

begin
  Backoff := 0;
  LastHeartbeat := 0;

  while not StopRequested do
  begin
    WS := TClientWebSocket.Create;
    try
      State('connecting');
      Log(llInfo, 'Connect to ' + FUrl);

      try
        // Nota: TClientWebSocket.Connect hija sinkrona u tista' tibblokka skont DNS/TLS.
        // Għalhekk qed taħdem hawn fil-worker.
        WS.Connect(FUrl);
      except
        on E: Exception do
        begin
          State('connect_failed');
          Log(llWarn, 'Connect failed: ' + E.ClassName + ': ' + E.Message);
          Backoff := NextBackoffMs(Backoff);
          WaitRes := FStopEvent.WaitFor(Backoff);
          if WaitRes = wrSignaled then Break;
          Continue;
        end;
      end;

      State('open');
      Backoff := 0;

      SetLength(Buf, 16 * 1024);
      SB := TStringBuilder.Create;
      try
        while (WS.State = TWebSocketState.Open) and (not StopRequested) do
        begin
          // Heartbeat bħala messaġġ tal-app, għax Ping/Pong mhux espost b'mod ċar f'kull verżjoni ta' Delphi.
          if (FHeartbeatSec > 0) then
          begin
            if (LastHeartbeat = 0) or (TThread.GetTickCount64 - LastHeartbeat >= UInt64(FHeartbeatSec) * 1000) then
            begin
              try
                WS.Send('ping');
                LastHeartbeat := TThread.GetTickCount64;
                Log(llDebug, 'Heartbeat ping sent');
              except
                on E: Exception do
                begin
                  Log(llWarn, 'Heartbeat send failed: ' + E.Message);
                  Break;
                end;
              end;
            end;
          end;

          // Receive: ibbażat fuq frames, għalhekk nużaw StringBuilder għall-frammentazzjoni.
          try
            Received := WS.Receive(Buf);
          except
            on E: Exception do
            begin
              Log(llWarn, 'Receive failed: ' + E.ClassName + ': ' + E.Message);
              Break;
            end;
          end;

          case Received.Kind of
            TWebSocketMessageKind.Text:
              begin
                SB.Append(TEncoding.UTF8.GetString(Buf, 0, Received.BytesReceived));
                if Received.EndOfMessage then
                begin
                  Msg := SB.ToString;
                  SB.Clear;

                  if Assigned(FOnText) then
                    TThread.Queue(nil,
                      procedure
                      begin
                        FOnText(Msg);
                      end);
                end;
              end;

            TWebSocketMessageKind.Binary:
              begin
                // F'ħafna protokolli tan-negozju, Text/JSON huwa l-istandard.
                // Binary jista' jiġi bufferjat hawn b'mod simili jew imqassam direttament.
                Log(llDebug, 'Binary frame received: ' + Received.BytesReceived.ToString + ' bytes');
              end;

            TWebSocketMessageKind.Close:
              begin
                Log(llInfo, 'Server requested close');
                Break;
              end;
          end;

          // Sleep żgħir, biex tinżamm il-CPU f'ċirkostanzi ta' loop rapidu.
          // Mhux wisq kbir, inkella l-latenza ssir agħar.
          TThread.Sleep(1);
        end;
      finally
        SB.Free;
      end;

      SafeClose;
      State('closed');

    finally
      WS.Free;
    end;

    if StopRequested then
      Break;

    // Reconnect wara għeluq ordnat jew wara żbalji
    Backoff := NextBackoffMs(Backoff);
    Log(llInfo, 'Reconnect in ' + Backoff.ToString + ' ms');
    WaitRes := FStopEvent.WaitFor(Backoff);
    if WaitRes = wrSignaled then
      Break;
  end;

  State('stopped');
  Log(llInfo, 'Worker stopped');
end;

end.

X’jaħdem il-kodiċi b’mod intenzjonat „differenti“ mill-eżempji tipċi

  • Stop mingħajr forża: Minflok li jiġu „maqtugħin“ il-threads, Stop jisħaq Event. Il-Worker jispiċċa l-loops f’postijiet definiti. Dan jnaqqas il-ħruġ ta‘ hangs waqt it-tmiem u jevita lekkijiet tar-riżorsi fil-socket-stack.
  • Queue minflok Synchronize: Logging u Events jiġu pprovduti permezz ta‘ TThread.Queue lejn il-Mainthread. Dan hu importanti meta Stop/Shutdown jiġu mill-UI jew minn handlers ta‘ Service-Control. Synchronize jista‘ jwaqqaf il-proċess jekk il-Mainthread jkun qed jistenna.
  • Kunsiderazzjoni tal-fragmentazzjoni: Test tal-WebSocket jista‘ jasal f’frames fragmentati. Għalhekk nużaw TStringBuilder u niċċekkjaw EndOfMessage.
  • Heartbeat bħala protocol ta‘ applikazzjoni: Ħafna setups jmutu minħabba idle-timeouts (Load Balancer, nginx, Cloud WAF). Test sempliċi „ping“ bħala test huwa spiss aktar effettiv bħala leva operattiva milli t-tama f'“TCP keepalive“ jew f’API Ping/Pong li mhux dejjem disponibbli.

Kundizzjonijiet ta‘ bażi u trappi fl-operazzjoni

1) DNS, TLS u Proxy: Connect jista‘ jibbloċċa

TClientWebSocket.Connect huwa sinkronu. Skont ir-riżoluzzjoni tal-DNS, TLS-handshake, verifika tas-sertifikat jew ambjent tal-proxy, dan jista‘ jieħu diversi sekondi. Il-kodiċi jpoġġih b’mod konxju f’Worker. Jekk għandek bżonn timeouts stretti, trid tiċċekkja fuq livell ta‘ API jekk il-verżjoni ta‘ Delphi tiegħek tipprovdi għażliet ta‘ timeout, jew tikkapsula Connect f’thread separat u twaqqaf permezz tal-loġika tal-proċess. Importanti: „twaqqif“ hawn normalment ifisser „ħolqot il-konnessjoni bħala ‚maħruqa‘ u restarta l-Worker“, mhux „qtil immedjat tal-operazzjoni tas-socket“.

2) Idle-Timeouts: għaliex Heartbeat spiss huwa meħtieġ

F’infrastrutturi korporattivi WebSocket spiss jintuża warajh Reverse Proxy (nginx, IIS ARR) jew Load Balancer. Ħafna minn dawn il-komponenti jagħlqu l-konnessjonijiet jekk ma jkunux hemm fluss ta‘ data għal perjodi twal. TCP-Keepalive mhux dejjem jista‘ jkun konfigurabbli fuq perjodu qasir biżżejjed (u fuq Windows ħafna drabi huwa f’ċikli ta‘ minuti aktar milli sekondi). Heartbeat fuq livell ta‘ applikazzjoni hu workaround stabbli. Attenzjoni li server u client għandhom l-istess kunċett (per eżempju „ping“/“pong“ bħala test jew JSON).

3) Threading u UI: l-avvenimenti jridu jibqgħu deċouplati

Jekk il-proċessament ta‘ OnText huwa tqal (parsing tal-JSON, aċċess tad-DB b‘BDE-Ablosung bil-konnessjoni nativen, aġġornamenti tal-UI), m’għandux jimblokka kollox fil-Mainthread. Il-wrapper jipprovdi biss il-messaġġ. Mudell tipiku: OnText jpoġġi l-payload f’queue (per eżempju TThreadedQueue<string>), worker separat jipproċessa b’backpressure (jiġifieri tul ta‘ queue limitat). Dan jipprevjeni li burst-load jiffriża l-UI jew jibdel il-fluss tal-akkwist tad-dejta.

Debugging: x’għandek tħalli fil-log meta jinqata‘ xi drabi

WebSockets huma famużi għal sitwazzjonijiet fejn „jaħdmu għal jiem u mbagħad jispiċċaw“. Mingħajr logging huwa diffiċli ħafna li tinqas il-problema. Punti ta‘ log utli:

  • Timestamp (UTC), URL, u bidliet fl-istat (connecting/open/closed).
  • Close-Reason, jekk disponibbli (Server jipperċedi Close vs. żball tan-netwerk).
  • Żbalji fil-mibgħut tal-heartbeat u eċċezzjonijiet fil-ħruġ tal-reċeviment inkluż it-tip tal-eċċezzjoni.
  • Opzjonali: daqs tal-messaġġi miksuba (mhux il-kontenut), biex jiġu identifikati eksplosjonijiet fil-volumi ta‘ data.

Jekk tagħlaq permezz ta‘ TLS: iċċekkja wkoll jekk bidliet fis-sertifikat (skadenza, issuer ġdid) jikkorelazzjoni ma‘ żbalji fit-tul tal-ħin. F’ambjenti imħafffa huma kandidati wkoll proksiji u bokks ta‘ DPI (Deep Packet Inspection).

Varianti: meta System.Net.WebSockets ikun biżżejjed – u meta le

System.Net.WebSockets huwa biżżejjed għal ħafna każijiet ta’ integrazjoni, speċjalment meta tkun qed taħdem maʼ test/JSON, tagħbija moderata u strateġiji taʼ reconnect ċari. Il-limiti jidhru skont il-verżjoni taʼ Delphi u l-mira tal-pjattaforma:

  • Appoġġ Ping/Pong nieqes jew limitat: F’dawk il-każijiet, App-Heartbeat jibqa’ l-mudell robust.
  • Timeouts/Cancelazzjoni nieqsa fil-Connect/Receive: F’dak il-każ għandek tibni l-arkitettura b’mod li worker imsakkar jibqa‘ isolat u l-applikazzjoni xorta tagħlaq nadif (eż. permezz ta‘ watchdog tal-proċess jew b’istanzi separati tal-worker).
  • Tagħbija għolja jew streams binarji: F’dak il-każ jiswa kunċett aktar robust ta‘ framing/buffering (eż. ring buffer, separate Binary-Event, Message-Assembler b’limits).

Għal sitwazzjonijiet legacy (ġenerazzjonijiet anzjani taʼ Delphi, rekwiżiti TLS/Proxy estremament speċifiċi) libreriji bħal ICS f’xi proġetti huma soluzzjoni aktar pragmatika. Aktar importanti mhix tant „liema library“, imma li timmaniġġja Shutdown, Reconnect u Observability (logs/metriki) bħala suġġetti ta‘ livell ewlieni.

Konklużjoni: klient WebSocket ta‘ Delphi huwa komponent operattiv – b’limiti ċari

WebSocket huwa adattat tajjeb għal push-events, live-status, messaġġi ta‘ magni jew proċessi u bħala kanal ta‘ ritorn għal portali u servizzi. Il-wrapper murija jiffoka fuq il-punti li spiss jagħmlu d-differenza f’soluzzjonijiet korporattivi diġitali: reconnect kontrollat, heartbeat kontra idle-timeouts, proċessar tat-test sigur kontra frammentazzjoni u triq ta‘ stop li ma tibqax imsakkra waqt deployment jew update.

Il-limiti tal-użu jibqgħu: jekk għandek bżonn garanziji iebsa għall-abort tal-Connect/Receive f’intervalli ta‘ żmien ristretti jew jekk tniedi rati ta‘ data estremament għoljin, għandek tidħol aktar fil-fond fit-timeouts, speċjalitajiet tal-pjattaforma u, fejn applikabbli, stacks alternattivi. Madankollu, għall-maġġoranza tas-senarji ta‘ integrazione u modernizzazzjoni, client imkapsulat tajjeb u b’log effettiv bħal dak murija hawn huwa bażi solida li tista‘ tintegra fi sistemi Delphi eżistenti.

Jekk trid tdaħħal komponent bħal dan f’arkitettura eżistenti (eż. Layer-3 Arkitektura b’saffijiet ċari ta‘ servizz u UI) jew trid tiddedbuggja disconnects sporadiċi f’kundizzjonijiet reali, nistgħu nagħmlu valutazzjoni mirriżultat magħkom: Ikkuntattjana.

Fil-kuntest professjonali, Heartbeat Ping/Pong għandhom rwol importanti wkoll meta integrazjonijiet, flussi ta‘ data u evoluzzjoni sussegwenti jeħtieġ li jaħdmu flimkien b’mod nadif.

Tiddiskutu proġett jew inizjattiva ta‘ modernizzazzjoni ma‘ Net-Base.

Pass li jmiss

Meta suġġett jissarraf f’proġett reali, l-arkitettura, is-sistema eżistenti u l-operazzjoni għandhom jiġu kkunsidrati flimkien kmien.

Aħna nappoġġjaw mhux biss f'kwistjonijiet puntwali, iżda wkoll meta biċċiet ta' kodiċi sors, temi legacy jew ideat għal portali jridu jsiru proġett korporattiv stabbli u affidabbli.

  • L-istat attwali, l-istat tal-mira u r-riskji tekniċi jiġu vvalutati flimkien.
  • REST, aċċess għad-dejta, portali u Rollout mhux se jiġu posposti bħala konsegwenzi tardivi.
  • Tara kmieni liema triq hija sostenibbli kemm mill‑punt ta’ vista ekonomiku kif ukoll mill‑punt ta’ vista operattiv.

Aqsam il-post

Aqsam dan il-post direttament

LinkedIn, X, XING, Facebook, WhatsApp u E-Mail huma immedjatament disponibbli. Għal Instagram nippreparaw il-link u t-test qasir direttament.

Imejl

Instagram jiftaħ f'tab ġdid. Il-link u t-test qasir jiġu kkopjati qabel fil-clipboard.