Home > Mobile >  TIdTCPServer OnExecute fires multiple times
TIdTCPServer OnExecute fires multiple times

Time:10-28

I have an app that receives a TCP socket connection from another app, processes the content that is streamed across the socket, builds a request based on the received data, sends the request using TIdHTTP to an external server, processes the response, and sends an ack back to the tcp client.

procedure TMyClass.TCPExecute(AContext: TIdContext);
var
  s: AnsiString;
begin
  s := TransferTCPBytesToString(AContext.Connection.IOHandler);
  // test string for terminating characters
  if Pos(#28#13, s) > 0 then
  begin
  //********PROBLEM: THIS WILL BE CALLED MULTIPLE TIMES 
  //********WITH THE SAME COMPLETE DATA STREAM CONTENT!
    BuildAndSendExternalRequest(s);
    AContext.Connection.IOHandler.InputBuffer.Clear;
    // send the response back to tcp client
    WriteTCPResponse(AContext.Connection.IOHandler);
  end;
end;

function TMyClass.TransferTCPBytesToString(AnIOHandler: TIdIOHandler): AnsiString;
begin
  if AnIOHandler.InputBufferIsEmpty then
    Exit;
  // read message from tcp client connection
  AnIOHandler.ReadBytes(FTCPBytes, AnIOHandler.InputBuffer.Size, True);
  SetLength(Result, Length(FTCPBytes));
  // copy message bytes to string
  Move(FTCPBytes[0], Result[1], Length(FTCPBytes));
end;

procedure TMyClass.WriteTCPResponse(AnIOHandler: TIdIOHandler);
var
  bytes: TBytes;
begin
  // FTCPBytes contains data from tcp client
  // bytes will contain processed response from external server
  BuildACK(FTCPBytes,bytes);
  // FTCPBytes now contains ACK data
  AnIOHandler.WriteDirect(FTCPBytes, Length(FTCPBytes));
end;

The request/response cycle completes successfully; the response sent back to the tcp client requestor is correct and the process executes and completes as expected.

However, because the OnExecute event fires multiple times with the same data, two requests are received, built, sent, acknowledged etc.

In looking at the logs on the tcp client app, the logs only show one request being sent. Not sure what is going on here. But I need to make sure that the request/response cycle is 1-1, not 1-many!

Thanks.

CodePudding user response:

the OnExecute event fires multiple times ...

It is supposed to, as it is a looped event. The loop runs for as long as the connection is alive. The code inside your OnExecute event handler is responsible for deciding what happens on each loop iteration.

The ideal situation is where the event handler receives 1 message from the client, sends 1 response back to the client, and then exits, so that the next loop iteration is ready to handle the next request/response pair.

Which, based on your description, you are trying to do, but the code shown is not handling that very well. Most notably, the code is not accounting for the byte-stream nature of TCP. It does not differentiate one incoming message from another. It just dumps the entire IOHandler.InputBuffer content as-is into an AnsiString (why an AnsiString?), and if that AnsiString happens to contain the delimiter #28#13 then you are processing the entire AnsiString and then throwing it away, losing anything received after the delimiter (ie, part of the next message). And if the AnsiString doesn't contain the delimiter, then you are still throwing away the AnsiString, thus losing all data received so far for the current message before the delimiter arrives.

If all of your messages are delimited by #28#13, then I suggest you use the IOHandler.ReadLn() or IOHandler.WaitFor() method to read each message properly.

Also, you are re-using the same FTCPBytes member for both input and output, which in of itself is wrong, but you are also not providing any concurrent access protection for it if you have multiple client connections present.

With that said, try something more like this instead:

procedure TMyClass.TCPConnect(AContext: TIdContext);
begin
  AContext.Connection.IOHandler.DefStringEncoding := IndyTextEncoding_8Bit;
end;

procedure TMyClass.TCPExecute(AContext: TIdContext);
var
  s: String;
  response: TIdBytes:
begin
  s := TransferTCPBytesToString(AContext.Connection.IOHandler);
  BuildAndSendExternalRequest(s, response);
  // send the response back to tcp client
  WriteTCPResponse(AContext.Connection.IOHandler, response);
end;

function TMyClass.TransferTCPBytesToString(AnIOHandler: TIdIOHandler): String;
begin
  // read message from tcp client connection
  Result := AnIOHandler.ReadLn(#28#13);
  or:
  Result := AnIOHandler.WaitFor(#28#13);
end;

procedure TMyClass.BuildAndSendExternalRequest(const Msg: String; var Response: TIdBytes);
begin
  // send Msg to external server as needed...
  // store response bytes in Response...
end;

procedure TMyClass.WriteTCPResponse(AnIOHandler: TIdIOHandler; const Response: TIdBytes);
var
  bytes: TIdBytes;
begin
  // Response contains processed response from external server
  BuildACK(bytes, Response);
  // bytes now contains ACK data
  AnIOHandler.Write(bytes);
end;

... with the same data

That is not possible given the code you have shown. The call to IOHandler.ReadBytes() in TransferTCPBytesToString() is reading and discarding all bytes from the IOHandler.InputBuffer, so there is no way the same bytes can still be in that buffer when TransferTCPBytesToString() is called again at a later time. So, the only way you could be seeing the same data over and over is if the client is sending the same data over and over to begin with.

  • Related