Board index » delphi » Delphi 7 & Indy 10 TidCmdTCPServer OnCommand Event

Delphi 7 & Indy 10 TidCmdTCPServer OnCommand Event


2007-04-11 05:57:18 PM
delphi261
Hi Everyone,
My application consist of a client and server module.
I have a TidCmdTCPServer & TidTCPClient component on the server and on the
client.
The clients send commands to the server module and then the server processes
the command where after it broadcasts the command to the rest of the
clients.
1. The server & all clients hang whenever 2 or more clients send the same
command
simultaneously to the server.
2. I am not really familiar with the design of the Indy (TidCmdTCPServer
etc) and how it handles the OnCommand event.
Does Indy create threads for each OnCommand. And how do you update VCL
components in the OnCommand?
Thread safe...
Please help.
Thanks.
 
 

Re:Delphi 7 & Indy 10 TidCmdTCPServer OnCommand Event

"Codeman II" <XXXX@XXXXX.COM>writes
Quote
The server & all clients hang whenever 2 or more clients
send the same command simultaneously to the server.
Then you are doing something wrong. Please show your actual code. It
is probably not thread-safe.
Quote
Does Indy create threads for each OnCommand.
No. It creates a new thread for each client that connects. A client
can send multiple commands over a single connection.
Quote
how do you update VCL components in the OnCommand?
The same way you do in any other threaded code - via the
TThread.Synchronize() method. Indy has a TIdSync class that wraps it
for easy use. Or you can write your own inter-thread mechanism to
communicate with your main thread as needed.
Gambit
 

Re:Delphi 7 & Indy 10 TidCmdTCPServer OnCommand Event

Quote
Then you are doing something wrong. Please show your actual code. It
is probably not thread-safe.
Here is my code for the OnCommand event of one of my commands.
CODE BEGIN
---------------------------------------------------------------------------
procedure TfrmMainServer.idCommandcmdFlagUpdateCommand(
ASender: TIdCommand);
var
Sync :TidSync;
X :Integer;
ACommand :String;
begin
{
Here I receive a command to update an item's Flag.
Broadcast this message to ALL NETWORK computers except to the IP this
message was received from
}
Sync := TIdSync.Create;
Try
Try
ResponseString.Clear;
ParamsList.Clear;
//Receive
SenderIP := ASender.Context.Binding.PeerIP;
ACommand := ASender.CommandHandler.Command + ' ';
For X := 0 to ASender.Params.Count - 1 do ACommand := ACommand +
ASender.Params.Strings[X] + ' ';
ACommand := Trim(ACommand);
//fADirection, fALine, fAIPAddress are public variables to hold values
for the SynchronizeMethod() method
//which update the log file display using a TRichEdit component.
fADirection := dirIN; fALine := ASender.CommandHandler.Command;
fAIPAddress := SenderIP;
Sync.SynchronizeMethod(WriteToLog);
//Sent
ResponseString.Add('FlagUpdate Received.');
ASender.Response.AddStrings(ResponseString);
fADirection := dirOUT; fALine := ResponseString.Text; fAIPAddress :=
LocalIP;
Sync.SynchronizeMethod(WriteToLog);
For X := 0 to ComputerIPS.Count - 1 do
begin
If ComputerIPS.Strings[X] <>SenderIP then
begin
//fACommand, fAHost holds values for the SynchronizeMethod() to
broadcast command to the rest of the clients.
fACommand := ACommand; fAHost := ComputerIPS.Strings[X];
Sync.SynchronizeMethod(SendCommand);
fADirection := dirOUT; fALine := 'Broadcasting to: ' + fAHost;
fAIPAddress := LocalIP;
Sync.SynchronizeMethod(WriteToLog);
end;
end;
//Blank line
fADirection := dirNone; fALine := ''; fAIPAddress := '';
Sync.SynchronizeMethod(WriteToLog);
Except
On E:Exception do
begin
//Error
fADirection := dirError; fALine := 'VCL Error: ' + E.Message;
fAIPAddress := LocalIP + ' -->' + SenderIP;
Sync.SynchronizeMethod(WriteToLog);
end;
On E:EidException do
begin
//Error
fADirection := dirError; fALine := 'Indy Error: ' + E.Message;
fAIPAddress := LocalIP + ' -->' + SenderIP;
Sync.SynchronizeMethod(WriteToLog);
end;
end;
Finally
Sync.Free;
end;
end;
---------------------------------------------------------------------------
CODE END
Thanks!
 

