I jak zwykle zacznę od kodu.
unit Server_Unit;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls, ContNrs, SyncObjs,
NewConnections, CheckDisconnects, SendRecvData, Clients;
type
{ TForm1 }
TForm1 = class(TForm)
bStart: TButton;
bStop: TButton;
bClose: TButton;
eConsole: TEdit;
eServerStatus: TEdit;
lServerStatus: TLabel;
mConsole: TMemo;
procedure bCloseClick(Sender: TObject);
procedure bStartClick(Sender: TObject);
procedure bStopClick(Sender: TObject);
procedure eConsoleKeyPress(Sender: TObject; var Key: char);
procedure FormCreate(Sender: TObject);
procedure FormResize(Sender: TObject);
private
{ private declarations }
procedure ParseCommand(Cmd : String; var RealCmd, Par1, Par2 : String);
public
{ public declarations }
NewConnection : TNewConnection;
CheckDisconnect : TCheckDisconnect;
SendRecvData : TSendRecvData;
end;
var
Form1 : TForm1;
Client : TFPObjectList;
CritSect : TCriticalSection;
implementation
{$R *.lfm}
{ TForm1 }
procedure TForm1.ParseCommand(Cmd : String; var RealCmd, Par1, Par2 : String);
var
tmpCommand : String;
tmpPar1,
tmpPar2 : String;
CharNumber1,
CharNumber2, i : Word;
Begin
tmpCommand := '';
tmpPar1 := '';
tmpPar2 := '';
CharNumber1 := 0;
CharNumber2 := 0;
for i := 1 to Length(cmd) do
if cmd[i] <> ' ' then
tmpCommand := tmpCommand + UpCase(cmd[i])
else
begin
CharNumber1 := i;
break;
end;
If CharNumber1 > 0 then
for i := CharNumber1 + 1 to Length(cmd) do
if cmd[i] <> ' ' then
tmpPar1 := tmpPar1 + UpCase(cmd[i])
else
begin
CharNumber2 := i;
break;
end;
If CharNumber2 > 0 then
for i := CharNumber2 + 1 to Length(cmd) do
tmpPar2 := tmpPar2 + UpCase(cmd[i]);
Par1 := tmpPar1;
Par2 := tmpPar2;
RealCmd := tmpCommand;
end;
procedure TForm1.FormResize(Sender: TObject);
begin
bStart.Left := 10;
bStart.Top := 10;
bStop.Left := bStart.Left;
bStop.Top := BStart.Height + 10;
lServerStatus.Left := bStop.Left;
lServerStatus.Top := bStop.Top + bStop.Height + 5;
eServerStatus.Left := lServerStatus.Left;
eServerStatus.Top := lServerStatus.Top + lServerStatus.Height + 5;
bClose.Left := eServerStatus.Left;
bClose.Top := Form1.Height - bClose.Height - 10;
mConsole.Left := Form1.Width div 2;
mConsole.Top := 0;
mConsole.Width := Form1.Width div 2 - 5;
mConsole.Height := Form1.Height - eConsole.Height - 10;
eConsole.Left := mConsole.Left;
eConsole.Top := 5 + mConsole.Height;
eConsole.Width := mConsole.Width;
end;
procedure TForm1.bCloseClick(Sender: TObject);
begin
if bStop.enabled then bStop.Click;
Form1.Close;
end;
procedure TForm1.bStartClick(Sender: TObject);
begin
eServerStatus.Text := 'Starting server...';
NewConnection.Resume;
bStart.Enabled := false;
bStop.Enabled := true;
eServerStatus.Text := 'Server running...';
end;
procedure TForm1.bStopClick(Sender: TObject);
var
i : LongInt;
begin
eServerStatus.Text := 'Stopping Server...';
if Client.Count > 0 then
for i := 0 to Client.Count - 1 do
TClient(Client[i]).Send('disconnect');
NewConnection.Suspend;
bStart.Enabled := true;
bStop.Enabled := false;
eServerStatus.Text := 'STOPPED';
end;
procedure TForm1.eConsoleKeyPress(Sender: TObject; var Key: char);
var
Command, Param1, Param2 : String;
i : LongInt;
begin
if key = #13 then
begin
Command := '';
Param1 := '';
Param2 := '';
ParseCommand(eConsole.Text,Command,Param1,Param2);
if command = 'CONNECTED' then
mConsole.Lines.Add(DateTimeToStr(now) + ' Connected clients: ' + IntToStr(Client.Count))
else if command = 'SEND' then
begin
if Client.Count > 0 then
begin
i := 0;
while not (i > Client.Count - 1) do
begin
if TClient(Client[i]).ID = Param1 then
TClient(Client[i]).Send(Param2);
Inc(i);
end;
end;
end;
eConsole.Text := '';
end
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Client := TFPObjectList.Create;
CritSect := TCriticalSection.Create;
NewConnection := TNewConnection.Create(true);
CheckDisconnect := TCheckDisconnect.Create(false);
SendRecvData := TSendRecvData.Create(false);
bStop.Enabled := false;
eServerStatus.Text := 'STOPPED';
end;
end.
unit Clients;
{==============================================================================}
{$mode objfpc}{$H+}
{==============================================================================}
interface
{==============================================================================}
uses
Classes, SysUtils, BlckSock, SynSock;
{==============================================================================}
type
TClient = Class
private
fID : String;
fSocket : TTCPBlockSocket;
fConnected : Boolean;
public
constructor Create(ID : String; sock : TSocket);
function Connected : Boolean;
function ID : String;
procedure Send(Data : String);
function Recv : String;
destructor Destroy;
end;
{==============================================================================}
implementation
{==============================================================================}
constructor TClient.Create(ID : String; sock : TSocket);
begin
fSocket := TTCPBlockSocket.Create;
fSocket.Socket := sock;
fID := ID;
fConnected := true;
fSocket.SendString(ID+CRLF);
end;
{------------------------------------------------------------------------------}
function TClient.Connected : boolean;
begin
result := fConnected;
end;
{------------------------------------------------------------------------------}
function TClient.ID : String;
begin
result := fID;
end;
{------------------------------------------------------------------------------}
procedure TClient.Send(Data : String);
begin
fSocket.SendString(Data+CRLF);
end;
{------------------------------------------------------------------------------}
function TClient.Recv : String;
var
tmpData : String;
TimeoutCount : Byte;
begin
TimeoutCount := 0;
repeat
tmpData := fSocket.RecvString(2000);
if fSocket.Lasterror <> 0 then
Inc(TimeoutCount);
until (tmpData <> '') or (TimeoutCount >= 5);
if Timeoutcount >= 5 then
fConnected := false;
result := tmpData;
end;
{------------------------------------------------------------------------------}
destructor TClient.Destroy;
begin
fConnected := false;
fID := '';
FreeAndNil(fSocket);
inherited Destroy;
end;
{==============================================================================}
end.
unit NewConnections;
{==============================================================================}
{$mode objfpc}{$H+}
{==============================================================================}
interface
{==============================================================================}
uses
Classes, SysUtils, BlckSock, SynSock, Clients;
{==============================================================================}
type
TNewConnection = class(TThread)
private
fListeningSocket : TTCPBlockSocket;
fMessage : String;
procedure ShowMessage;
function SetID : String;
protected
procedure Execute; override;
public
constructor Create(CreateSuspended : boolean);
end;
{==============================================================================}
implementation
{==============================================================================}
uses
Server_Unit;
{==============================================================================}
constructor TNewConnection.Create(CreateSuspended : boolean);
begin
FreeOnTerminate := True;
fListeningSocket := TTCPBlockSocket.Create;
fListeningSocket.Bind('127.0.0.1','1234');
fListeningSocket.Listen;
fMessage := '';
inherited Create(CreateSuspended);
end;
{------------------------------------------------------------------------------}
procedure TNewConnection.ShowMessage;
begin
Form1.mConsole.Lines.Add(fMessage);
end;
{------------------------------------------------------------------------------}
function TNewConnection.SetID : String;
var
Year, Month, Day, Hour, min, sec, msec : Word;
tmpID : String;
begin
DecodeDate(Date,Year,Month,Day);
DecodeTime(Time,Hour,Min,Sec,MSec);
tmpID := IntToStr(Year) + IntToStr(Month) + IntToStr(Day) + IntToStr(Hour) + IntToStr(min) + IntToStr(sec) + IntToStr(msec) + IntToStr(Client.Count);
result := tmpID;
end;
{------------------------------------------------------------------------------}
procedure TNewConnection.Execute;
var
tmpID : String;
tmpSocket : TSocket;
tmpClient : TClient;
begin
repeat
if fListeningSocket.CanRead(1) then
begin
tmpID := SetID;
tmpSocket := fListeningSocket.Accept;
tmpClient := TClient.Create(tmpID,tmpSocket);
CritSect.Enter;
try
Client.Add(tmpClient);
finally
CritSect.Leave;
end;
fMessage := DateTimeToStr(now) + ' Client connected. ID: ' + tmpID;
Synchronize(@ShowMessage);
end;
until Terminated;
end;
{==============================================================================}
end.
unit CheckDisconnects;
{==============================================================================}
{$mode objfpc}{$H+}
{==============================================================================}
interface
{==============================================================================}
uses
Classes, SysUtils, Clients;
{==============================================================================}
type
TCheckDisconnect = class(TThread)
private
fMessage : String;
procedure ShowMessage;
protected
procedure Execute; override;
public
constructor Create(CreateSuspended : boolean);
end;
{==============================================================================}
implementation
{==============================================================================}
uses Server_Unit;
{==============================================================================}
constructor TCheckDisconnect.Create(CreateSuspended : boolean);
begin
FreeOnTerminate := True;
fMessage := '';
inherited Create(CreateSuspended);
end;
{------------------------------------------------------------------------------}
procedure TCheckDisconnect.ShowMessage;
begin
Form1.mConsole.Lines.Add(fMessage);
end;
{------------------------------------------------------------------------------}
procedure TCheckDisconnect.Execute;
var
i : LongInt;
Connected : Boolean;
begin
repeat
i := 0;
if Client.Count > 0 then
begin
while not (i > Client.Count - 1) do
begin
CritSect.Enter;
try
Connected := TClient(Client[i]).Connected;
finally
CritSect.Leave;
end;
if not Connected then
begin
CritSect.Enter;
try
Client.Delete(i);
finally
CritSect.Leave;
fMessage := DateTimeToStr(now) + ' Client disconnected';
Synchronize(@ShowMessage);
end;
end;
Inc(i);
end;
end;
Sleep(1); // <- Zwiększyć wartość?
until Terminated;
end;
{==============================================================================}
end.
unit SendRecvData;
{==============================================================================}
{$mode objfpc}{$H+}
{==============================================================================}
interface
{==============================================================================}
uses
Classes, SysUtils, Clients;
{==============================================================================}
type
TSendRecvData = class(TThread)
private
fMessage : String;
procedure ShowMessage;
protected
procedure Execute; override;
public
constructor Create(CreateSuspended : boolean);
end;
{==============================================================================}
implementation
{==============================================================================}
uses Server_Unit;
{==============================================================================}
constructor TSendRecvData.Create(CreateSuspended : boolean);
begin
FreeOnTerminate := True;
fMessage := '';
inherited Create(CreateSuspended);
end;
{------------------------------------------------------------------------------}
procedure TSendRecvData.ShowMessage;
begin
Form1.mConsole.Lines.Add(fMessage);
end;
{------------------------------------------------------------------------------}
procedure TSendRecvData.Execute;
var
tmpData : String;
i : LongInt;
begin
repeat
i := 0;
if Client.Count > 0 then
begin
while not (i > Client.Count - 1) do
begin
TClient(Client[i]).Send('ping');
tmpData := TClient(Client[i]).Recv;
Inc(i);
end;
end;
Sleep(1); // <- Zwiększyć wartość?
until Terminated;
end;
{==============================================================================}
end.
Mam problem, że jak podłączy mi się załóżmy 10 klientów i potem będą się po kolei odłączać to nie są usuwane na bieżąco. W sensie odłączy się pierwszy to serwer to zarejestruje, odłączy się drugi to serwer również to zarejestruje ale jak odłączy się trzeci to serwer tego nie rejestruje i mi wisi taki klient zombie. Jednak gdy odłączy się czwarty klient to serwer zarejestruje odłączenie trzeciego i czwartego jednocześnie. W ostatecznym bilansie wszyscy podłączeni się odłączą jednak jak rozwiązać problem by serwer rejestrował odłączenie klienta w chwili gdy klient rzeczywiście się odłączy?
I pytanie numer dwa. W kodzie zaznaczyłem linijki z instrukcjami Sleep(1). Czy zwiększenie tego czasu ma sens? Bo według mnie ma gdyż akurat te dwa wątki są odpowiedzialne w tym momencie za sprawdzenie czy dany klient odpowiada i ewentualne rozłączenie jeśli nie odpowiada, więc nie musi to być sprawdzane stale a może być sprawdzane na przykład co minutę.