AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Tutorials Delphi Clientverwaltung mit TServerSocket
Tutorial durchsuchen
Ansicht
Themen-Optionen

Clientverwaltung mit TServerSocket

Ein Tutorial von Arnulf · begonnen am 4. Sep 2005 · letzter Beitrag vom 4. Mai 2006
Antwort Antwort
Seite 1 von 2  1 2      
Arnulf
Registriert seit: 28. Okt 2004
Viele verstehen nicht ganz, wie man Clients mit TServerSocket verwaltet.
Ich verwende dafür Sourcen aus meine Projekt, einfach aus Faulheit.
Also bitte nicht verwirren lassen von den Client Daten.
Der Beitrag erklärt nicht wie man die Sockets verwendet, sondern es geht um die reine Verwaltung der Clients.

Die Serverseitige Clientverwaltung beginnt einfach mal damit, daß man einen Prototypen der Clients erstellt.
Delphi-Quellcode:
type TConnectionInfo = record Used : Boolean;
                              PlayerName : string[255];
                              PlayerPort : string[32];
                              ClientVersion : string[32];
                              AlliedSkin : string[64];
                              AxisSkin : string[64];
                              LoginTime : TDateTime;
                              LastPingTime : TDateTime;
                              Status : integer;
                              OldStatus : integer;
                              CheatStatus : integer;
                              ScreenSize : int64;
                              ScreenTimeOut : TDateTime;
                              ScreenFileName : string[255];
                              Socket : TCustomWinSocket; // den socket hier zu speichern ist wichtig!
                       end;
Prototyp allein genügt nicht, also:
Delphi-Quellcode:
var
Connection: ^TConnectionInfo;
Jetzt kommen wir zum connect event.
Wenn ein Client zum Server verbindet, dann müssen wir für den Client Speicher bereit stellen in Form unseres Prototyps.
Ich hab ein connection maximum für meinen Server - der eigentliche Client teil beginnt also erst nach dem else:

Delphi-Quellcode:
procedure TfMain.ServerSocketClientConnect(Sender: TObject; Socket: TCustomWinSocket);
var ConnI : integer;

begin
      AddLogEntry( 'Client ' + GetFullAdress(Socket) + ' connected.' ); //AddLogEntry ist nur das log memo.

      ConnI := ServerSocket.Socket.ActiveConnections; // wieviele connections sind aktiv?
      if (ConnI = MAX_CONNECTIONS) // MAX_CONECTIONS ist eine constante bei mir 64 user maximal
      then begin
           try
           Socket.SendText('#DISCONNECT' );
           Socket.Close;
           AddLogEntry( 'WARNING: Connection maximum reached - user kicked' );
           except
                 AddLogEntry( 'WARNING: Conn Maximum reached and user kick failed' );
           end;

           end

      else begin
           GetMem(Connection,sizeof(TConnectionInfo));
           try
           Connection^.Used := TRUE;
           Connection^.PlayerPort := 'none';
           Connection^.ClientVersion := 'none';
           Connection^.LoginTime := Time;
           Connection^.LastPingTime := Time;
           Connection^.Socket := Socket;
           Connection^.Status := ST_WAITLOGIN;
           Connection^.OldStatus := ST_WAITLOGIN;
           Connection^.CheatStatus := 0;
           Connection^.ScreenSize := 0;
           Connection^.ScreenTimeOut := Time;
           Connection^.ScreenFileName := 'Not Assigned';
           Connection^.PlayerName := 'Not Assigned';
           Connection^.AlliedSkin := 'none';
           Connection^.AxisSkin := 'none';
           Socket.Data := Connection;
           except
           freemem(Socket.Data);
           socket.Close;
           end;
           end;
end;
Jetzt haben wir also einen Client Record erstellt - das ganze ist in weiterer folge für jeden Client in socket.data zu finden.
Ich kann also jederzeit - jeh nach informationen die ich zu dem jeweiligen Client in meinem Record speicher - auf clientdaten zugreifen, und die dem richtigen Socket zuordnen.

In meinem Fall mußte ich immer wieder einen Client finden dem ich einen bestimmten PlayerPort zugewiesen hab.
PlayerPort hat nichts mit den Tsocket Ports zu tun - das hat in dem Fall nur etwas mit den userdaten zu tun - PlayerPort soll man hier also als Synonym für die Identifikation des Users nehmen ( könnte name oder sonstwas sein ).

Als erstes beispiel mal ClientRead event - ein client schickt etwas an der server - aber welcher Client ist das? und wo soll ich den einordnen:

Delphi-Quellcode:
procedure TfMain.ServerSocketClientRead(Sender: TObject; Socket: TCustomWinSocket);
var tempcon : ^TConnectionInfo;
begin
     TempCon:=Socket.Data;
// was auch immer ich mit dem user mache wenn infos kommen, ich kann jetzt jederzeit
// auf den gesammten record des clients zugreifen:
     if TempCon^.PlayerPort = '1234then ........
// wenn infos kommen kann ich die genausogut dem client zuweisen:
     tempCon^.PlayerPort := floattostr(clientport);
end;
Im fall von Client Read ist das ja ganz nett, aber da hab ich den Socket ja schon und könnte gleich Antworten.
Was aber wenn ich jetzt einem bestimmten client etwas senden will und den Socket nicht gleich ansprechen kann?
Dann müssen wir die Threads durchgehen und nach der richtigen Verbindung suchen.
Ich hab mir dazu gleich eine procedure gebastelt, die mir gleich an den richtigen Client text verschickt.
Ich will also jetzt eine nachricht an den Client mit dem PlayerPort 12345 schicken.

Delphi-Quellcode:
procedure tfmain.ConnSockSend( PlayerPort : String; Text : string );

var scan: integer;
    tempcon: ^TConnectionInfo;

          begin
          try
            for scan := 0 to ServerSocket.Socket.ActiveConnections-1 do
              begin
                TempCon := ServerSocket.Socket.Connections[scan].Data;
                if (TempCon^.PlayerPort = PlayerPort) then
                   ServerSocket.Socket.Connections[scan].SendText( Text ); // break wäre gut oder?
              end;
          except
            AddLogEntry ('ERROR: Socksend method failed');
          end;
          end;
Die Routine läßt sich sicherlich noch optimieren - mit break oder wenn man while statt for verwendet ... egal.

Jedenfalls kann ich jetzt jederzeit mit
ConnSockSend ( '12345', ' Diese Nachricht ist für PlayerPort 12345' ); Text an einen bestimmten User verschicken.

Damit sind wir fast am ende des Tutorials.
Nur bevor jemand sich die Frage stellt, warum so umständlich?
Könnte ich nicht auch das ganze in ein Array oder ähnliches packen und auch dort den socket zu einem User speichern?
Ja das ginge schon, aber die Methode wird uns von TServerSocket zur Verfügung gestellt und hat den großen Vorteil, daß es Thread sicher ist.
Ich weiß einfach welcher Socket mit welchem Client noch aktiv ist und welcher nicht.
Ich erspar mir also im Prinzip einen haufen aufwand mit der Verwaltung von Threads oder Sockets.

Zum schluß nicht vergessen beim disconnect eines Clients:
Delphi-Quellcode:
procedure TfMain.ServerSocketClientDisconnect(Sender: TObject; Socket: TCustomWinSocket);

begin
      freemem(Socket.Data);
      AddLogEntry( 'Client ' + GetFullAdress(Socket) + ' disconnected.' );
end;
Ich hoffe das ganze ist nicht zu kompliziert geworden, aber es demonstriert wohl wie man TServerSocket eigentlich verwendet

Alles Gute
Arnulf
 
Benutzerbild von Jens Schumann
Jens Schumann

 
Delphi 2009 Professional
 
#2
  Alt 4. Sep 2005, 09:55
Hallo,
als mir klar wurde das Du die Clients über Records verwaltest habe ich aufgehört das Tutorial zu lesen. Ein Tutorial, dass in einer objektorientierten Welt Records verwendet ist eher schädlich als dass es nützt. Anfänger könnten dadurch auf die Idee gebracht werden Records zu verwenden.

Wenn ich glaube in einem Projekt einen Record verwenden zu müssen, weiss ich, dass ich einen schweren Designfehler im Projekt habe.

Ausgenommen sind natürlich die Records die man in Richtung Win32 API braucht. Da kann man
sich ja nicht gegen wehren.
  Mit Zitat antworten Zitat
Arnulf
 
#3
  Alt 4. Sep 2005, 11:28
naja dann erzähl mal wie du es machen würdest?
Für jeden Client brauch ich bestimmte daten um diesen zu verwalten.
Ich muß jederzeit drauf zugreifen können und auch jeden laufenden Thread mit den Userdaten abgleichen können.

Ich lass mir sehr gerne erklären wie es anders gemacht wird, weil ich hab mir das alles selbst beigebracht und bin keineswegs der Meinung den Stein der Weisen gefunden zu haben.
Allerdings wäre es mir lieber, wenn mir jemand erklärt wie sowas dann richtig gemacht wird anstatt mir einfach zu sagen, daß es schlecht ist.

Die Methode ist jedenfalls erheblich besser, als das ganze in ein array zu speichern und eine andere Methode hab ich bisher noch nicht gesehen.
Um genau zu sein hab ich noch nirgends gesehen oder nachlesen können, daß irgendjemand die .data eigenschaften von TSocketServer verwendet hat, als ich die entdeckt hab, war ich begeistert. Weil es einfach viel stress erspart wenn man auf die userdaten Thread sicher zugreifen kann.

Arnulf
  Mit Zitat antworten Zitat
Benutzerbild von Jens Schumann
Jens Schumann

 
Delphi 2009 Professional
 
#4
  Alt 4. Sep 2005, 19:37
Zitat von Arnulf:
naja dann erzähl mal wie du es machen würdest?
Mach aus dem Record ein Objekt. Dann stehen Dir auch alle Mechanismen der OOP
zur Verfügung.
  Mit Zitat antworten Zitat
