A Real World Client Server Application in Delphi - The Client
(Page 2 of 4 )
To make our client truly thread safe we need to solve the following two problems:
- Reading and processing the response from server.
- Displaying that information in a thread safe manner.
Currently, to read a response from the server, the client does this:
procedureTForm1.Button1Click(Sender: TObject);
begin
IdTCPClient1.SendCmd(edit1.text);
memo1.Clear;
memo1.lines.add(idtcpclient1.Socket.ReadLn);
end;
which as we've stated earlier is not thread safe. So, to read the responses from the server we will create a thread that reads those responses. That will be its sole purpose. It will be derived from the VCL's TThread class:
TReadResponse= class(TThread)
protected
FConn: TIdTCPConnection;
procedure Execute; override;
public
constructor Create(AConn: TIdTCPConnection);
reintroduce;
end;
TWriteResponse = class(TIdSync)
protected
FMsg: String;
procedure DoSynchronize; override;
public
constructor Create(const AResponse: String);
class procedure AddResponse(const AResponse: String);
end;
There are two classes here, TReadResponse and TWriteResponse. TReadResponse contains one method and a variable. The variable will hold the connection status of the client, and the execute method is used to do the actual reading:
while not Terminated and FConn.Connected do
begin
TWriteResponse.AddResponse (FConn.IOHandler.ReadLn);
end;
You always override the execute method when you use the VCL's TThread class. This enables you to make it do whatever you want it to do. In our case, we want it to read the response from the server.
The TWriteResponse class is primarily responsible for writing the response that the client gets from the server to the Delphi component, which in this case is the memo component:
procedure TWriteResponse.DoSynchronize;
begin
form1. memo1.Lines.Add(FMsg);
end;
The FMsg variable is used to store the response from the server. And the DoSyncronize method is then responsible for writing that response to the memo component. The class itself is derived from the TIdSync class. TIdSync allows for synchronizations with the ability to pass parameters to the synchronized methods as well. TIdSync also allows for return values to be returned from the main thread.
Before writing the implementations of these methods, add the following to your form's variable section:
rr: TReadResponse = nil;
Your complete client code should look something like this:
unit client;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, IdBaseComponent, IdComponent, IdTCPConnection,
IdTCPClient,IdException,idContext,idSync;
type
TReadResponse = class(TThread)
protected
FConn: TIdTCPConnection;
procedure Execute; override;
public
constructor Create(AConn: TIdTCPConnection); reintroduce;
end;
TWriteResponse = class(TIdSync)
protected
FMsg: String;
procedure DoSynchronize; override;
public
constructor Create(const AResponse: String);
class procedure AddResponse(const AResponse: String);
end;
TForm1 = class(TForm)
IdTCPClient1: TIdTCPClient;
Edit1: TEdit;
Label1: TLabel;
Memo1: TMemo;
Button1: TButton;
Label2: TLabel;
Button2: TButton;
Button3: TButton;
Memo2: TMemo;
Label3: TLabel;
cname: TEdit;
Label4: TLabel;
procedure Button3Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure IdTCPClient1Status(ASender: TObject;
const AStatus: TIdStatus; const AStatusText: String);
procedure IdTCPClient1Connected(Sender: TObject);
procedure IdTCPClient1Disconnected(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
rr: TReadResponse = nil;
implementation
{$R *.dfm}
constructor TWriteResponse.Create(const AResponse: String);
begin
FMsg := AResponse;
inherited Create;
end;
procedure TWriteResponse.DoSynchronize;
begin
form1.memo1.Clear;
form1.memo1.Lines.Add(FMsg);
end;
class procedure TWriteResponse.AddResponse (const AResponse: String);
begin
with Create(AResponse) do try
Synchronize;
finally
Free;
end;
end;
constructor TReadResponse.Create(AConn: TIdTCPConnection);
begin
FConn := AConn;
inherited Create(False);
end;
procedure TReadResponse.Execute;
begin
while not Terminated and FConn.Connected do
begin
TWriteResponse.AddResponse(FConn.IOHandler.ReadLn);
end;
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
close;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
E: Exception;
begin
IdTCPClient1.Host:='localhost';
IdTCPClient1.Port:=6000;
IdTCPClient1.Connect;
try
IdTCPClient1.Socket.WriteLn(cname.Text);
except
on E: EIdException do begin
ShowMessage('Indy Exception: ' + E.Message);
end else begin
ShowMessage('VCL Exception: ' + E.Message);
end;
end;
button2.Enabled:=false;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
IdTCPClient1.IOHandler.WriteLn(edit1.text+'@'+cname.Text+';');
end;
procedure TForm1.IdTCPClient1Status(ASender: TObject;
const AStatus: TIdStatus; const AStatusText: String);
begin
memo2.Lines.Add(Astatustext)
end;
procedure TForm1.IdTCPClient1Connected(Sender: TObject);
begin
rr:= TReadResponse.Create(IdTCPClient1);
end;
procedure TForm1.IdTCPClient1Disconnected(Sender: TObject);
begin
if rr <> nil then
begin
rr.Terminate;
rr.WaitFor;
FreeAndNil(rr);
end;
end;
end.
Next: The Server >>
More Delphi-Kylix Articles
More By Leidago