4

A TCP server is sending data frames continuosly every 8ms. I want to program a client able to receive these data frames. Is there any procedure in Indy 9 to know if there is data available in the buffer?

My current programs is the following (I am using a Thread):

procedure TThreadRead.Execute;
var
  buffer: array [0..755] of byte;
  //s1: string;
  //i: integer;
begin
  IdTCPClient1.RecvBufferSize:= 756;
  IdTCPClient1.Connect;
  while Terminated = false do
  begin
    if IdTCPClient1.InputBuffer.Size = 0 then
       IdTCPClient1.ReadFromStack(True,0,False);
    while IdTCPClient1.InputBuffer.Size > 0 do
    begin
       ReadBuffer(buffer, FClient.InputBuffer.Size);
       //s1:= '';
       //For i:=0 To Length(buffer)-1 Do
       //  s1:=s1+IntToHex(Ord(buffer[i]),2); //Read values-->global var
       //Form1.Memo1.Text:=s1;
    end;
  end;
end;

Is there any more efficient solution for reading TCP data continuously (like onread event in UDP)?

Thanks in advance.

user1361263
  • 101
  • 2
  • 5
  • 11

1 Answers1

7

TIdTCPClient is not an asynchronous component. It does not tell you when data arrives. You need to use a Timer or a Thread to periodically poll the socket for new data (TIdUDPServer uses an internal thread to trigger its OnUDPRead event), eg:

procedure TForm1.Button1Click(Sender: TObject);
begin
  IdTCPClient1.Connect;
  Timer1.Enabled := True;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  Timer1.Enabled := False;
  IdTCPClient1.Disconnect;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
var
  s1: string;
begin
  s1 := IdTCPClient1.CurrentReadBuffer;
  ...
end;

With that said, CurrentReadBuffer() is generally not the best choice to use. Typically you would do something more like this instead:

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  Timer1.Enabled := False;

  IdTCPClient1.ReadFromStack(True, 0, False);

  while IdTCPClient1.InputBuffer.Size > 0 do
  begin
    // read one complete frame and process as needed ...
  end;

  Timer1.Enabled := True;
end;

Update: given new information about the frame structure and your switch to a thread, you should be doing this instead:

procedure TThreadRead.Execute;
var
  buffer: array of Byte;
  numbytes: Integer;
begin
  SetLength(buffer, 0);
  IdTCPClient1.Connect;
  try
    while not Terminated do
    begin
      numbytes := StrToInt('$' + IdTCPClient1.ReadString(8)) - 8;
      if numbytes <> Length(buffer) then
        SetLength(buffer, numbytes);
      if numbytes > 0 then
        IdTCPClient1.ReadBuffer(buffer[0], numbytes);
      // process buffer up to numbytes as needed...
    end;
  finally
    IdTCPClient1.Disconnect;
  end;
end;
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • I am doing something wrong, because your code is not working... why do you read from stack when inputbuffer.size = 0? Why is not CurrentReadBuffer() the best choice to use? Is TidTCPServer an asynchronous component? Maybe it is easier to use a TCP server for reading data... – user1361263 Jun 05 '12 at 09:42
  • 1
    Indy does not read data from the socket (ie the Stack) until you tell it to read something. When the timer triggers, it checks if the `InputBuffer` is empty, and if so then calls `ReadFromStack()` to read whatever data is currently in the socket at that moment, if any, and puts it in the `InputBuffer`. The code does not try to process any frames until data has reached the `InputBuffer` first, indicating that there is something available to process. If your `InputBuffer` is always empty, even after calling `ReadFromStack()`, then the server is never sending any data to your client at all. – Remy Lebeau Jun 05 '12 at 17:36
  • You cannot use `TIdTCPServer` to communicate with another server. You have to use `TIdTCPClient` for that. – Remy Lebeau Jun 05 '12 at 17:38
  • If your server really is sending data frames every 8ms, then there is no benefit to doing the `InputBuffer.Size` checking at all. Just call `ReadFromStack()` immediately and then read as many frames are present in the `InputBuffer`, and then set the timer interval as low as you can (better would be to use a separate thread instead, since `TTimer` cannot operate at 8ms precision). Doing the kind of `Size` checking I showed is more beneficial when there are longer delays between the frames. – Remy Lebeau Jun 05 '12 at 17:44
  • I have updated my answer accordingly, though the earlier `InputBuffer.Size` checking code should have worked fine. – Remy Lebeau Jun 05 '12 at 17:46
  • I tried your code and I am able to read data from stack, however after reading several data frames my program stops to receive data (inputBuffer.size becomes zero...). If I disconnect the TCP connection and start it again, I can read data from stack again. – user1361263 Jun 10 '12 at 13:05
  • I do not understand why my TCP client stops to receive data – user1361263 Jun 10 '12 at 13:07
  • Then you are still not reading correctly. Please update your question with the latest code you are using. And please show what the actual frame data look like that is being transmitted by the server. – Remy Lebeau Jun 10 '12 at 17:17
  • I have updated my first post with the current program I am using. The TCP server is sending frames of 756 bytes. I am receiving correct frames (the header is always the same). However after receiving several frames (between 200 and 1000), I receive a last frame with size smaller than 756 and I do not receive more frames. I need to disconnect and connect again a TCP connection to receive data again. This error also happens using timers instead of a Tthread. – user1361263 Jun 11 '12 at 07:49
  • The code you have shown is still misusing the `InputBuffer`, but you have not yet shown what the frame structure actually looks like so I cant show you what your reading code neds to look like. Again, I ask you to please show what the frames look like. However, the fact that the server sends a smaller frame is likely an indication that the server is simply be running out of data to send to you. Please provide more information about how the frames themselves work, regardless of how your code needs to reads them. – Remy Lebeau Jun 12 '12 at 00:10
  • TCPserver sends every 8ms a data frame of hexadecimal values (756 bytes) that starts with the following header "000002F4". Because `IdTCPClient1.RecvBufferSize:= 756` (same size of the data frame), in my TMemo I can see every data frames starting with the header "000002F4". If I change the buffer size (e.g. 10000), received frames do not start with "000002F4"... – user1361263 Jun 12 '12 at 07:31
  • In fact if the buffer size is increased, TIdClient read the input buffer less times before stoping... – user1361263 Jun 12 '12 at 07:46
  • The `RecvBufferSize` has no effect on the code I showed you. It is merely the size of the buffer used for **Indy's** internal reading, not **your** reading. `000002F4` is hex notation for `756`, so what you should be doing is reading 8 characters, convert them to an integer, and then read however many bytes the integer says. I have updated my answer to show that. – Remy Lebeau Jun 13 '12 at 00:04
  • Dear Remy, thanks for your response. But the program still freezes... Even if I read less bytes from the buffer each time `IdTCPClient1.ReadBuffer(buffer[0], 100)` (knowing that the server is sendending 756bytes every time...) No error occurs... just it freezes after reading several frames – user1361263 Jun 13 '12 at 16:30
  • It sounds like the value of the header includes the length of the header itself. If so, then you have to subtract 8 when calling `ReadBuffer()`. I updated my answer according. – Remy Lebeau Jun 13 '12 at 19:53
  • Dear Remy, I decreased the frecuency that the TCP server uses to send data frames (10Hz, 1frame every 0.1seconds) and your code works perfectly. However, if I increase the frecuency (125Hz, 1 frame every 0.008seconds), the program freezes after receiving between 200 and 300 frames... – user1361263 Jun 14 '12 at 21:46
  • The only way that increasing the frequency and changing nothing else would affect Indy's ability to read the frames is if the frames are getting corrupted, such as if the device is overlapping them, sending a new frame before a previous frame has finished being sent. A packet sniffer, like Wireshark, can show you what data Indy is actually receiving from the network. That will show you if corrupting is really occuring or not. – Remy Lebeau Jun 14 '12 at 23:15
  • Dear Remy, thank you for helping me with this issue. Finally, it worked properly taking into account your advices. – user1361263 Jun 29 '12 at 08:35