views:

545

answers:

3

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.

A: 

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.

Roddy
Hi, I'm afraid ReadLn() is no option because the data has no line feed delimiter.I know that the data may not be complete (and often is not), but the re-construction is done later in the .Read(str) Method.Actually the datatype 'string' is just used to transport raw bytes (because this is what how we need the data later).And to the exception: ReadLn() can internally also call ReadFromStack() which would again lead to a EAccessViolation...
Tarnschaf
If the data has a delimiter of any kind, you can pass that delimiter to ReadLn(), it is not limited to CR/LF characters only. If the data is delimited another way (such as via a preceeding headers that specifies how to read the remaining data), then you have to handle that individually. It is not a good idea to use strings as raw byte buffers, especially if you upgrade to D2009, where string behavior has changed. If you need to operate on raw bytes, then use actual raw byte buffers, such as TBytes. Indy has methods available for reading/writing raw bytes.
Remy Lebeau - TeamB
A: 

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;
Tarnschaf
Indy handles the Connected() calls internally for you. Connected() returns True even though the socket may have already been closed if there is still unread data in the InputBuffer (this is by design). You should get rid of the call to Connected(), as well as the exception handler (so TIdTCPServer can handle the errors for you (which is necessary for proper socket management). Just call ReadFromStack() normally and let it report any errors it needs to.
Remy Lebeau - TeamB
A: 

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.

Remy Lebeau - TeamB
Well, yes, I guess that's correct. Since ServerExecute functions do not overlap (do they?) can I just use a single CriticalSection and lock when inside ServerExecute and also lock when doing a Disconnect?
Tarnschaf
TIdTCPServer is a multi-threaded component. Each client connection runs in its own thread. Thus, the OnExecute event handler can be running multiple times simultaneous, each one in a different thread context.
Remy Lebeau - TeamB
You do not need to lock access to a server's individual client connections, unless you need to read from them in multiple threads, or write to them in multiple threads (which generally suggests you might have a bad code design to begin with).
Remy Lebeau - TeamB