Net-Base Περιοδικό

01.06.2026

Delphi Πελάτης WebSocket: ανθεκτική σύνδεση, καθαρός τερματισμός, αξιόπιστη αποσφαλμάτωση

Ένας Delphi WebSocket Client μπορεί γρήγορα να είναι «κάπως συνδεδεμένος» — αλλά σε παραγωγική λειτουργία μετρούν η επανσύνδεση, τα Heartbeats, ο καθαρός τερματισμός και η δυνατότητα αποσφαλμάτωσης. Με έναν πρακτικό wrapper βασισμένο στο System.Net.WebSockets (με Fallback) και ένα απόσπασμα κώδικα για διαχείριση νημάτων και...

01.06.2026

Από το θέμα του περιοδικού στην πρακτική εφαρμογή του έργου

Σχετικές σελίδες υπηρεσιών και τεχνολογίας για το άρθρο

Γιατί ένας Delphi WebSocket Client στην πράξη είναι περισσότερο από «Connect»

Ένας Delphi WebSocket Client στήνεται σε λεπτά: URL, Connect, SendText, έτοιμο. Σε εξατομικευμένο επιχειρησιακό λογισμικό και σε λύσεις κοντά στις διαδικασίες, το ζήτημα εμφανίζεται συνήθως κατά τη λειτουργία: ο Reverse Proxy διακόπτει τις ανενεργές συνδέσεις, οι κινητές ή VPN διαδρομές έχουν σύντομα NAT-Timeouts, αλλάζουν πιστοποιητικά, και στο τερματισμό η διεργασία κολλάει επειδή ένας Receive-Loop εξακολουθεί να είναι μπλοκαρισμένος. Επιπλέον: ένα WebSocket είναι ένα μακροχρόνιο, με κατάσταση κανάλι — εφαρμόζονται επομένως άλλοι κανόνες σε σύγκριση με το κλασικό HTTP/REST (Request/Response, βραχύβιο).

Σε αυτό το απόσπασμα πηγαίου κώδικα δεν πρόκειται για «Hello WebSocket», αλλά για έναν πρακτικό Client-Wrapper με:

  • καθαρό Start/Stop (χωρίς κολλήματα κατά το Shutdown),
  • Receive-Loop με Cancellation (σήμα ακύρωσης) αντί για «Thread kill»,
  • Reconnect με Backoff (ελεγχόμενη επανασύνδεση),
  • Heartbeat ως μοτίβο εφαρμογής (επειδή το Ping/Pong δεν είναι διαθέσιμο παντού),
  • Debug- και Trace-Hooks που βοηθούν πραγματικά σε περιπτώσεις υποστήριξης.

Η υλοποίηση βασίζεται σε System.Net.WebSockets (Delphi RTL· WebSocket-Client-API με TClientWebSocket). Όπου αυτό το επίπεδο RTL δεν είναι διαθέσιμο ή είναι πολύ περιορισμένο σε παλαιότερες εκδόσεις, ένα fallback μέσω βιβλιοθήκης (π.χ. ICS) είναι συχνά χρήσιμο — παρακάτω υπάρχει μια τοποθέτηση.

Σκίτσο αρχιτεκτονικής: ένας Wrapper αντί για διασκορπισμένες κλήσεις WebSocket

Ένα συχνό σφάλμα σε ώριμες Delphi εφαρμογές: οι φόρμες UI ή τα Service-Module «μιλούν απευθείας WebSocket» και έχουν στη συνέχεια χρονοδιακόπτες, Threads και χειριστές εξαιρέσεων διασκορπισμένους παντού. Καλύτερα είναι ένα σαφές δομικό στοιχείο με καλά ορισμένα Events και μια μικρή μηχανή καταστάσεων.

Σύντομη τοποθέτηση όρων: Backoff σημαίνει ένα διάστημα αναμονής που αυξάνεται σταδιακά μετά από σφάλματα (π.χ. 1s, 2s, 4s …), ώστε να μην κατακλύζονται ο server και το δίκτυο. Το CancellationToken είναι ένα σήμα ακύρωσης από τον κόσμο του .NET· σε Delphi δεν υπάρχει ακριβώς το ίδιο πρότυπο, αλλά μπορούμε να το μιμηθούμε με TEvent και ένα flag «StopRequested». Το TThread.Queue προγραμματίζει κώδικα για εκτέλεση στο main thread (UI) χωρίς να μπλοκάρει τον worker· το Synchronize μπλοκάρει και είναι σε διαδρομές Shutdown συχνά ο λόγος για deadlocks.

Απόσπασμα πηγαίου: Delphi WebSocket Client με Stop, Reconnect και Message-Dispatch

Ο παρακάτω κώδικας είναι σκόπιμα δομημένος ως «λειτουργικό δομικό στοιχείο»: μια κλάση που μπορεί να χρησιμοποιηθεί παρόμοια σε VCL/FMX ή σε Windows- και Windows- και Linux-Services (ανάλογα με την έκδοση/πλατφόρμα Delphi). Ο πυρήνας είναι ένας worker-thread που κρατάει τον Receive-Loop και αναφέρει στην εφαρμογή μέσω 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: χρήσιμο για την αποφυγή idle-timeouts πίσω από 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: Worker δεν αποκρίθηκε εντός του timeout; πιθανό μπλοκάρισμα στο στρώμα του δικτύου');
    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
        // Σημείωση: TClientWebSocket.Connect είναι συγχρονική και μπορεί να μπλοκάρει ανάλογα με DNS/TLS.
        // Γι' αυτό τρέχει εδώ στο 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 ως App-μήνυμα, επειδή το Ping/Pong δεν εκτίθεται καθαρά σε κάθε έκδοση της 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: βασισμένο σε frame, συνεπώς StringBuilder για συναρμολόγηση κατατμημένων μηνυμάτων.
          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
                // Σε πολλά επιχειρησιακά πρωτόκολλα το Text/JSON είναι το πρότυπο.
                // Το binary μπορεί να με παρόμοιο τρόπο να αποθηκευτεί σε buffer ή να προωθηθεί απευθείας.
                Log(llDebug, 'Binary frame received: ' + Received.BytesReceived.ToString + ' bytes');
              end;

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

          // Μίνι-ύπνος, για να μειώσει την κατανάλωση CPU σε πολύ γρήγορα loops.
          // Όχι πολύ μεγάλο, αλλιώς επιδεινώνεται η καθυστέρηση.
          TThread.Sleep(1);
        end;
      finally
        SB.Free;
      end;

      SafeClose;
      State('closed');

    finally
      WS.Free;
    end;

    if StopRequested then
      Break;

    // Επανασύνδεση μετά από καθαρό κλείσιμο ή μετά από σφάλματα
    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.

Τι κάνει ο κώδικας σκόπιμα «διαφορετικά» σε σχέση με τα τυπικά παραδείγματα

  • Stop χωρίς βία: Αντί να τερματίζει Threads, το Stop θέτει ένα Event. Ο Worker τερματίζει τα Loops σε καθορισμένα σημεία. Αυτό μειώνει τα «κολλήματα» κατά το κλείσιμο και αποφεύγει διαρροές πόρων στο socket-stack.
  • Queue αντί για Synchronize: Το logging και τα events περνούν μέσω TThread.Queue στον Mainthread. Αυτό είναι σημαντικό όταν Stop/Shutdown προέρχεται από το UI ή από Service-Control-Handler. Το Synchronize μπορεί να μπλοκάρει εάν ο Mainthread βρίσκεται σε κατάσταση αναμονής.
  • Λαμβάνεται υπόψη η τμηματοποίηση: Το WebSocket-κείμενο μπορεί να φτάνει θραυσματοποιημένο σε frames. Γι’ αυτό χρησιμοποιείται ο TStringBuilder και ελέγχεται το EndOfMessage.
  • Heartbeat ως πρωτόκολλο εφαρμογής: Πολλές εγκαταστάσεις αποτυγχάνουν λόγω idle-timeouts (Load Balancer, nginx, Cloud WAF). Ένα ελαφρύ κείμενο «ping» λειτουργεί ως χειριστήριο λειτουργίας συχνά πιο αξιόπιστα από την ελπίδα σε «TCP keepalive» ή ένα Ping/Pong-API που δεν υπάρχει παντού.

Περιορισμοί και παγίδες στη λειτουργία

1) DNS, TLS και Proxy: Connect μπορεί να μπλοκάρει

TClientWebSocket.Connect είναι συγχρονική. Ανάλογα με την ανάλυση DNS, το TLS-handshake, τον έλεγχο πιστοποιητικού ή το περιβάλλον proxy, αυτό μπορεί να διαρκέσει αρκετά δευτερόλεπτα. Ο κώδικας το τοποθετεί εσκεμμένα σε έναν Worker. Εάν χρειάζεστε επιπλέον αυστηρά timeouts, πρέπει σε επίπεδο API να ελέγξετε εάν η έκδοση Delphi σας παρέχει επιλογές timeout, ή να ενθυλακώσετε το Connect σε ξεχωριστό Thread και να διακόψετε μέσω λογικής διεργασίας. Σημαντικό: «ακύρωση» σημαίνει εδώ συνήθως «σημειώνετε τη σύνδεση ως κατεστραμμένη και ανανεώνετε τον Worker», όχι «να σκοτώσετε άμεσα μια socket-ενέργεια».

2) Idle-Timeouts: γιατί το Heartbeat συχνά είναι υποχρεωτικό

Σε εταιρικά δίκτυα ένα WebSocket συχνά τερματίζεται πίσω από έναν Reverse Proxy (nginx, IIS ARR) ή έναν Load Balancer. Πολλά από αυτά τα στοιχεία κλείνουν συνδέσεις όταν για μεγάλο χρονικό διάστημα δεν ρέουν δεδομένα. Το TCP-Keepalive δεν είναι πάντα ρυθμισμένο αρκετά σύντομα (και υπό Windows συχνά μετριέται σε λεπτά και όχι σε δευτερόλεπτα). Ένα Heartbeat σε επίπεδο εφαρμογής αποτελεί συνεπώς έναν σταθερό workaround. Φροντίστε ο Server και ο Client να έχουν το ίδιο μοντέλο (π.χ. «ping»/«pong» ως κείμενο ή JSON).

3) Threading και UI: τα γεγονότα πρέπει να παραμένουν αποσυνδεδεμένα

Εάν η επεξεργασία OnText είναι βαριά (JSON-Parsing, προσβάσεις σε DB με BDE-Ablosung mit nativer Anbindung, ενημερώσεις UI), δεν πρέπει να μπλοκάρει τα πάντα στον Mainthread. Ο wrapper παρέχει μόνο το μήνυμα. Ένα τυπικό μοτίβο είναι: το OnText τοποθετεί το payload σε μια Queue (π.χ. TThreadedQueue<string>), ένας ξεχωριστός Worker το επεξεργάζεται με Backpressure (δηλαδή περιορισμένο μέγεθος Queue). Αυτό αποτρέπει το πάγωμα του UI ή την απώλεια ρυθμού λήψης σε περίπτωση burst-φορτίου.

Debugging: τι πρέπει να καταγράφετε όταν «μερικές φορές» διακόπτεται

Τα WebSockets έχουν τη φήμη «τρέχουν για μέρες και μετά όχι». Χωρίς logging είναι δύσκολο να περιοριστεί το πρόβλημα. Σημεία καταγραφής με νόημα:

  • Χρονοσφραγίδα (UTC), URL και αλλαγές κατάστασης (connecting/open/closed).
  • Close-Reason, εφόσον διαθέσιμο (Server ζητά Close vs. σφάλμα δικτύου).
  • Σφάλματα αποστολής Heartbeat και εξαιρέσεις στη λήψη, συμπεριλαμβανομένου του τύπου Exception.
  • Προαιρετικά: μεγέθη των ληφθέντων μηνυμάτων (όχι τα περιεχόμενα), για να εντοπιστεί έκρηξη δεδομένων.

Εάν τερματίζετε μέσω TLS: ελέγξτε επιπλέον εάν αλλαγές πιστοποιητικού (λήξη, νέος Issuer) συσχετίζονται χρονικά με σφάλματα. Σε σκληροποιημένα περιβάλλοντα, Proxy- και DPI-συσκευές (Deep Packet Inspection) είναι επίσης πιθανοί ύποπτοι.

Επιλογές: πότε αρκεί το System.Net.WebSockets — και πότε όχι

System.Net.WebSockets είναι επαρκές για πολλές περιπτώσεις ενσωμάτωσης, ειδικά όταν πρόκειται για Text/JSON, μέτριο φορτίο και σαφείς στρατηγικές επανασύνδεσης. Τα όρια εμφανίζονται ανάλογα με την έκδοση του Delphi και τον στοχευόμενο στόχο πλατφόρμας:

  • Ανεπαρκής/περιορισμένη υποστήριξη Ping/Pong: Τότε το App-Heartbeat παραμένει το ανθεκτικό πρότυπο.
  • Έλλειψη Timeouts/Cancellation στο Connect/Receive: Τότε πρέπει να σχεδιάσετε την αρχιτεκτονική έτσι ώστε ένας κολλημένος Worker να παραμένει απομονωμένος και η εφαρμογή να τερματίζει καθαρά (π.χ. με watchdog διεργασιών ή ξεχωριστές Worker-Instanzen).
  • Υψηλό φορτίо ή δυαδικά streams: Τότε αξίζει ένα ισχυρότερο σχήμα framing/buffering (π.χ. ring buffer, ξεχωριστό Binary-Event, Message-Assembler με όρια).

