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 = '1234' then ........
// 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