Delphi-PRAXiS
Seite 3 von 3     123   

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Netzwerke (https://www.delphipraxis.net/14-netzwerke/)
-   -   Delphi Indy TCPServer beenden mit toten Clients (https://www.delphipraxis.net/168753-indy-tcpserver-beenden-mit-toten-clients.html)

hsg 12. Jun 2012 14:48

AW: Indy TCPServer beenden mit toten Clients
 
Zitat:

Zitat von mjustin (Beitrag 1170436)
Zitat:

Zitat von hsg (Beitrag 1170424)
So, nun werde ich meine Delphi-Umgebung zerstören in dem ich die aktuelle Indy-Version einspiele :-D

Sicherheitshalber installiere ich Indy nie als Komponenten in der IDE, das dauert eh viel zu lange - stattdessen setze ich nur für das Projekt die Pfade Indy/Lib/Core, Prtotocols und System (und lasse die Orignalversion von Indy installiert). Aber "zerstören" ist noch ein zu sanftes Wort für den Indy-Effekt, den ich auch schon kennenlernte :)

Wie installierst du denn das Zeug?
Wenn ich das richtig sehe, muss ich ja dieses Fulld10.bat (BDS2006) aufrufen, aber dann installiert der ja alles in irgendwelchen vordefinierten Verzeichnissen, oder?
Ein ReadMe im Zip-File wäre nett gewesen :(
Wenn du nur die Pfade setzt, wie stellst du sicher, dass nicht die fertigen Pakete dir in die Suppe spucken?

Für heute gebe ich erst mal auf, denn es ist endlich Feierabend! (und wieder nichts geschafft :( )

mjustin 12. Jun 2012 16:11

AW: Indy TCPServer beenden mit toten Clients
 
Zitat:

Zitat von hsg (Beitrag 1170471)
Wie installierst du denn das Zeug?
Wenn ich das richtig sehe, muss ich ja dieses Fulld10.bat (BDS2006) aufrufen, aber dann installiert der ja alles in irgendwelchen vordefinierten Verzeichnissen, oder?
Ein ReadMe im Zip-File wäre nett gewesen :(
Wenn du nur die Pfade setzt, wie stellst du sicher, dass nicht die fertigen Pakete dir in die Suppe spucken?

Nach dem Entpacken hat man die Quelltexte in den Verzeichnisse /Indy10.5.8/Lib/Source, Protocols und System. Wenn Delphi diese in den Projektsuchpfaden findet, werden die installierten Indy Packages nicht verwendet und es gibt keine Konflikte. (Ausser in den Fällen in denen man DCU Dateien ohne Source in den Pfaden hat, die mit einer anderen Indy Version kompiliert wurden).

Wenn in allen meinen Projekten keine Designtimekomponenten von Indy (oder davon abhängige andere Komponenten) benutzt werden, kann ich die Indy Packages auch deinstatallieren, zumindest in den installierten Packages abwählen.

hsg 13. Jun 2012 05:57

AW: Indy TCPServer beenden mit toten Clients
 
So, habe nun die neuere Version der Indys probiert. Leider das gleiche Ergebnis.

Ich werde mir jetzt mal das ganze mit dem ICS-Zeugs ansehen. Leider ist die Dokumentation von den Komponenten mehr als dürftig :-( Wenn also noch jemand ein paar Links hat, immer her damit!

taveuni 13. Jun 2012 08:02

AW: Indy TCPServer beenden mit toten Clients
 
Da hats massig Beispiele dabei (OverbyteIcsV7\Delphi\Internet\).
Ausserdem eine aktive Newsgroup.

DataCool 13. Jun 2012 08:21

AW: Indy TCPServer beenden mit toten Clients
 
Hi,

wäre es möglich Deinen gesamten Code des OnExecute Events des Servers zu posten ?
Ich habe auch schon lange mit den Problem gekämpft, habe aber jetzt keinerlei Probleme!
Man muss nur ein Paar Sachen beachten:

- GANZ GANZ wichtig keine Exception schlucken, diese müssen zwingend im OnExecute auch auftreten,
ansonsten merkt "die kontrollierende Instanz" von Indy nicht, das überhaupt ein Fehler aufgetreten ist;
Erst beim nächsten WriteLn/ReadLn

- Bitte füge mal folgenden Code ganz oben im OnExecute ein:
Delphi-Quellcode:
  AContext.Connection.IOHandler.CheckForDataOnSource(500); // millisekunden, nach Bedarf varieren
  if not AContext.Connection.Connected then exit;
  if not AContext.Connection.IOHandler.InputBufferIsEmpty then
  begin
    // hier Deinen ganz normalen Ablauf implementieren
  end
  else begin
    // Keine Daten im InputBuffer; theoretisch muss nichts gemacht werden; auch kein SLEEP !
    //
    // hier könnte man gut überprüfen, wann der letzte "Kontakt"/Datenaustausch mit dem Client war
    // ist dieser länger her als Dein definierter Timeout, dann einfach etwas in die Connection schreiben
    // das kann ein "NOOP" Kommando sein, aber auch ein Kommando auf das der Client gar nicht kennt
    // das Schreiben dient nur dazu um wirklich eine Exception auszulösen, die Indy dann handeln kann.
    // Bestehend die Connection noch kommt das Kommando normal beim Client an und
    //kann behandelt oder ignoriert werden
    AContext.connection.Socket.WriteLn('NOOP');
    // oder:
    // AContext.connection.Socket.WriteLn('STFU'); // ;-)
  end;
- Des Weiteren ist es sehr zu empfehlen, wie auch schon mehrfach vorher erwähnt wurde,
das Protokoll so aufzubauen das es ein "NOOP" Kommando gibt,
ebenso ein "QUIT/EXIT" Kommando

- Ich für meinen Teil gehe immer so wat das ich mit ein TClient-Objekt erstelle und diese in der Eigenschaft
AContext.Data mitführe. Zusätzlich zu den Eigenschaft die das TClient-Objekt definieren,
führe ich immer noch eine Property LastContact oder LastTimeStamp oder whatever mit,
die mir die Information liefert, wann zum letzten mal Erfolgreich Daten gelesen oder geschrieben wurden.

Wenn das alles beherzigt wird, gibt es keinerlei Probleme mit "Zombie" Clients.

Greetz Data

hsg 13. Jun 2012 10:26

AW: Indy TCPServer beenden mit toten Clients
 
Liste der Anhänge anzeigen (Anzahl: 1)
Zitat:

Zitat von DataCool (Beitrag 1170574)
Hi,

...
Wenn das alles beherzigt wird, gibt es keinerlei Probleme mit "Zombie" Clients.

Greetz Data

Hi,

ich habe die (reduzierte) Datei mal angehängt. Deine Hinweise sind entsprechend eingebaut.
Im gesamten Programm werden im Moment nur ganz wenige Exceptions abgefangen (und auch nur an den dringendsten Stellen).

Wir führen bereits ein TClient-Objekt mit, allerdings ohne TimeStamp oder dergleichen.
Das Noop-Kommando habe ich jetzt für den Versuch mit eingebaut, wird aber in der Realität nicht möglich sein, da es ein paar ältere Clients gibt, die mit den NOOP-Meldungen nicht zurande kommen werden (Update auf neue Version wahrscheinlich nicht möglich)

Ich weise noch mal (vorsichtshalber) darauf hin: Es gibt nur dann Probleme, wenn der Clientrechner im Netzwerk (physikalisch) nicht mehr erreichbar ist.
Egal, ob Clientprogramm läuft oder nicht.

Das ist auch in der jetzigen Version so.

Bei Bedarf (und jemand sich mit dem netten DeviceEmulator von Microsoft auskennt) kann ich auch den Client zur Verfügung stellen Damit kann man dann recht elegant das Phänomen deutlich machen.

DataCool 13. Jun 2012 11:21

AW: Indy TCPServer beenden mit toten Clients
 
Liste der Anhänge anzeigen (Anzahl: 1)
Hi Hsg,

ich habe mir das ganze gerade mal auf die schnelle angeschaut
und ein paar kleine Änderungen gemacht ohne Testmöglichkeit, deshalb ohne Gewähr auf Richtig.

Erstmal habe ich innerhalb der Procedure DisConnectAllClients
das Senden mit Try except gekapselt, damit auch alle Clients wirklich die Nachricht bekommen
und nicht wenn bei z.b. Client2 eine Exception während des Senden auftritt und die restlichen Clients dann
nicht mehr benachrichtigt werden.
Des Weiteren habe ich LockList und UnlockList mit Try finally abgesichert !! Unbedingt zu empfehlen !!!
Delphi-Quellcode:
procedure TPDAServer.DisConnectAllClients();
var i       : Integer;
    oCont   : TIdContext;
    oList   : TList;

begin
  // Benachrichtige die Clients vom Ableben:
  // ACHTUNG: Innerhalb der IDE tritt irgendwo eine EIdClosedSocked Exception auf
  // das ist wohl ein bekanntes Problem, ist in der EXE wohl nicht der Fall!
  if oServer <> nil then begin
    oList := oServer.Contexts.LockList;
    try
      if oList.Count > 0 then begin
        for i:=0 to oList.Count -1 do begin
          oCont          := TIdContext(oList[i]);
          if (oCont <> nil) and (oCont.Connection <> nil) then begin
            try
              // nach dem Aufruf von CheckDataOnSource kannst Du Dich auch auf die Property connected verlassen
              oCont.Connection.AContext.Connection.IOHandler.CheckForDataOnSource(100);
              if oCont.Connection.Connected then begin
                Info('Aufforderung zur Abmeldung für Client: ' + GetHostName(oCont));
                oCont.Connection.IOHandler.Writeln('CLOSE_REQUEST@      ');
              end;
    //          oCont.Connection.Disconnect(false);
            except
              on e:exception do begin
                {$IFDEF DEBUG}
                OutputDebugString(PChar(e.Mesage));
                {$ENDIF}
              end;
            end;
          end; // if oCont <> nil
        end;
      end;
    finally
      oServer.Contexts.UnlockList();
    end;
  end; // if oServer <> null
end;
Weitere Änderung im OnExecute:
Delphi-Quellcode:
// .. snip
      //AContext.Connection.IOHandler.CheckForDisconnect(False, True);
      //AContext.Connection.CheckForGracefulDisconnect(False);
      // Damit Exceptions auch ausgelöst und weitergereicht werden, geändert zu:
      AContext.Connection.IOHandler.CheckForDisconnect(true, True);
      AContext.Connection.CheckForGracefulDisconnect(true);

      // Meiner Meinung nach sogar beides überflüssig an dieser Stelle
      // Wenn's hier benutzt wird, dann mit Exception

// .. snip
Ein weiterer sehr entschiedener Punkt ist:
Wo wird DisConnectAllClients aufgerufen, in Deinem Sourcecode Auszug ist kein Aufruf davon zu finden!
Und gerade im Setter der Active Eigenschaft Deines Servers, solltest Du vorm setzen der Eigenschaft
Active = false, woher alle Clients benachrichtigen, das der Server heruntergefahen wird und die Clients
die Verbindung trennen sollen.

Delphi-Quellcode:
procedure TPDAServer.SetActive(const lAct: Boolean);
var
    dtTimeout : TDateTime;
    iClientCount : Integer;

    // etwas unschön hier reingequescht, kann/darf gerne woanders positioniert werden
    function getClientCount : Integer;
    var oList    : TList;
    begin
      oList := oServer.Contexts.LockList;
      try
        result := oList.Count;
      finally
        oServer.Contexts.UnlockList();
      end;
    end;

begin

  if not lAct then begin
    // Server soll beendet werden, vorher alle noch aktiven Clients benachrichtigen
    DisConnectAllClients;

    // maximalen Timeout berechnen, der auf Clients gewartet wird
    dtTimeOut := now + 10/24/60/60; // jetzt + 10 Sekunden

    iClientCount := getClientCount;
    while (iClientCount > 0) and (now < dtTimeout) do begin
      // Application.Processmessages; // eventuell bei Bedarf
      Sleep(250); // warten ...
      iClientCount := getClientCount;
    end;

  end;
  oServer.Active   := lAct;
end;
Das sollte Dein Problem jetzt aber lösen (hoff ich)

Greetz Data

hsg 14. Jun 2012 05:59

AW: Indy TCPServer beenden mit toten Clients
 
Hallo Data,

Zitat:

Zitat von DataCool (Beitrag 1170629)
Hi Hsg,

ich habe mir das ganze gerade mal auf die schnelle angeschaut
und ein paar kleine Änderungen gemacht ohne Testmöglichkeit, deshalb ohne Gewähr auf Richtig.

Es war nur ein kleiner Fehler drin :)

Zitat:

Zitat von DataCool (Beitrag 1170629)
Erstmal habe ich innerhalb der Procedure DisConnectAllClients
das Senden mit Try except gekapselt, damit auch alle Clients wirklich die Nachricht bekommen
und nicht wenn bei z.b. Client2 eine Exception während des Senden auftritt und die restlichen Clients dann
nicht mehr benachrichtigt werden.
Des Weiteren habe ich LockList und UnlockList mit Try finally abgesichert !! Unbedingt zu empfehlen !!!

Ein weiterer sehr entschiedener Punkt ist:
Wo wird DisConnectAllClients aufgerufen, in Deinem Sourcecode Auszug ist kein Aufruf davon zu finden!
Und gerade im Setter der Active Eigenschaft Deines Servers, solltest Du vorm setzen der Eigenschaft
Active = false, woher alle Clients benachrichtigen, das der Server heruntergefahen wird und die Clients
die Verbindung trennen sollen.

Ich habe ein paar Try-Excepts rausgeworfen gehabt, damit ich sicherstellen konnte, dass nicht alle Exceptions abgefangen werden und nicht mehr durchgereicht werden. Mit dem Try...finally hast du natürlich recht.

Das DisconnectAllClients wird vom Hauptfenster aufgerufen. Ich hatte es ursprünglich mal an der SetActive-Stelle drin, gab dann aber Probleme beim Beenden auch unter normalen Bedingungen. Das Application.ProcessMessages ist an der Stelle entscheidend, denn ansonsten können die Clients sich nicht erfolgreich abmelden. Das gesamte Programm ist so geschrieben, dass es sowohl als Dienst als auch als Fenster-Programm kompiliert werden kann. Daher habe ich in der uPdaServer.pas kein definiertes Applikations-Fenster zur Verfügung und deswegen den entsprechenden Aufruf von DisConnectAllClients in die Verwaltungsroutine geschoben.

Delphi-Quellcode:
procedure TMainForm.StopServer;
var
  i: Integer;
begin
  if oServer <> nil then begin

    mleInfo.Lines.Add('Warten auf Abmeldung Clients');
    oServer.DisConnectAllClients();
    for i := 0 to 3 do begin
      Sleep(1000);
      Application.ProcessMessages();
    end; // for i := 0 to
    mleInfo.Lines.Add('Server wird gestoppt');

    oServer.Active                 := False;
    FreeAndNil(oServer);

    btnClearLog.Click;
    btnServer.Tag                  := 0;
    btnServer.Caption              := 'Server starten';
    btnGetClientList.Enabled       := False;
  end;
end;
Zitat:

Zitat von DataCool (Beitrag 1170629)
Das sollte Dein Problem jetzt aber lösen (hoff ich)

Greetz Data

leider nein :(

Gruß
hsg

Edit:
iClientCount ist beim oServer.Active := false größer als Null. Der TimeOut ist in deiner Schleife die Abbruchbedingung.


Alle Zeitangaben in WEZ +1. Es ist jetzt 00:48 Uhr.
Seite 3 von 3     123   

Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz