Uczę się co to sa wątki, aplikacje wielowątkowe i jak je pisać. Dorzuciłem do tego pakiet synapse bo obydwie rzeczy są mi potrzebne w moim projekcie. Zacznę może od kodów:
Kod Serwera:
unit main
unit Main;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
blckSock, SynSock, Servers;
type
{ TfMainForm }
TfMainForm = class(TForm)
bStart: TButton;
bStop: TButton;
bClose: TButton;
eConsole: TEdit;
eServerStatus: TEdit;
lServerStatus: TLabel;
mConsole: TMemo;
procedure bStartClick(Sender: TObject);
procedure bStopClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormResize(Sender: TObject);
private
public
Server : TServer;
tmpSock : TSocket;
end;
var
fMainForm: TfMainForm;
implementation
{$R *.lfm}
{ TfMainForm }
procedure TfMainForm.FormResize(Sender: TObject);
begin
bStart.Top := 10;
bStart.Left := 10;
bStop.Top := 15 + bStart.Height;
bStop.Left := bStart.Left;
bClose.Top := fMainForm.Height - bClose.Height - 10;
bClose.Left := bStart.Left;
lServerStatus.Top := 25 + bStart.Height + bStop.Height;
lServerStatus.Left := bStart.Left;
eServerStatus.Top := 30 + bStart.Height + bStop.Height + lServerStatus.Height;
eServerStatus.Left := bStart.Left;
eServerStatus.Width := fMainForm.Width div 2 - 20;
mConsole.Top := 0;
mConsole.Left := fMainForm.Width div 2;
mConsole.Width := fMainForm.Width div 2;
mConsole.Height := fMainForm.Height - eConsole.Height - 5;
eConsole.Top := mConsole.Height + 5;
eConsole.Left := mConsole.Left;
eConsole.Width := mConsole.Width - 2;
end;
procedure TfMainForm.FormCreate(Sender: TObject);
begin
eServerStatus.Text := 'STOPPED';
end;
procedure TfMainForm.bStartClick(Sender: TObject);
begin
eServerStatus.Text := 'Starting server...';
Server := TServer.Create(false);
eServerStatus.Text := 'Server running';
end;
procedure TfMainForm.bStopClick(Sender: TObject);
begin
eServerStatus.Text := 'Stopping server...';
FreeAndNil(Server);
eServerStatus.Text := 'STOPPED';
end;
end.
unit servers
unit Servers;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, blckSock, SynSock, Clients;
type
TServer = class(TThread)
private
fSocket : TTCPBlockSocket;
fCheck : Boolean;
fMessage : String;
procedure ShowStatus;
protected
procedure Execute; override;
public
Socket : TSocket;
Ilosc : Word;
Client : Array of TClient;
constructor Create(CreateSuspended : Boolean);
end;
implementation
uses Main;
constructor TServer.Create(CreateSuspended : Boolean);
begin
fSocket := TTCPBLockSocket.Create;
fSocket.Bind('127.0.0.1','1234');
fSocket.Listen;
Ilosc := 0;
SetLength(Client,Ilosc);
fCheck := false;
inherited Create(CreateSuspended);
end;
procedure TServer.ShowStatus;
begin
fMainForm.mConsole.Lines.Add(fMessage);
end;
procedure TServer.Execute;
begin
repeat
if fSocket.CanRead(1) then
begin
Socket := fSocket.Accept;
Inc(Ilosc);
SetLength(Client,Ilosc);
Client[Ilosc-1] := Tclient.Create(false,Socket);
end;
until (Terminated);
end;
end.
unit clients
unit Clients;
{$mode objfpc}{$H+}
interface
uses
{$ifdef unix}
cthreads,
cmem,
{$endif}
Classes, SysUtils, BlckSock, SynSock;
type
TClient = class(TThread)
private
Socket : TTCPBlockSocket;
Data : String;
procedure AddDataToMemo;
protected
procedure Execute; override;
public
constructor Create(CreateSuspended : Boolean; sock : TSocket);
end;
implementation
uses Main;
constructor TClient.Create(CreateSuspended : Boolean; sock : TSocket);
begin
FreeOnTerminate := True;
Socket := TTCPBlockSocket.Create;
Socket.Socket := sock;
inherited Create(CreateSuspended);
end;
procedure TClient.AddDataToMemo;
begin
fMainForm.mConsole.Lines.Add(Data);
end;
procedure TClient.Execute;
begin
repeat
Data := Socket.RecvString(1);
If Data <> '' Then Synchronize(@AddDataToMemo);
until (Terminated) or (Data = 'disconnect');
end;
end.
Tak wygląda kod serwera który napisałem. Serwer ten służy do testów i nauki.
kod klienta który też służy do testowania:
program Test_Client;
{$mode objfpc}{$H+}
uses
{$IFDEF UNIX}{$IFDEF UseCThreads}
cthreads,
{$ENDIF}{$ENDIF}
Classes,
Crt, blckSock, SysUtils;
var
Socket : TTCPBlockSocket;
Buff : String;
Data : String;
begin
ClrScr;
Write('Establishing connection...');
Socket := TTCPBLockSocket.Create;
Socket.Connect('127.0.0.1','1234');
If Socket.LastError <> 0 Then
Begin
Write ('Error: ');
WriteLn (Socket.LastErrorDesc);
ReadKey;
Halt (Socket.LastError);
end;
WriteLn('DONE!');
WriteLn('Input:');
Repeat
ReadLn(Buff);
Data := Buff + CRLF;
Socket.SendString(Data);
until Buff = 'disconnect';
FreeAndNil(Socket);
end.
I teraz mam kilka pytań:
- Jak wykryć po stronie serwera (czyt. tego który napisałem), że klient się odłączył? Problem sprowadza się w sumie do tego jak wykryć, że dany wątek się zakończył. Potrzebne mi to jest do zmniejszania rozmiaru tablicy która jest zwiększana przy podłączaniu klienta.
Wpadłem na pomysł by klient podczas odłączenia wysyłał jakieś dane do serwera przez co serwer będzie wiedział, że klient się odłączył ale co wtedy gdy klientowi na przykład prąd wyłączą?
Opcja druga to by serwer sprawdzał czy klient jest cały czas podłączony na przykład poprzez wysyłanie do klienta zapytania. Klient odpowie znaczy jest, klient nie odpowie odłącz go.
Opcja trzecia to połączenie opcji powyżej.
Jednak dalej zostaje pytanie jak wykryć czy dany wątek się zakończył czy nie.
Myślałem o ustawieniu w wątku FreeOnTerminate na false i potem sprawdzaniu czy Terminated = true jeśli tak to zwolnij obiekt wątku zmniejsz tablicę i tak dalej.
-
Jak widać mam oddzielny wątek który ma za zadanie sprawdzać czy coś chce się podłączyć do serwera (oraz sprawdzać czy się odłączyło). Ten wątek jest wywoływany z wątku głównego a wątki odpowiedzialne za poszczególnych klientów są wywoływane z wątku sprawdzającego. Czy takie odpalanie wątków jest prawidłowe?
-
Mam tablicę dynamiczną klientów która zwiększa się jeśli klient się podłączy i ma się zmniejszać jeśli się odłączy. Zmniejszanie rozmiaru tablicy dynamicznej bez utraty danych ogólnie wygląda tak
var
DynArray : array of Integer;
wielkosc : Word;
i : Word;
begin
wielkosc := 10;
SetLength(DynArray, wielkosc);
for i := 0 to wielkosc - 1 do
DynArray[i] := i;
for i := 5 to wielkosc - 1 do
DynArray[i-1] := DynArray[i];
dec(wielkosc);
SetLength(DynArray, wielkosc);
end.
// Tu mogłem coś popitolić bo niestety nie jestem pewien czy coś działa jeśli tego nie przetestuję (czyt. skompiluje i odpalę).
Czy tak samo to działa w przypadku dynamicznej tablicy obiektów (na przykładzie mojego serwera obiektów typu TTCPBlockSocket)?
- I ostatnie pytanie dotyczy wątków klienta. Jak wygląda to wydajnościowo. Czy oddzielny wątek dla każdego klienta jest sensowny, czy może podzielić to jakoś, na przykład 1 wątek na 1000 klientów? I jak taki podział by wyglądał pod względem czasu dłubania w kodzie. Mi się wydaje (tak wydaje mi się bo jeszcze nie ogarniam wątków do końca), że oddzielny wątek dla każdego jest szybciej napisać niż wykrywanie który klient jest właśnie tym przy którym powinno się odpalić kolejny wątek no i potem jak liczba podłączonych klientów spadnie to myśleć jak zaprogramować by wątki przełączały między sobą obsługę danego klienta/ów i które wątki zakończyć a które zostawić by pracowały dalej. Jednak nurtuje mnie jak zachowa się system gdy do serwera podłączy się 100000 klientów i program odpali 100000 wątków. Czy 100000 wątków będzie działać tak samo szybko jak 100 wątków? W końcu obsługują tą samą liczbę klientów.