nova2002
asked on
More on Basic Client/Server Application
Hi,
I hope TheRealLoki is arround :)
First of all, Loki, I tried to download your examples from :
https://sourceforge.net/projects/internetdemos/
For Delphi 7, but the zip files are corrupted. They cant be unzipped.
Second, I tried to capture the output of CreateProcess, but I cant find if TIdTCPServer has "Synchronize" procedure. Do we need to use "PostMessage" then ?
Here is the CMD_PROCESS procedure :
procedure TfServerMain.CMD_PROCESS(A Thread: TIdPeerThread; InCmd: string);
const
PingInterval = 10; // ping every "x" seconds
PingTimeout = 10; // if no response to "ping" in "x" seconds, then fail
var
WaitResult : integer;
StartupInfo: TStartupInfo;
ProcessInfo: TProcessInformation;
iResult : integer;
Filename: string;
ClientResponding: Boolean;
Counter: integer;
S: string;
BeforePing, AfterPing: Int64;
begin
try
AThread.Connection.ReadTim eout := PingTimeout * 1000;
if (uppercase(InCmd) = 'TEST') then
begin
if FileExists('C:\Windows\Not epad.exe') then Filename := 'C:\Windows\Notepad.exe'
else if FileExists('C:\WinNT\Notep ad.exe') then Filename := 'C:\WinNT\Notepad.exe';
end
else
Filename := '';
ClientResponding := True; // default
Counter := 0;
// for this example we will just run notepad
if Filename <> '' then
try
FillChar(StartupInfo, SizeOf(TStartupInfo), 0);
with StartupInfo do
begin
cb := SizeOf(TStartupInfo);
dwFlags := STARTF_USESHOWWINDOW or STARTF_FORCEONFEEDBACK;
(* you could pass sw_show or sw_hide as parameter
sw_hide would make more sense if you do not wish to see anything on the screen
but not a good idea if the application doe not close itself*)
wShowWindow := SW_SHOW; //visibility;
end;
if CreateProcess(nil,PChar(Fi lename), nil, nil, False,
NORMAL_PRIORITY_CLASS, nil, nil,
StartupInfo, ProcessInfo) then
repeat
// we are going to check the process is still running
// however, every 10 seconds, we will send a "PING" to the client
// if we do not receive a "PONG" response, we will terminate the process
WaitResult := WaitForSingleObject(Proces sInfo.hPro cess, 1000); //Wait for 1 seconds at a time.
inc(Counter);
if Counter >= PingInterval then
begin
Counter := 0;
try
BeforePing := GetTickCount;
SendStringWithLogging(AThr ead, 'PING');
S := AThread.Connection.ReadLn; // readtimeout is set above (10 seconds)
AfterPing := GetTickCount;
ThreadLogMessage(lmtInform ation, ldIn, (AThread.Data as TClientSpecificData).ID + ' - ' + S + ' (' + IntToStr(AfterPing-BeforeP ing) + 'ms)');
except
on e: exception do
begin // assume any error is a read timeout
ClientResponding := False;
ThreadLogMessage(lmtInform ation, ldIn, (AThread.Data as TClientSpecificData).ID + ' - Ping Failed: ' + E.Message);
//Terminate the process
iresult := Integer(TerminateProcess(P rocessInfo .hProcess, 0));
end;
end;
end; //of check ping
until ( (WaitResult <> WAIT_TIMEOUT) or (not ClientResponding) )
else
iresult := GetLastError; //eror occurs during CreateProcess see help for details }
if ProcessInfo.hProcess <> 0 then
CloseHandle(ProcessInfo.hP rocess);
if ProcessInfo.hThread <> 0 then
CloseHandle(ProcessInfo.hT hread);
except
on e: exception do
begin
ThreadLogMessage(lmtInform ation, ldNone, (AThread.Data as TClientSpecificData).ID + 'Process error: ' + E.Message);
end;
end;
finally
if ClientResponding then
begin
AThread.Connection.ReadTim eout := 0; // reset to infinite
SendStringWithLogging(AThr ead, 'PROCESS_DONE');
end;
end;
end;
I just want to addapt with one of your other demo (multithreading CreateProcess) that is capable to capture the output of CreateProcess :
procedure TRunConsoleAndCaptureThrea d.execute;
var
StartupInfo: TStartupInfo;
SecurityAttributes: TSecurityAttributes;
ProcessInfo: TProcessInformation;
hReadStdOut, hNewStdOut: THandle;
buf: string;
statusCode, bread, bavail: cardinal;
Success: boolean;
iresult: integer;
begin
try
SecurityAttributes.nLength := sizeOf(SecurityAttributes) ;
SecurityAttributes.bInheri tHandle := True;
SecurityAttributes.lpSecur ityDescrip tor := nil;
if not CreatePipe(hReadStdOut, hNewStdOut, @SecurityAttributes, 0) then
raise Exception.Create('Can not create a pipe for STDOUT!');
try
GetStartupInfo(StartupInfo );
StartupInfo.cb := sizeOf(StartupInfo);
StartupInfo.wShowWindow := SW_HIDE; // SET THIS TO "SW_HIDE" IF YOU DO NOT WANT TO SEE THE PROCESS
StartupInfo.hStdOutput := hNewStdOut;
StartupInfo.hStdError := hNewStdOut;
// StartupInfo.hStdInput := 0;
StartupInfo.hStdInput := hNewStdOut;
StartupInfo.dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
//e.g. for a dos call to get the dir of c:\ you would have
// CommandLine := ''
// Parameters := 'c:\winnt\system32\cmd.exe /c dir c:\';
if (CommandLine <> '') and (Parameters <> '') then
Success := CreateProcess( pChar(CommandLine), pChar( Parameters ), @SecurityAttributes, nil, True, 0, nil, nil, StartupInfo, ProcessInfo )
else if (CommandLine <> '') and (Parameters = '') then
Success := CreateProcess( pChar(CommandLine), nil, @SecurityAttributes, nil, True, 0, nil, nil, StartupInfo, ProcessInfo )
else if (CommandLine = '') and (Parameters <> '') then
Success := CreateProcess( nil, pChar( Parameters ), @SecurityAttributes, nil, True, 0, nil, nil, StartupInfo, ProcessInfo )
else
Success := False;
if Success then
begin
// give CPU time to really running processes
Sleep(100);
// save spawned processes state
repeat
GetExitCodeProcess(Process Info.hProc ess, statusCode);
PeekNamedPipe(hReadStdOut, nil, 0, nil, @bavail, nil);
while bavail > 0 do
begin
SetLength(buf, bavail);
ReadFile(hReadStdOut, buf[1], bavail, bread, nil);
// remove those ugly double CR
Data := StringReplace(buf, #13#13, #13, [rfReplaceAll]);
AllData := AllData + Data;
if ( (not Terminated) and (assigned(fCurrentDataEven t) ) ) then
Synchronize(NotifyCurrentD ata);
PeekNamedPipe(hReadStdOut, nil, 0, nil, @bavail, nil);
end;
until ( Terminated or (statusCode <> STILL_ACTIVE)); // if not active - exit;
SetLength(buf, 0);
if Terminated then
begin // terminate this process
iresult := Integer(TerminateProcess(P rocessInfo .hProcess, 0));
end;
end;
finally
CloseHandle(ProcessInfo.hT hread);
CloseHandle(ProcessInfo.hP rocess);
CloseHandle(hReadStdOut);
CloseHandle(hNewStdOut);
end;
except
on e: exception do
begin
//
end;
end;
if ( (not Terminated) and (AllData <> '') and (assigned(fAllDataEvent) ) ) then
Synchronize(NotifyAllData) ;
Synchronize(NotifyComplete );
end;
I need help with the "synchronize" procedure, or maybe I have to do it differently ???
Thanks for any help.
I hope TheRealLoki is arround :)
First of all, Loki, I tried to download your examples from :
https://sourceforge.net/projects/internetdemos/
For Delphi 7, but the zip files are corrupted. They cant be unzipped.
Second, I tried to capture the output of CreateProcess, but I cant find if TIdTCPServer has "Synchronize" procedure. Do we need to use "PostMessage" then ?
Here is the CMD_PROCESS procedure :
procedure TfServerMain.CMD_PROCESS(A
const
PingInterval = 10; // ping every "x" seconds
PingTimeout = 10; // if no response to "ping" in "x" seconds, then fail
var
WaitResult : integer;
StartupInfo: TStartupInfo;
ProcessInfo: TProcessInformation;
iResult : integer;
Filename: string;
ClientResponding: Boolean;
Counter: integer;
S: string;
BeforePing, AfterPing: Int64;
begin
try
AThread.Connection.ReadTim
if (uppercase(InCmd) = 'TEST') then
begin
if FileExists('C:\Windows\Not
else if FileExists('C:\WinNT\Notep
end
else
Filename := '';
ClientResponding := True; // default
Counter := 0;
// for this example we will just run notepad
if Filename <> '' then
try
FillChar(StartupInfo, SizeOf(TStartupInfo), 0);
with StartupInfo do
begin
cb := SizeOf(TStartupInfo);
dwFlags := STARTF_USESHOWWINDOW or STARTF_FORCEONFEEDBACK;
(* you could pass sw_show or sw_hide as parameter
sw_hide would make more sense if you do not wish to see anything on the screen
but not a good idea if the application doe not close itself*)
wShowWindow := SW_SHOW; //visibility;
end;
if CreateProcess(nil,PChar(Fi
NORMAL_PRIORITY_CLASS, nil, nil,
StartupInfo, ProcessInfo) then
repeat
// we are going to check the process is still running
// however, every 10 seconds, we will send a "PING" to the client
// if we do not receive a "PONG" response, we will terminate the process
WaitResult := WaitForSingleObject(Proces
inc(Counter);
if Counter >= PingInterval then
begin
Counter := 0;
try
BeforePing := GetTickCount;
SendStringWithLogging(AThr
S := AThread.Connection.ReadLn;
AfterPing := GetTickCount;
ThreadLogMessage(lmtInform
except
on e: exception do
begin // assume any error is a read timeout
ClientResponding := False;
ThreadLogMessage(lmtInform
//Terminate the process
iresult := Integer(TerminateProcess(P
end;
end;
end; //of check ping
until ( (WaitResult <> WAIT_TIMEOUT) or (not ClientResponding) )
else
iresult := GetLastError; //eror occurs during CreateProcess see help for details }
if ProcessInfo.hProcess <> 0 then
CloseHandle(ProcessInfo.hP
if ProcessInfo.hThread <> 0 then
CloseHandle(ProcessInfo.hT
except
on e: exception do
begin
ThreadLogMessage(lmtInform
end;
end;
finally
if ClientResponding then
begin
AThread.Connection.ReadTim
SendStringWithLogging(AThr
end;
end;
end;
I just want to addapt with one of your other demo (multithreading CreateProcess) that is capable to capture the output of CreateProcess :
procedure TRunConsoleAndCaptureThrea
var
StartupInfo: TStartupInfo;
SecurityAttributes: TSecurityAttributes;
ProcessInfo: TProcessInformation;
hReadStdOut, hNewStdOut: THandle;
buf: string;
statusCode, bread, bavail: cardinal;
Success: boolean;
iresult: integer;
begin
try
SecurityAttributes.nLength
SecurityAttributes.bInheri
SecurityAttributes.lpSecur
if not CreatePipe(hReadStdOut, hNewStdOut, @SecurityAttributes, 0) then
raise Exception.Create('Can not create a pipe for STDOUT!');
try
GetStartupInfo(StartupInfo
StartupInfo.cb := sizeOf(StartupInfo);
StartupInfo.wShowWindow := SW_HIDE; // SET THIS TO "SW_HIDE" IF YOU DO NOT WANT TO SEE THE PROCESS
StartupInfo.hStdOutput := hNewStdOut;
StartupInfo.hStdError := hNewStdOut;
// StartupInfo.hStdInput := 0;
StartupInfo.hStdInput := hNewStdOut;
StartupInfo.dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
//e.g. for a dos call to get the dir of c:\ you would have
// CommandLine := ''
// Parameters := 'c:\winnt\system32\cmd.exe
if (CommandLine <> '') and (Parameters <> '') then
Success := CreateProcess( pChar(CommandLine), pChar( Parameters ), @SecurityAttributes, nil, True, 0, nil, nil, StartupInfo, ProcessInfo )
else if (CommandLine <> '') and (Parameters = '') then
Success := CreateProcess( pChar(CommandLine), nil, @SecurityAttributes, nil, True, 0, nil, nil, StartupInfo, ProcessInfo )
else if (CommandLine = '') and (Parameters <> '') then
Success := CreateProcess( nil, pChar( Parameters ), @SecurityAttributes, nil, True, 0, nil, nil, StartupInfo, ProcessInfo )
else
Success := False;
if Success then
begin
// give CPU time to really running processes
Sleep(100);
// save spawned processes state
repeat
GetExitCodeProcess(Process
PeekNamedPipe(hReadStdOut,
while bavail > 0 do
begin
SetLength(buf, bavail);
ReadFile(hReadStdOut, buf[1], bavail, bread, nil);
// remove those ugly double CR
Data := StringReplace(buf, #13#13, #13, [rfReplaceAll]);
AllData := AllData + Data;
if ( (not Terminated) and (assigned(fCurrentDataEven
Synchronize(NotifyCurrentD
PeekNamedPipe(hReadStdOut,
end;
until ( Terminated or (statusCode <> STILL_ACTIVE)); // if not active - exit;
SetLength(buf, 0);
if Terminated then
begin // terminate this process
iresult := Integer(TerminateProcess(P
end;
end;
finally
CloseHandle(ProcessInfo.hT
CloseHandle(ProcessInfo.hP
CloseHandle(hReadStdOut);
CloseHandle(hNewStdOut);
end;
except
on e: exception do
begin
//
end;
end;
if ( (not Terminated) and (AllData <> '') and (assigned(fAllDataEvent) ) ) then
Synchronize(NotifyAllData)
Synchronize(NotifyComplete
end;
I need help with the "synchronize" procedure, or maybe I have to do it differently ???
Thanks for any help.
further:-
Indy 9 does have a synchronize, you need to override the class and call it yourself
Indy 10 uses TIdYarn, which is more complicated.
This is why i just use PostMessage instead, becuase it works with both versions
Indy 9 does have a synchronize, you need to override the class and call it yourself
Indy 10 uses TIdYarn, which is more complicated.
This is why i just use PostMessage instead, becuase it works with both versions
ASKER
Thanks Loki, I will try to redownload it again later.
Btw, in the Client side, I noticed that you have put a "disconnect" button but you removed it.
procedure TfClientMain.bDisconnectCl ick(Sender : TObject);
begin
// IdTCPClient1.Disconnect;
end;
I would enable it like this :
procedure TfClientMain.bDisconnectCl ick(Sender : TObject);
begin
with IndyInAThread do begin
TCPClientInsideThread.Disc onnect;
ThreadLogMessage(lmtInform ation, ldNone, 'Disconnected');
TCPClientInsideThread.Free ;
end;
end;
In this case, the object IdTCPClient1 is not necessary at all, right ?
Btw, in the Client side, I noticed that you have put a "disconnect" button but you removed it.
procedure TfClientMain.bDisconnectCl
begin
// IdTCPClient1.Disconnect;
end;
I would enable it like this :
procedure TfClientMain.bDisconnectCl
begin
with IndyInAThread do begin
TCPClientInsideThread.Disc
ThreadLogMessage(lmtInform
TCPClientInsideThread.Free
end;
end;
In this case, the object IdTCPClient1 is not necessary at all, right ?
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Try downloading them again
To get any message from the console back to the main thread, while running in an Indy Thread,
you could do a Postmessage
as demonstrated in the ThreadLogMessage()
e.g. you could call
ThreadLogMessage(mtInforma
or
ThreadLogMessage(mtInforma
procedure TfServerMain.ThreadLogMess
var
i: Integer;
PS: PString;
begin
New(PS);
PS^ := S;
i := 0;
if LogMessageType = lmtInformation then i := tlmtInformation;
if LogMessageType = lmtWarning then i := tlmtWarning;
if LogMessageType = lmtError then i := tlmtError;
if LogDirection = ldNone then i := i + tldNone;
if LogDirection = ldIn then i := i + tldIn;
if LogDirection = ldOut then i := i + tldOut;
PostMessage(fServerMain.Ha
end;