![]() |
Server disconnectet Client
hallo dp,
wiedermal ein socket-problem: diesmal möchte ich vom server aus einzelne Clienten disconnecten. beim connecten eines clienten werden die clienten-daten in ein listbox geladen:
Code:
nun möchte ich das man ein item der listbox anklickt, dann auf den 'disconnect' button geht und die
procedure TForm1.ServerSocket1ClientConnect(Sender: TObject;
Socket: TCustomWinSocket); var ib : integer; begin Memo1.Lines.Add(timetostr(now)+' Neuer User betritt den Chat !'); edit10.text := 'User eingeloggt: ' + IntToStr(ServerSocket1.Socket.ActiveConnections); listbox1.Clear; for ib := 0 to Serversocket1.Socket.ActiveConnections - 1 do listbox1.Items.Add(serversocket1.Socket.Connections[ib].RemoteHost + ' ' + serversocket1.Socket.Connections[ib].remoteaddress); end; verbindung zu diesem clienten getrennt wird. oder eigentlich würde mir auch schon 'normaler' Code (ohne listbox) helfen. ;) bisher habe ich folgendes versucht:
Code:
beides funzt aber net !
procedure button1click;
begin serversocket1.socket.connections[0].disconnect(1); // oder das: serversocket1.socket.connections[1].disconnect(1); end; die delphi hilfe konnte mir nicht helfen und aus google wurde ich auch net schlau ! :roll: Frohe Weihnachten ! |
Re: Server disconnectet Client
Moin!
1. Warum löscht bei einem einzelnen Connect eines neuen Clients gleich die Listbox? Warum fügst du nicht einfach den Client der Listbox hinten an? 2. Warum nutzt du nicht die AddObject() Methode von TStrings und fügst den ServerSocket1ClientConnect() übergebenen Socket einfach mit an den Eintrag mit an? Dieser ist immer eindeutig zu der Connection. 3. Mit dem Vorschlag aus 2. wird es beim ServerSocket1ClientDisconnect() einfacher, weil du dann in dem Objects[] Array nachschauen kannst nach dem Socket der sich verabschiedet hat und somit nur noch den einen Eintrag löschen musst bei der ListBox 4. Und mit dem Vorschlag aus 2. wird deine eigentliche Frage (disconnecten eines gewählten Clients) auch einfach lösbar: Den Socket wieder aus dem Objects[] Array der ListBox rausholen, zu einem TCustomWinSocket casten und einfach seine Methdoe Close() aufrufen. Fertig ist die Suppe... MfG Muetze1 |
Re: Server disconnectet Client
Hallo,
Zitat:
aktiven connections wieder rein ! :roll: (Not macht wohl erfinderisch !) Zitat:
Zitat:
Zitat:
|
Re: Server disconnectet Client
Moin!
Zitat:
Delphi-Quellcode:
Procedure TForm1.ServerSocket1ClientConnect(Sender: TObject; Socket: TCustomWinSocket);
Var lTemp : String; Begin Memo1.Lines.Add(timetostr(now)+' Neuer User betritt den Chat !'); edit10.text := 'User eingeloggt: ' + IntToStr(ServerSocket1.Socket.ActiveConnections); // die oben übergebene Instanz "Socket" ist der neu angelegte Socket der für diesen neuen Client // angelegt wurde bzw. dessen Connection verwaltet. Daher können wir von ihm direkt die benötigten // Informationen wie RemoteHost etc abfragen. Ich habe hier mal eine temporäre lokale Variable lTemp // angelegt für den String der in ListBox erscheint. // Beim AddObject() kannst du ein Object mit angeben was dann zu dem Eintrag mit verwaltet wird. Wenn // du also mit Delete einen Eintrag löscht, dann fliegt dieses gleich mit raus. Daher hast du immer // zu jedem Eintrag den Socket und kannst z.B. wenn der Nutzer einen Eintrag in der ListBox ausgewählt // hat auch direkt diesem Client was schicken - einfach aus ListBox1.Items.Objects[] das Objekt rausholen // und zu einem TCustomWinSocket typecasten. lTemp := Socket.RemoteHost + ' ' + Socket.RemoteAddress; ListBox1.Items.AddObject(lTemp, Socket); End; Zitat:
Delphi-Quellcode:
Procedure TForm1.ServerSocket1ClientDisconnect(Sender: TObject; Socket: TCustomWinSocket);
Var i : Integer; Begin // Socket ist eindeutig, daher haben wir durch das Connect schon diesen Socket auch in der ListBox. // Also: ListBox1.Items.Objects[] durchgehen, schauen wo dieser übergebene Socket ist und dann den // Eintrag löschen. Fertig! Memo1.Lines.Add(timetostr(now)+' User verlässt den Chat !'); edit10.text := 'User eingeloggt: ' + IntToStr(ServerSocket1.Socket.ActiveConnections-1); // noch ist ActiveConnections nicht aktuell! For i := 0 To ListBox1.Items.Count-1 Do Begin If ( ListBox1.Items.Objects[i] = Socket ) Then Begin ListBox1.Items.Delete(i); Break; End; End; End; Zitat:
Delphi-Quellcode:
Voraussetzung für alle von mir hier dargelegten Posts ist es, dass MultiSelection ausgeschaltet ist bei der ListBox!
Procedure TForm1.Button1Click(Sender: TObject);
Var lSocket : TCustomWinSocket; Begin // wenn ein Eintrag ausgewählt ist... If ( ListBox1.ItemIndex > -1 ) Then Begin // dann den mit AddObject() hinzugefügten Socket mal rausholen lSocket := ListBox1.Items.Objects[ ListBox1.ItemIndex ] As TCustomWinSocket; // ... und dann einfach den Socket schliessen lSocket.Close; End; End; MfG Muetze1 |
Re: Server disconnectet Client
Ich danke dir erstmal ;)
Ich teste das morgen mal aus ! |
Re: Server disconnectet Client
hallo muetze1,
vielen dank für deine hilfe funzt alles wunderbar ;) warum bin ich da nicht selber drauf gekommen ? :mrgreen: jetzt hab ich nur ( :roll: ) noch ein problem: da alle clienten dann in der listbox abgespeichert werden, steht da ja der host und die ip. wenn nun aber mehrere clienten eingeloggt sind, weiß ich dann nicht mehr welcher listbox-eintrag (also ip + host) nun zu welchem user gehört ! deshalb habe ich versucht den nicknamen mit in die listbox zu speichern: Im ClientConnect steht:
Code:
Nachricht setzt sich aus dem Nicknamen (den der User eingibt) und 'hat den Raum betreten' zusammen.
ClientSocket1.Socket.SendText(xorstring(nachricht, ''));
Da der Nickname eh nur 6 Zeichen haben darf, hab ich ihn wie folgt ausgelesen:
Code:
nun hab ich zwar den nicknamen im serverprogramm, doch wie bekomm ich den nun zu dem entsprechenden
procedure TForm1.ServerSocket1ClientRead(Sender: TObject; Socket: TCustomWinSocket);
var Texta, encode : string; begin texta := Socket.ReceiveText; nick := xorstring(texta, ''); nick:=Copy(nick, 1, 6); showmessage(nick); end; host + ip eintrag in die listbox ?? hab schon versucht das mit bei OnClientConnect einzubauen, aber da ClientRead ja erst nach OnClientConnect ausgeführt wird, funzt das net ! |
Re: Server disconnectet Client
Moin!
Richtig, beim ClientConnect gibt es noch keine Kommunikation und der Client kann daher nur danach seinen Nick schicken. Daher musst du das nachträglich zuordnen. Erstmal aber nochwas zwischendurch: Die ListBox speichert nicht die Sockets - sie hält auch einen Verweis auf diese Instanzen. Der ServerSocket verwaltet und hält diese hauptsächlich. Daher kannst du z.B. auch weiterhin die Connections[] des ServerSockets durchgehen um den Nickname zu zu ordnen. Andere Möglichkeit: Du baust dir ein Objekt - besser eine kleine Klasse - die nix weiter enthält als 2 Variablen im Public Bereich:
Delphi-Quellcode:
Und wenn du nun bei der ListBox einfach anstatt dem Socket folgendes hinzufügst:
TUserSocket = Class
Public Socket : TCustomWinSocket; NickName : String; End;
Delphi-Quellcode:
Somit haben wir es nun so, das wir nicht mehr direkt den Socket reinhängen sondern diese Instanz von TUserSocket die uns beide nötigen Informationen hält: Den Socket und den Nick. Nun fehlt noch die Zuordnung vom Nick:
Procedure TForm1.ServerSocket1ClientConnect(Sender: TObject; Socket: TCustomWinSocket);
Var lTemp : String; lUser : TUserSocket; Begin Memo1.Lines.Add(timetostr(now)+' Neuer User betritt den Chat !'); edit10.text := 'User eingeloggt: ' + IntToStr(ServerSocket1.Socket.ActiveConnections); lUser := TUserSocket.Create; lUser.Socket := Socket; lUser.NickName := ''; // noch unbekannt, sind ja im Connect... lTemp := Socket.RemoteHost + ' ' + Socket.RemoteAddress; ListBox1.Items.AddObject(lTemp, lUser); End;
Delphi-Quellcode:
Nun enthält die TUserSocket in der ListBox auch den Nickname, wenn der denn ankommt.
// Diese Funktion geht nur durch die ListBox durch und sucht den TUserSocket zu dem übergebenen Socket
Function TForm1.FindUserSocket(ASocket : TCustomWinSocket): TUserSocket; Var i : Integer; Begin Result := Nil; For i := 0 To ListBox1.Items.Count-1 Do If ( TUserSocket(ListBox1.Items.Objects[i]).Socket = ASocket ) Then Begin Result := TUserSocket(ListBox1.Items.Objects[i]); Break; End; End; procedure TForm1.ServerSocket1ClientRead(Sender: TObject; Socket: TCustomWinSocket); var lUser : TUserSocket; lText : string; begin lText := Socket.ReceiveText; lUser := FindUserSocket( Socket ); If ( Assigned(lUser) ) Then Begin lText := xorstring(lText, ''); lUser.NickName := Copy(lText, 1, 6); ShowMessage(lUser.NickName); End; End; Dadurch, das in der ListBox nun eine Instanz von TUserObject hängt die wir selber instanziiert haben, müssen wir uns um die Freigabe kümmern und natürlich die anderen Routinen abhändern.
Delphi-Quellcode:
Zu den ClientSocket/ServerSocket als Chat mit Nicks, Channels, etc siehe für Ideen und Hinweise auch den (bisher noch unkommentierten) Code von mir an:
Procedure TForm1.ServerSocket1ClientDisconnect(Sender: TObject; Socket: TCustomWinSocket);
Var i : Integer; lUser : TUserSocket; Begin // Socket ist eindeutig, daher haben wir durch das Connect schon diesen Socket auch in der ListBox. // Also: ListBox1.Items.Objects[] durchgehen, schauen wo dieser übergebene Socket ist und dann den // Eintrag löschen. Fertig! Memo1.Lines.Add(timetostr(now)+' User verlässt den Chat !'); edit10.text := 'User eingeloggt: ' + IntToStr(ServerSocket1.Socket.ActiveConnections-1); // noch ist ActiveConnections nicht aktuell! lUser := FindUserSocket( Socket ); If ( Assigned(lUser) ) Then Begin For i := 0 To ListBox1.Items.Count-1 Do Begin If ( ListBox1.Items.Objects[i] = lUser ) Then Begin ListBox1.Items.Delete(i); Break; End; End; lUser.Free; End; End; Procedure TForm1.Button1Click(Sender: TObject); Var lUser : TUserSocket; Begin // wenn ein Eintrag ausgewählt ist... If ( ListBox1.ItemIndex > -1 ) Then Begin // dann den mit AddObject() hinzugefügten TUserSocket mal rausholen lUser := ListBox1.Items.Objects[ ListBox1.ItemIndex ] As TUserSocket; ShowMessage(lUser.NickName + ' wurde gekickt'); // ... und dann einfach den Socket schliessen lUser.Socket.Close; End; End; ![]() MfG Muetze1 |
Re: Server disconnectet Client
:gruebel:
Code:
bin ich blöd ? Das muss doch bei type rein, oder ?
TUserSocket = Class
da kommt fehlermeldung: Zitat:
|
Re: Server disconnectet Client
das was du da hast ist eine vorwärtsdeklaration... du musst die klasse auch noch irgendwo "richtig" deklarieren (die abschnitte/methoden/felder der klasse etc)
|
Re: Server disconnectet Client
Moin!
Zitat:
Natürlich heisst es so
Delphi-Quellcode:
:wall: :wall: :wall:
Type
TUserSocket = Class Public Socket : TCustomWinSocket; NickName : String; End; MfG Muetze1 |
Re: Server disconnectet Client
:wall:
oh, ich dachte das kommt in den allgemeinen public ! sorry ! :wall: :wall: nochmal vielen dank für deine hilfe ! :wink: wenn ich mein programm dann veröffentliche (natürlich in der dp) werde ich dich dankend erwähnen ;) |
Re: Server disconnectet Client
Moin!
Zitat:
Zitat:
MfG Muetze1 |
Re: Server disconnectet Client
Zitat:
Zitat:
|
Re: Server disconnectet Client
Moin!
Zitat:
MfG Muetze1 |
Re: Server disconnectet Client
:gruebel:
wenn du bei delphi ein neues formular erstellst, steht in der "leeren" Unit bereits ein type !? [ot]was nu mit deinem namen? wieso nicht ? [/ot] |
Re: Server disconnectet Client
Hi,
ich hab mir deinen Source jetzt ma angeguckt, bin an der Umsetzung aber gescheitert. Ich hab genau deinen Source genommen, hat aber nicht funktioniert, d.h. die ListBox blieb ganz leer, weder IP noch Host wurde angezeigt. Aus lauter Programmierfrust ( :wall: ) hab ich nochma drüber nachgedacht und bin auf folgendes gekommen:
Code:
Achso beim Clienten steht dann logischerweise:
procedure TForm1.ServerSocket1ClientConnect(Sender: TObject;
Socket: TCustomWinSocket); var ltemp :string; begin edit10.text := 'User eingeloggt: ' + IntToStr(ServerSocket1.Socket.ActiveConnections); nick := Socket.ReceiveText; nick1 := xorstring(nick, ''); memo1.lines.Add(nick1); nick1:=Copy(nick1, 1, 6); lTemp := Socket.RemoteHost + ' ' + Socket.RemoteAddress + ' ' + nick1; ListBox1.Items.AddObject(lTemp, Socket); end;
Code:
Das funktioniert aber nicht immer, nur manchmal, d.h. es macht was es will !!!!
procedure ClientSocket1ClientConnect;
begin ClientSocket1.socket.sendtext(nachricht); Meistens geht es beim zweiten Mal, also wenn der Client die Verbindung trennt und erneut herstellt dann geht es. Wieso das ? |
Re: Server disconnectet Client
Moin!
Debuggen kann ich dazu nur sagen. Und wenn du den Fehler durch das debuggen nicht findest (jeder muss das mal lernen), dann kannst du uns den Quellcode hier hinten anhängen und mal nachfragen ob es sonst einer von uns tut. Bei dem oben geposteten Code sehe ich keinen grundlegenden Fehler, daher liegt es wahrscheinlich am Zusammenspiel. MfG Muetze1 |
Re: Server disconnectet Client
Ich glaube das hängt irgendwie damit zusammen, dass im OnClientRead der Server die Nachricht ja verarbeiten will, dies dann aber im ClientConnect ja schon getan wurde !
Kann das sein das die beiden sich da gegenseitig in die Quere kommen ? |
Re: Server disconnectet Client
Moin!
OnClientConnect: Wir einmalig beim Verbindungsaufbau aufgerufen. Zu diesem Zeitpunkt wurde der Aufbau der Verbindung mit dem Client abgeschlossen und die Verbindung steht. Es liegen noch keine Daten an, die man empfangen könnte. Wenn man in diesem Ereignis ein ReceiveText o.ä. aufruft, dann ist es mehr als Zufall, wenn dort was zu empfangen wäre. OnClientRead: Dieses Ereignis kann mehrfach aufgerufen werden. Es wird immer ausgelöst, wenn Daten vom Client empfangen wurden und im Buffer abgelegt wurden. Diese können dann im OnClientRead ausgelesen werden. Mit dem Auslesen werden diese Daten auch gleich aus dem Buffer entfernt. Die zu empfangenen Daten können in unterschiedliche Teile zerstückelt sein, es gibt keine Garantie das sie in einem Stück bzw. genauso unterteilt ankommen wie sie losgeschickt wurden. Es kann also vorkommen das 2 nacheinander mit SendText losgeschickte Texte mit einem einzigen ReceiveText empfangen werden. Genausogut kann es mehrere OnClientRead und ReceiveText benötigen um einen Text zu empfangen der auf der Clientseite mit einem einzigen SendText verschickt wurde. Ein Auslesen des Buffers durch ReceiveBuf() oder ReceiveText sollte immer nur im OnClientRead statt finden. Nein, es kann nicht sein, das diese sich behackeln - wenn man beachtet das ein ReceiveBuf()/-Text() nur im OnClientRead stattfinden darf. MfG Muetze1 |
Re: Server disconnectet Client
Aber beim Client-Programm schick ich ja auch im ClientConnect eine Nachricht zum Server.
Die soll ja dann halt im Server auch bei ClientConnect eben lesen. Vielleicht gehts nur nicht weils zu schnell ist, d.h. vielleicht sollte man beim ClientConnect noch ein delay oder sowas einbauen ?? |
Re: Server disconnectet Client
Moin!
Zitat:
Du musst dich von der Vorstellung trennen, beim Connect des Clients dessen NickName zu erfahren - das geht nur im nachhinein. Du musst dir in deinem Programm eine solche Struktur vorhalten, das du den Nicknamen er später erfährst - es geht nicht anders. In meinem oben verlinkten Chat wird das ganze zwar über Streams gelöst aber trotzdem wird dort der NickName auch erst später übermittelt. Trotzdem ist es noch schnell genug als das es nicht auffällt. MfG Muetze1 |
Re: Server disconnectet Client
Ok, du hast Recht ! :thumb:
Gibs nicht ne andere Möglichkeit den Nicknamen "einfach" mit in die ListBox zu bekommen, ohne eine extra Class zu erstellen ? Wenn nicht dann lass ich dieses Feature halt weg ! ;) |
Re: Server disconnectet Client
Moin!
Zitat:
... dann hast du nämlich folgendes: 1. in der Procedure hast du den Socket übergeben bekommen 2. du hast den nick ausgelesen aus dem mit ReceiveText empfangenen 3. du hast die ListBox wo hinten bei Items.Objects[] der Socket drinne steht der dir übergeben wurde 4. Wenn du den Eintrag in 3. gefunden hast, dann kannst du doch einfach seinen Text abändern. MfG Muetze1 |
Re: Server disconnectet Client
Zitat:
Wenn ein Client sich einloggt sendet dieser ja automatisch 'MrX hat Raum betreten'. Nun müsste man doch eigentlich nur ein einziges Mal (für jeden Clienten) diese achricht abfangen und in die Listbox schreiben !? |
Re: Server disconnectet Client
Moin!
Zitat:
Aber dann lass uns doch mal überlegen. Wir müssten uns doch einfach eine Situation raussuchen die nur einmal auftritt und am besten kurz nach dem Verbinden. Also mir fällt da z.B. "Mrx hat den Raum betreten" ein - warum achtest/wartest du nicht auf den Spruch und holst dir da den Nick raus und fügst ihn zur Listbox hinzu - nochmal sollte der Spruch ja nicht kommen. MfG Muetze1 |
Re: Server disconnectet Client
Genau daran hab ich auch schon gedacht.
Vielleicht mit POS ? Aber dann müsste man das gesamte memo in einen string kopieren und wie krieg ich dann noch den nick raus selbst wenn ich 'hat den Raum betreten' gefunden habe ? wie meinst du das, den nick rausholen ? |
Re: Server disconnectet Client
Hi,
nach langem probieren habe ich es jetzt geschafft ! :party: Sobald sich ein User einloggt, wird der Nick mit in der ListBox angezeigt ! Hier der Source:
Code:
Sobald sich ein User einloggt, geht der Timer "an". Und im Timer passiert folgendes:
procedure TForm1.ServerSocket1ClientConnect(Sender: TObject;
Socket: TCustomWinSocket); var wert, wert1, ib : integer; begin edit10.text := 'User eingeloggt: ' + IntToStr(ServerSocket1.Socket.ActiveConnections); lTemp := Socket.RemoteHost + ' ' + Socket.RemoteAddress; timer2.Enabled := true; end;
Code:
Sobald ein Client etwas sendet wird die Nachricht abgefangen und das Item der ListBox zugefügt.
procedure TForm1.Timer2Timer(Sender: TObject);
begin if nick <>'' then begin nick:=Copy(nick, 1, 6); test := ltemp + ' ' + nick; ListBox1.Items.AddObject(test, ServerSocket1); timer2.Enabled := false; end; end; Nun gibt es wieder ein neues Problem: Das User kicken funktioniert jetzt nicht mehr ! :wall: Fehlermeldung: Ungültige Typumwandlung. Dabei hat sich doch eigentlich nix verändert, außer der Wert der in die ListBox eingetragen wird ! :gruebel: Echt schlimm: ein Problem gelöst, kommt ein neues ! :roll: |
Re: Server disconnectet Client
Moin!
Aua aua aua... Deine Variable "nick" ist wohl global und daher fällt deine Methode schonmal flach, wenn sich 2 Nicks/clients direkt nacheinander anmelden, dann haut er die Nicks durcheinander bzw. nutzt den gleichen nick für beide. Dann nochmal ein Zitat von dir: Zitat:
Zitat:
Und warum überhaupt mit dem Timer? Warum? Warum bloss? Woher willst du wissen, das bis zum Ablauf des Timers der Nick angekommen ist? Und woher willst du wissen das es der Nick zu dem Socket ist? Du hast einen ServerSocket der haufenweise Clients haben kann. Dein ganzes Programm sollte im Normalfall Event-driven sein, daher: reagiere auf Ereignisse und handle nicht selber. Zitat:
Funktionen dazu: Pos(), Copy(), Delete() MfG Muetze1 |
Re: Server disconnectet Client
Zitat:
Zitat:
Zitat:
Zitat:
|
Re: Server disconnectet Client
Moin!
Zitat:
Zitat:
Zitat:
Delphi-Quellcode:
Index ist dabei der Index des Eintrages den du ändern möchtest.
ListBox1.Items[Index] := 'Was auch immer';
MfG Muetze1 |
Re: Server disconnectet Client
Zitat:
Zitat:
Aber warum funktioniert denn das kicken jetzt nicht mehr ? :gruebel: |
Re: Server disconnectet Client
Moin!
Zitat:
![]() So hart es klingen mag, aber ich weiss sonst nicht gross wie ich dir das sonst verständlich machen soll... MfG Muetze1 |
Re: Server disconnectet Client
Moin,
ok, ich gucks mir nochmal an. Trotzdem vielen Dank für deine Hilfe ! ;) |
Re: Server disconnectet Client
Moin!
Mir ist noch was eingefallen zur Verdeutlichung: Angenommen wir haben mehrere Komponenten auf einer Form und immer wenn auf einer von diesen geklickt wird, dann wird ein und die gleiche Routine aufgerufen (entspricht der OnClientConnect). Darin fügen wir den Namen der Komponente die geklickt wird einer ListBox hinzu und als Objekt die Komponente selber (so hatte ich es). Du hast es aber nun durch deine Änderungen so gemacht, das du beim Klick den Komponentennamen der ListBox hinzufügst und die Form auf der sie liegen. Nun weiss ich aber nachher nicht mehr, welche Komponente das war, wenn ich später mal in der ListBox nachschaue. Egal wo ich nachschaue, in jedem Eintrag der ListBox steht die Form drinne, aber das interessiert mich nicht, die habe ich und die ist eh immer gleich. Ich will da direkt die Komponente haben... Für die Analogien: Form = ServerSocket1 (TServerSocket) Komponente = Socket (TCustomWinSocket) Instanz MfG Muetze1 |
Re: Server disconnectet Client
Zitat:
Könnte man dann nicht mit ner for-Schleife alle Komponenten (also alle Sockets) durchgehen und wenns die richtige ist, den User rauswerfen ? |
Re: Server disconnectet Client
Moin!
Zitat:
Und zu der Schleife: Warum nicht einfach immer den jeweiligen Socket zum ListBox Eintrag hinzufügen? Dann ist es so einfach wie eh und je... Und selbst mit der Schleife: woher wissen das der jeweilige Eintrag der richtige ist? MfG Muetze1 |
Re: Server disconnectet Client
Aber bloß wie ??? :gruebel:
Geht das eine, geht das andere nicht mehr ! :wall: |
Alle Zeitangaben in WEZ +1. Es ist jetzt 05:32 Uhr. |
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