Why do I get an AccessViolation in Indy Sockets 9 IdTcpServer ServerExecute?
-
06-07-2019 - |
Question
First Question:
Is the following routine a correct implementation of an Indy 9 IdTcpServer.OnExecute routine?
procedure TMyConnServer.ServerExecute(AContext: TIdPeerThread);
var
buffSize: integer;
str: string;
begin
AContext.Connection.ReadFromStack(True, 600, False);
buffSize := AContext.Connection.InputBuffer.Size;
if (buffSize > 0) then
{ Extract input buffer as string }
str := AContext.Connection.ReadString(buffSize);
{ Notify connection object of received data }
if (AContext.Data <> nil) then
begin
TConnectionHandler(AContext.Data).Read(str);
end;
end;
end;
Second (actually more important) Question:
Now there is occasionally an access violation (read from address 000000). Obviously in the line:
AContext.Connection.ReadFromStack(True, 600, False);
but checking if AContext / Connection / InputBuffer / IOHandler = nil BEFORE is false. AFTER the call (and after the exception was raised) the IOHandler is nil.
We are using RAD Studio / Delphi 2007.
Solution
The only way the IOHandler can become nil like you describe is if another thread in your app called Disconnect() on the connection while your worker thread was still running.
OTHER TIPS
Well, the simplest onExecute handler I have is like this. (excuse C++ instead of Delphi, but you'll get the idea.
void __fastcall MyPanel::DoTCPExecute(TIdPeerThread * AThread)
{
AnsiString text =AThread->Connection->ReadLn();
// now do something with text
}
The only obvious issue I can see is that you're trying to use the "timing" of data to determine when you have a complete string. This is a real no-no with TCP. You might just have the first byte of a string, or you might have several strings all sent at once. With TCP there's no guarantee that each "send" ends up as single "receive" at the other end.
You need to "delimit" your string in some other way. Readln uses the newline character as a terminator - another approach is to prefix each chunk of data with a length field. You read the length, then read the remaining data.
The code works like that, but I don't think it's a clean option:
if (AContext.Connection.Connected) then
begin
try
AContext.Connection.ReadFromStack(false, 1, false);
except on E: EAccessViolation do
// ignore
end;
end;
buffSize := AContext.Connection.InputBuffer.Size;