Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Indy TCPServer doesn't receive all packages from TCPClient when sending packages too frequently

I have a problem with Indy TCP connection. I use Turbo Delphi 2006 with Indy 10. I want to send multiple TCP packages from idTCPClient to idTCPServer.

It works finely, when I want to send only one package, or I insert a sleep(100) command between two calls of the function. But if I call this function too frequently, it doesn't call the server's onExecute every time.

My code for sending:

procedure SendData(var data: TIdBytes) ;
begin
  FormMain.IdTCPClient.Connect ;
  FormMain.IdTCPClient.Socket.Write(data);
  FormMain.IdTCPClient.Disconnect ;
end ;

I call this function several times (5-10 times in a second), and want to process all of these packages in my server application:

procedure TFormMain.IdTCPServerMainExecute(AContext: TIdContext);
var
  data: TIdBytes ;
begin
  AContext.Connection.IOHandler.ReadBytes(data, 4, false)
  // processing data
end

Thank you for your answers in advance!

like image 658
hunyadym Avatar asked Feb 07 '12 00:02

hunyadym


1 Answers

Every time you call Connect(), you are creating a new connection, and TIdTCPServer will start a new thread to handle that connection (unless you enable thread pooling, that is). Is that what you really want? It would be more efficient to have the client leave the connection open for a period of time and reuse the existing connection as much as possible. Disconnect the connection only when you really do not need it anymore, such as when it has been idle for awhile. Establishing a new connection is an expensive operation on both ends, so you should reduce that overhead as much as possible.

On the client side, when you call Write(data), it will send the entire TIdBytes, but you are not sending the length of that TIdBytes to the server so it knowns how many bytes to expect. TIdIOHandler.Write(TIdBytes) does not do that for you, you have to do it manually.

On the server side, you are telling ReadBytes() to read only 4 bytes at a time. After each block of 4 bytes, you are exiting the OnExecute event handler and waiting for it to be called again to read the next block of 4 bytes. Unless the length of the client's source TIdBytes is an even multiple of 4, ReadBytes() will raise an exception (causing the server to disconnect the connection) when it tries to read the client's last block that is less than 4 bytes, so your server code will not receive that block.

Try this instead:

procedure SendData(var data: TIdBytes) ; 
begin 
  FormMain.IdTCPClient.Connect; 
  try
    FormMain.IdTCPClient.IOHandler.Write(Longint(Length(data))); 
    FormMain.IdTCPClient.IOHandler.Write(data); 
  finally
    FormMain.IdTCPClient.Disconnect; 
  end;
end; 

procedure TFormMain.IdTCPServerMainExecute(AContext: TIdContext); 
var 
  data: TIdBytes; 
begin 
  with AContext.Connection.IOHandler do
    ReadBytes(data, ReadLongint, false);
  // process data 
end;

With that said, if changing the client code to send the TIdBytes length is not an option for whatever reason, then use this server code instead:

procedure TFormMain.IdTCPServerMainExecute(AContext: TIdContext); 
var 
  LBytes: Integer;
  data: TIdBytes; 
begin 
  // read until disconnected. returns -1 on timeout, 0 on disconnect
  repeat until AContext.Connection.IOHandler.ReadFromSource(False, 250, False) = 0;
  AContext.Connection.IOHandler.InputBuffer.ExtractToBytes(data);
  // process data 
end;

Or:

procedure TFormMain.IdTCPServerMainExecute(AContext: TIdContext); 
var 
  strm: TMemoryStream;
  data: TIdBytes;
begin 
  strm := TMemoryStream.Create;
  try
    // read until disconnected
    AContext.Connection.IOHandler.ReadStream(strm, -1, True);
    strm.Position := 0;
    ReadTIdBytesFromStream(strm, data, strm.Size);
  finally
    strm.Free;
  end;
  // process data 
end;

Or:

procedure TFormMain.IdTCPServerMainExecute(AContext: TIdContext); 
var 
  strm: TMemoryStream;
begin 
  strm := TMemoryStream.Create;
  try
    // read until disconnected
    AContext.Connection.IOHandler.ReadStream(strm, -1, True);
    // process strm.Memory up to strm.Size bytes
  finally
    strm.Free;
  end;
end;
like image 57
Remy Lebeau Avatar answered Oct 26 '22 20:10

Remy Lebeau