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.
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.