Re:Delphi 7 & Indy 10 TidCmdTCPServer OnCommand Event

"Codeman II" <XXXX@XXXXX.COM>writes
Quote
ResponseString.Clear;
ParamsList.Clear;
SenderIP := ...;
Where are those variables declared? Are they being shared amongst
multiple threads?
Quote
ASender.Context.Binding.PeerIP;
TIdContext does not have a Binding property. You have to access it
through the Connection instead:
SenderIP := ASender.Context.Connection.Socket.Binding.PeerIP;
Quote
ACommand := ASender.CommandHandler.Command + ' ';
For X := 0 to ASender.Params.Count - 1 do ACommand := ACommand
+
ASender.Params.Strings[X] + ' ';
ACommand := Trim(ACommand);
You could simply use the RawLine property instead:
ACommand := ASender.RawLine;
Quote
fADirection, fALine, fAIPAddress are public variables to
hold values for the SynchronizeMethod() method
which update the log file display using a TRichEdit component.
Then they are being shared amongst multiple threads. You are not
protecting them adequately enough. You should write a new class so
that they can be local to each call to SynchronizeMethod() (which is
itself static so you don't need an instance of TIdSync unless you
derive from it) without effecting other threads.
Quote
For X := 0 to ComputerIPS.Count - 1 do
begin
If ComputerIPS.Strings[X] <>SenderIP then
Where is ComputerIPS declared? That looks like another shared list
that is not being protected correctly. You don't need to maintain
your own list of IPs anyway. Just use the TIdCmdTCPServers built-in
Contexts list instead.
Quote
fACommand, fAHost holds values for the SynchronizeMethod()
to broadcast command to the rest of the clients.
More variables that need to be local to the thread context that is
calling SynchronizeMethod(). Don't use shared variables.
With that said, try the following instead (untested):
type
TLogSync = class(TIdSync)
private
fDirection: String;
fLine: String;
fIPAddress: String;
procedure DoSynchronize; override;
public
procedure Write(const ADirection, ALine, AIPAddress:
String);
end;
procedure TLogSync.DoSynchronize;
begin
// write fDirection, fLine, and fIPAddress to TRichEdit as
needed...
end;
procedure TLogSync.Write(const ADirection, ALine, AIPAddress:
String);
begin
fDirection := ADirection;
fLine := ALine;
fIPAddress := AIPAddress;
Synchronize;
end;
procedure TfrmMainServer.IdCommandCmdFlagUpdateCommand(ASender:
TIdCommand);
var
Log: TLogSync;
LLocalIP, LSenderIP: String;
LContext: TIdContext;
LServer: TIdCmdTCPServer;
LList: TIdList;
I: Integer;
begin
Log := TLogSync.Create;
try
try
LLocalIP :=
ASender.Context.Connection.Socket.Binding.IP;
LSenderIP :=
ASender.Context.Connection.Socket.Binding.PeerIP;
Log.Write(dirIN, ASender.CommandHandler.Command,
LSenderIP);
ASender.Response.Add('FlagUpdate Received.');
Log.Write(dirOUT, ASender.Response.Text, LLocalIP);
LServer :=
TIdCmdTCPServer(TIdCommandHandlers(ASender.CommandHandler.Collection).
Base);
// or just use your form's TIdCmdTCPServer variable
directly, it is ok here
LList := LServer.Contexts.LockList;
try
for I := 0 to List.Count - 1 do
begin
try
LContext := TIdContext(List[I]);
if LContext <>ASender.Context then
begin
// send ACommand to LContext as
needed...
LContext.Connection.IOHandler.WriteLn(ASender.RawLine);
Log.Write(dirOUT, 'Broadcasting to: '
+ LContext.Connection.Socket.Binding.PeerIP, LLocalIP);
end;
except
end;
end;
Log.Write(dirNone, '', '');
finally
LServer.Contexts.UnlockList;
end;
except
on E: EIdException do
begin
Log.Write(dirError, 'Indy Error: ' + E.Message,
LLocalIP + ' -->' + LSenderIP);
end;
on E: Exception do
begin
Log.Write(dirError; 'VCL Error: ' + E.Message,
LLocalIP + ' -->' + LSenderIP);
end;
end;
finally
Log.Free;
end;
end;
Gambit
 

Re:Delphi 7 & Indy 10 TidCmdTCPServer OnCommand Event

Quote
Where are those variables declared? Are they being shared amongst
multiple threads?
Yes, and I believe that is the problem then.
Thanks for that.
I will try your code and code back.
Thanks for the help!
 

Re:Delphi 7 & Indy 10 TidCmdTCPServer OnCommand Event

Hi Remy,
In what unit is "TIdList" declared?
I am unable to find it.
Thanks!
 

Re:Delphi 7 & Indy 10 TidCmdTCPServer OnCommand Event

I don't seem to have "IdObjs".
Not sure whether I need it but I did a search and it appears that TidList is
declared in IdObjs.
 

Re:Delphi 7 & Indy 10 TidCmdTCPServer OnCommand Event

"Codeman II" <XXXX@XXXXX.COM>writes
Quote
I don't seem to have "IdObjs".
Then you are using an old version. You will have to use TList
instead, or else upgrade to the latest version.
Gambit
 

Re:Delphi 7 & Indy 10 TidCmdTCPServer OnCommand Event

What is the latest version?
Also can you please give me the url for the download page?
Thanks again.
 

Re:Delphi 7 & Indy 10 TidCmdTCPServer OnCommand Event

"Codeman II" <XXXX@XXXXX.COM>writes
Quote
What is the latest version?
The Development Snapshot is currently 10.1.6.
Quote
Also can you please give me the url for the download page?
It is on Indy's website.
Gambit
 

Re:Delphi 7 & Indy 10 TidCmdTCPServer OnCommand Event

Quote
>Also can you please give me the url for the download page?

It is on Indy's website.
I have click the "Download Development Snapshot" link on the Demo Play
Ground page.
But I get a "Page Forbidden" error.
Are you allowed to give me a URL here or can you mail me a download link?
Please.
 

Re:Delphi 7 & Indy 10 TidCmdTCPServer OnCommand Event

"Codeman II" <XXXX@XXXXX.COM>writes
Quote
I have click the "Download Development Snapshot" link on
the Demo Play Ground page.
There is no Demo Playground on the Indy website. You are on the
AtoZed website instead. Go to the actual Indy website instead:
www.indyproject.org
Quote
But I get a "Page Forbidden" error.
That is because the AToZed website is accessing the wrong URL for the
snapshot. I guess the Indy website was reorganized and AToZed never
updated their links.
Gambit
 

Re:Delphi 7 & Indy 10 TidCmdTCPServer OnCommand Event

Quote
LList := LServer.Contexts.LockList;
try
for I := 0 to List.Count - 1 do
begin
try
LContext := TIdContext(List[I]);
if LContext <>ASender.Context then
begin
// send ACommand to LContext as>
needed...

LContext.Connection.IOHandler.WriteLn(ASender.RawLine);
Log.Write(dirOUT, 'Broadcasting to: '>+
LContext.Connection.Socket.Binding.PeerIP, LLocalIP);
end;
except
end;
end;
Log.Write(dirNone, '', '');
finally
LServer.Contexts.UnlockList;
end;
Hi Remy,
Can you please explain the code snippet above.
I took it from your example.
What I actually was trying to do is to keep a list of all client computers.
When a client starts up it sends a "ConnectDB" command to the server and
then a "Quit".
The server in return must then add that client (if it does not already
exists) into a stringlist or so in
order to broadcast new changes to all clients.
Is my logic fine?
How would you suggest I must do this?
Thanks a lot!
 

Re:Delphi 7 & Indy 10 TidCmdTCPServer OnCommand Event

BTW: I mean the PeerIP (client IP) needs to be added in TStringList.
With the IP I can then broadcast changes over the LAN when
necessary.
 

Re:Delphi 7 & Indy 10 TidCmdTCPServer OnCommand Event

"Codeman II" <XXXX@XXXXX.COM>writes
Quote
Can you please explain the code snippet above.
What is there to explain. It is self-explanatory. It is looping
through the server's list of active client connections, broadcasting
the received command to each client that is not the one who sent the
original command.
Quote
What I actually was trying to do is to keep a list of all client
computers.
You don't need to do that yourself. The server does that for you
automatically. The code I gave you is accessing the server's built-in
list of connected clients.
Quote
When a client starts up it sends a "ConnectDB" command to the
server and then a "Quit". The server in return must then add that
client (if it does not already exists) into a stringlist or so
That is all handled for you by the server.
If you want to only broadcast to clients that previously sent the
ConnectDB command, then you could use the TIdContext.Data property to
keep track of that, and then have the broadcasting code check it for
each client when needed.
Quote
in order to broadcast new changes to all clients.
Which is exactly what my code example is doing.
Gambit