Arnulf
 
#5
  Alt 5. Sep 2005, 00:17
weil ich hab mir das alles selbst beigebracht daran wollte ich nur nochmal kurz erinnern

Im ernst ich hab nichtmal eine Ahnung wovon du redest.
Ich hab von 10 Jahren aufgehört zu Programmieren - das waren die Anfänge von oop.
Und ich hab mit einem seltsamen Projekt den wiedereinstieg gefunden.

Kurz und gut - ich hab keine Ahnung wovon du redest.
Ich weiß auch nicht was so schlecht ist and einem record.

Arnulf
  Mit Zitat antworten Zitat
Arnulf
 
#6
  Alt 2. Okt 2005, 01:26
Ich bin leicht hartnäckig, deshalb prezisiere ich mein unwissen etwas und hoffe auf eine antwort die klar macht was gemeint ist.

Also oop würde doch bedeuten ich soll das ganze ding kapseln und methoden zur verfügung stellen.
Ja aber welche denn? man will ja nur userdaten speichern.
soll ich jetzt add,count,delete und das vielleicht noch für string, integer usw. in ein objekt kapseln? danach vielleicht noch suchmethoden und ein sendtext?

Ich finde das würde so einiges verkomplizieren - sowohl für mich als auch für einen programmierer der das nutzen will.
klar oop ist eine nette sache, mit der ich mich nicht besonders auskenn , aber ich seh einfach den sinn in dem fall nicht.
Auch in einem objekt wäre das doch einfach nur ein record.
Auch wenn ichs schön mit methoden gekapselt hab und weiß gott was damit mache, ist und bleibt es im objekt letzten endes ein record.

Ich lass mich gerne von etwas anderem überzeugen, aber letzten endes will ich ja nur mit einem fertigen component user daten verwalten und das geht schon so wie ichs geschrieben hab.

Und versteh mich hier nicht falsch, ich bin immer bereit dazu zu lernen. Keines wegs bin ich sauer über kritik, aber ich wills dann auch verstehen

Arnulf
  Mit Zitat antworten Zitat
Hybrid
 
#7
  Alt 28. Dez 2005, 15:29
Hi Arnulf.
Erstmal ein großes Dankeschön für dieses Beispiel, wie man vernünftig Clients verwaltet.
Ich habe das ein kleines Problem mit meinem Server / Client.
Wenn ein Client Verbindet, wird
1. kein Log-Eintrag erzeugt, dass ein neuer Client da ist

So wie hier:
(ist bei mir übrigens exakt gleich geblieben)
Delphi-Quellcode:
begin
      AddLogEntry( 'Client ' + GetFullAdress(Socket) + ' connected.' ); //AddLogEntry ist nur das log memo.
Die Prozedur dazu ist simpel:
Delphi-Quellcode:
Procedure AddLogEntry(Log :String);
begin
  Logfile.Add('['+DateTimeToStr(Now)+'] '+Log);
end;
2. wird auch beim Trennen der Verbindung nichts eingetragen. Erst (und jetzt kommt's) wenn man den Server auf Active := false setzt !!


Die TConnectionInfo entspricht exakt der aus deinem Tutorial.
Mein Server und der Client benutzen den Port 25287 und liegen beide auf dem selber Rechner.
(kann es sein, dass Sockets nicht mit Lokalen Verbindungen auf dem selben Port klar kommen?)

////////////
Ok mir fällt gerade auf dass auch beim ClientRead nix gemacht wird.
Nun bin ich verwirrt...
Übrigens: Am Schreiben selbst kann es nicht liegen, da andere Log-Eintragungen, wie z.b. die Verbindung zur MySQL DB, eingetragen werden.


Wenn du noch irgendwelche Infos brauchst (meinetwegen auch meinen Source), dann sag bescheid.

Danke.

mfG Hybrid
  Mit Zitat antworten Zitat
Muetze1
 
#8
  Alt 28. Dez 2005, 15:33
Klingt fast so, als wenn die Event->Methodenzuweisungen im Objektinspektor fehlen bzw. wieder rausgeflogen sind.
  Mit Zitat antworten Zitat
Hybrid
 
#9
  Alt 28. Dez 2005, 15:41
Erstmal danke für die schnelle Antwort
Hab grad nochma nachgeguckt und die Einträge im Inspektor sind alle da.
Das Komischste ist aber, dass die Verbindung aber auf jeden Fall besteht,
da ein senden an alle Clienten mittels...
Server.Socket.Connections[i].SendText(Text); ...funktioniert.
  Mit Zitat antworten Zitat
Muetze1
 
#10
  Alt 28. Dez 2005, 15:43
Ok, der nächste Schritt wäre dann ja ein Brechpunkt in den zugewiesenen Methoden zu setzen und abzuwarten ob sie auslösen.
  Mit Zitat antworten Zitat
Antwort Antwort
Seite 1 von 2  1 2      


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 20:24 Uhr.
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz