The following code seems to work, most of the time. But there is a problem. If the third party server is sending nothing the code appears to hang - waiting.
{code}
function TTSPayForm.ReadDPSMessage: IXMLDocument;
const
cStart = ' -1 then Break;
end;
StartPos := IndyMax(InputSize-(Length(Term)-1), Length(cStart));
IdTCPClient1.IOHandler.CheckForDisconnect(True, True);
IdTCPClient1.IOHandler.CheckForDataOnSource(IdTimeoutDefault);
until False;
except
on E : Exception do
begin
ShowMessage('DPS connection dropped.');
end;
end;
// IDTCPClient1.IOHandler.
sXmlStrm := TMemoryStream.Create;
try
IdTCPClient1.IOHandler.ReadStream(sXmlStrm, TermPos + Length(Term), False);
sXmlStrm.Position := 0;
// Memo1.Text := ReadStringFromStream(sXmlStrm, -1, Indy8BitEncoding);
sXmlStrm.Position := 0;
Result := TXMLDocument.Create(nil);
Result.LoadFromStream(sXmlStrm);
finally
sXmlStrm.Free;
end;
end;
{code}
The code runs within a timer. But the timer is "hangs" while the TCPClient is waiting for a message from a third party "server". Which it will not get until something happens on the hardware that server is connected to. The server is connected to an EFTPOS pinpad.
Sometimes it is necessary to cancel the transaction. But the cancellation message cannot be sent from the software until the TCPClient gets another message. As soon as that happens the cancellation message is sent.
I can't, for the life of me, figure some way to enable the Waitfor request to be interrupted. In other words - if there is nothing there then the TCPClient should stop waiting.
Prior to the call the following code executes:
{code}
if IdTCPClient1.IOHandler.InputBufferIsEmpty then
begin
if not IdTCPClient1.IOHandler.CheckForDataOnSource(10) then
Exit;
end;
DPSMessage := ReadDPSMessage;
{code}
Do I need to clear the input buffer AFTER a successful read?
Any ideas?
Thanks
I've set the code to track on a log file. The following is the contents of the file.
PRESENT/INSERT 1:18:13 p.m. Line 1055
//Text1[1]/nodePRESENT/INSERT 1:18:13 p.m. Line 1067
/Text2[1]/node()OR SWIPE CARD 1:18:13 p.m. line 1150
TRANS. CANCELLED 1:18:13 p.m. Line 1055
//Text1[1]/nodeTRANS. CANCELLED 1:18:13 p.m. Line 1067
AR TRANS CANCELLED 1:18:51 p.m. Line 914
What I find very strange is that the time doesn't change from 'PRESENT/INSERT' to 'TRANS. CANCELLED'. Even though I counted something like 30 seconds. It's as if something is 'turning off" the timer/clock. As you can see 'AR TRANS CANCELLED' is logged at 38 seconds later.
I have looked for anything that could have turned off the timer and I couldn't see anything. But the times in the log file are from the PC's clock. Using "Now".
Alan
Edited by: Alan Jeffery on Dec 27, 2014 4:20 PM
Vote for best question.
Score: 0
# Vote: 0
Date Posted: 27-Dec-2014, at 4:26 PM EST
From: Alan Jeffery
Re: IdTCPClient seems to "hang" waiting for response
Alan wrote:
> I can't carry out the processing inside a thread as the code needs
> to access the UI.
A worker thread can access the UI, it just needs to sync with the main thread
in order to access the UI safely.
> Sometimes the server response requires buttons be displayed and/or clicked.
All the more reason why the socket code should be moved to a worker thread.
> You may see that I've spotted that the clock seems to be disabled if
> there is no response from the TCPClient.
The clock is not stopped/disabled. You are simply not accessing the clocik
in a timely manner because your code is blocking.
--
Remy Lebeau (TeamB)
Vote for best answer.
Score: 0
# Vote: 0
Date Posted: 27-Dec-2014, at 4:52 PM EST
From: Remy Lebeau (TeamB)
Re: IdTCPClient seems to "hang" waiting for response
> {quote:title=Remy Lebeau (TeamB) wrote:}{quote}
> Alan wrote:
>
> > The following code seems to work, most of the time. But there is
> > a problem. If the third party server is sending nothing the code
> > appears to hang - waiting.
>
> Indy uses infinite timeouts by default. You can either set the IOHandler.ReadTimeout
> property to a non-infinte interval, or pass a non-infinite value to the ATimeout
> parameter of TIdIOHandler.WaitFor(). That will cause Indy to raise an exception
> if the timeout elapses.
>
> > The code runs within a timer.
>
> Then you should not be doing a looped read. If the entire data is not available
> immediately, exit the function and let the timer call it again later, eg:
>
> {code}
> const
> cStart = ' cEnd = '';
>
> procedure Timer(Sender: TObject);
> var
> Term: TIdBytes;
> begin
> Term := ToBytes(cEnd);
> IdTCPClient1.IOHandler.CheckForDataOnSource(10);
> while IdTCPClient1.IOHandler.InputBuffer.IndexOf(Term) <> -1 do
> DPSMessage := ReadDPSMessage;
> end;
>
> function TTSPayForm.ReadDPSMessage: IXMLDocument;
> var
> sXml: string;
> sXMLStrm : TMemoryStream;
> begin
> Result := nil;
>
> try
> sXml := IdTCPClient1.IOHandler.ReadLn(cEnd, Indy8BitEncoding) + cEnd;
> except
> on E : Exception do
> begin
> ShowMessage('DPS connection dropped.');
> Exit;
> end;
> end;
>
> // Memo1.Text := sXml;
>
> sXmlStrm := TMemoryStream.Create;
> try
> WriteStringToStream(sXmlStrm, sXml, Indy8BitEncoding);
> sXmlStrm.Position := 0;
> Result := TXMLDocument.Create(nil);
> Result.LoadFromStream(sXmlStrm);
> finally
> sXmlStrm.Free;
> end;
> end;
> {code}
>
> Otherwise, move the reading to a worker thread and get rid off the timer
> completely. Let the thread loop and block, and notify the main thread whenever
> a new XML message arrives.
>
> > But the timer is "hangs" while the TCPClient is waiting for a message from
> a third party "server".
>
> That is because Indy operations are blocking, and you are performing them
> in the context of the main UI thread, where they do not belong.
>
> > I can't, for the life of me, figure some way to enable the Waitfor
> > request to be interrupted. In other words - if there is nothing there
> > then the TCPClient should stop waiting.
>
> The only options are to specifying a read timeout, or close the socket.
>
> > Prior to the call the following code executes:
>
> That only tells you if any data is waiting, but it does not tell you if a
> complete message is waiting.
>
> > Do I need to clear the input buffer AFTER a successful read?
>
> No.
>
> --
> Remy Lebeau (TeamB)
Remy
Thanks for that. I can't carry out the processing inside a thread as the code needs to access the UI. Sometimes the server response requires buttons be displayed and/or clicked.
I'll try your suggested fix and see how it goes.
You may see that I've spotted that the clock seems to be disabled if there is no response from the TCPClient. I added the contents of a log file to the original message.
Regards.
Alan
Vote for best answer.
Score: 0
# Vote: 0
Date Posted: 27-Dec-2014, at 4:34 PM EST
From: Alan Jeffery
Re: IdTCPClient seems to "hang" waiting for response
Alan wrote:
> The following code seems to work, most of the time. But there is
> a problem. If the third party server is sending nothing the code
> appears to hang - waiting.
Indy uses infinite timeouts by default. You can either set the IOHandler.ReadTimeout
property to a non-infinte interval, or pass a non-infinite value to the ATimeout
parameter of TIdIOHandler.WaitFor(). That will cause Indy to raise an exception
if the timeout elapses.
> The code runs within a timer.
Then you should not be doing a looped read. If the entire data is not available
immediately, exit the function and let the timer call it again later, eg:
{code}
const
cStart = ' -1 do
DPSMessage := ReadDPSMessage;
end;
function TTSPayForm.ReadDPSMessage: IXMLDocument;
var
sXml: string;
sXMLStrm : TMemoryStream;
begin
Result := nil;
try
sXml := IdTCPClient1.IOHandler.ReadLn(cEnd, Indy8BitEncoding) + cEnd;
except
on E : Exception do
begin
ShowMessage('DPS connection dropped.');
Exit;
end;
end;
// Memo1.Text := sXml;
sXmlStrm := TMemoryStream.Create;
try
WriteStringToStream(sXmlStrm, sXml, Indy8BitEncoding);
sXmlStrm.Position := 0;
Result := TXMLDocument.Create(nil);
Result.LoadFromStream(sXmlStrm);
finally
sXmlStrm.Free;
end;
end;
{code}
Otherwise, move the reading to a worker thread and get rid off the timer
completely. Let the thread loop and block, and notify the main thread whenever
a new XML message arrives.
> But the timer is "hangs" while the TCPClient is waiting for a message from
a third party "server".
That is because Indy operations are blocking, and you are performing them
in the context of the main UI thread, where they do not belong.
> I can't, for the life of me, figure some way to enable the Waitfor
> request to be interrupted. In other words - if there is nothing there
> then the TCPClient should stop waiting.
The only options are to specifying a read timeout, or close the socket.
> Prior to the call the following code executes:
That only tells you if any data is waiting, but it does not tell you if a
complete message is waiting.
> Do I need to clear the input buffer AFTER a successful read?
No.
--
Remy Lebeau (TeamB)