Για Legacy καταστάσεις (παλαιότερες γενιές Delphi, πολύ συγκεκριμένες απαιτήσεις TLS/Proxy) βιβλιοθήκες όπως η ICS είναι σε ορισμένα έργα πιο πρακτικές. Σημαντικότερο από το «ποια Library» είναι να αντιμετωπίζετε το Shutdown, το Reconnect και την Observability (logs/μετρήσεις) ως θέματα πρώτης κατηγορίας.

Συμπέρασμα: ένας Delphi WebSocket Client είναι ένα στοιχείο λειτουργίας — με σαφή όρια

Ένα WebSocket είναι εξαιρετικό για Push-Events, Live-Status, μηνύματα μηχανών ή διεργασιών και ως κανάλι επιστροφής για portals και υπηρεσίες. Ο παρουσιαζόμενος Wrapper επικεντρώνεται σε σημεία που στις ψηφιακές επιχειρησιακές λύσεις συχνά κάνουν τη διαφορά: ελεγχόμενη επανασύνδεση, Heartbeat έναντι idle-timeouts, fragment-safe επεξεργασία κειμένου και μια διαδρομή τερματισμού που δεν κολλάει κατά το deployment ή update.

Τα όρια χρήσης παραμένουν: εάν χρειάζεστε σκληρές εγγυήσεις για την ακύρωση Connect/Receive σε πολύ στενά χρονικά παράθυρα ή έχετε εξαιρετικά υψηλούς ρυθμούς δεδομένων, πρέπει να εμβαθύνετε σε timeouts, ιδιαιτερότητες πλατφόρμας και ενδεχομένως εναλλακτικά stacks. Για την πλειονότητα των σεναρίων ενσωμάτωσης και εκσυγχρονισμού, ωστόσο, ένας καλά απομονωμένος, καλά καταγεγραμμένος client όπως ο παραπάνω αποτελεί σταθερή βάση που ενσωματώνεται σε ωριμασμένα Delphi συστήματα.

Εάν θέλετε να εντάξετε ένα τέτοιο στοιχείο σε μια υπάρχουσα αρχιτεκτονική (π.χ. Layer-3 Αρχιτεκτονική με σαφή στρώματα υπηρεσιών και UI) ή να αποσφαλματώσετε σποραδικές αποσυνδέσεις σε πραγματικές συνθήκες, μπορούμε να το αξιολογήσουμε στοχευμένα μαζί σας — επικοινωνήστε μαζί μας.

Στο επιχειρησιακό πλαίσιο το Heartbeat Ping/Pong παίζει επίσης σημαντικό ρόλο όταν ενσωματώσεις, ροές δεδομένων και περαιτέρω ανάπτυξη πρέπει να συνεργάζονται με συνέπεια.

Συζητήστε έργο ή έργο εκσυγχρονισμού με Net-Base.

Επόμενο βήμα

Όταν από το θέμα προκύψει ένα πραγματικό έργο, η αρχιτεκτονική, η υφιστάμενη κατάσταση και η λειτουργία πρέπει να εξεταστούν έγκαιρα από κοινού.

Wir unterstuetzen nicht nur bei Einzelfragen, sondern auch dann, wenn aus Source-Schnipseln, Legacy-Themen oder Portalideen ein belastbares Unternehmensprojekt werden soll.

  • Η υφιστάμενη κατάσταση, το επιθυμητό μελλοντικό μοντέλο και οι τεχνικοί κίνδυνοι αξιολογούνται από κοινού.
  • REST, η πρόσβαση στα δεδομένα, οι πύλες και το rollout δεν αναβάλλονται ως μετέπειτα συνέπειες.
  • Αναγνωρίζετε έγκαιρα ποια προσέγγιση είναι οικονομικά και λειτουργικά βιώσιμη.

Κοινοποίηση δημοσίευσης

Μοιραστείτε αυτήν την ανάρτηση απευθείας

LinkedIn, X, XING, Facebook, WhatsApp und E‑Mail είναι άμεσα διαθέσιμα. Για το Instagram ετοιμάζουμε άμεσα τον σύνδεσμο και το σύντομο κείμενο.

Ηλεκτρονικό ταχυδρομείο

Το Instagram ανοίγει σε μια νέα καρτέλα. Ο σύνδεσμος και το σύντομο κείμενο αντιγράφονται πρώτα στο πρόχειρο.