Jump to content

Recommended Posts

Hello,

 

I am trying to create the app that sends a messages to the server and gets back an answer from it in a thread on the regular time intervals. Request contains 6 bytes, answer should be 65 bytes. My code looks like that:

//TThread Constructor
  inherited Create(True);
  TCPClient := TIdTCPClient.Create;
  TCPClient.Host := AHost;
  TCPClient.Port := APort;
  TCPClient.ConnectTimeout := 5000;
  TCPClient.ReuseSocket := rsTrue;

//TThread Execute procedure
SetLength(wBuffer, 6);
  //Here i do write some bytes
  SetLength(rBuffer, 65);
  while not Terminated do
  begin
    try
      TCPClient.Connect;
    except on E: Exception do
      begin
        TThread.Queue(nil,
          procedure
            begin
              Form2.mmo1.Lines.Add('Exception: ' + e.Message);
            end);
        for i := 1 to 5 do
        begin
          if not Terminated then
            Sleep(1000);
        end;
        Continue;
      end;
    end;
    Reading := False;
    i := 1;
    while not Terminated do
    begin
      if not Reading then
      begin
        Reading := True;
        TCPClient.IOHandler.Write(wBuffer, 6, 0);
      end;
      try
        if Reading then
        begin
         if TCPClient.IOHandler.CheckForDataOnSource(100) then
         begin
          TCPClient.IOHandler.ReadBytes(rBuffer, 65, False);
          TThread.Queue(nil,
            procedure
              begin
                Form2.mmo1.Lines.Add('Buffer size: ' + IntToStr(TCPClient.IOHandler.InputBuffer.Size));
              end);
          if not TCPClient.IOHandler.InputBufferIsEmpty then
          begin
			//I do some stuff here
            i := i +1;
            Reading := False;
            Sleep(1000);
          end;
         end;
        end;
      except on E: Exception do
        begin
        TThread.Queue(nil,
          procedure
            begin
              Form2.mmo1.Lines.Add('Reading exception: ' + e.Message);
            end);
          Sleep(1000);
          Continue;
        end;
      end;
    end;
  end;

I do get only one line in the memo:

Buffer size: 0

What am i doing wrong? How can i fix it?

 

Thank you in advance.

Share this post


Link to post
5 hours ago, @rturas said:

 


  TCPClient.ReuseSocket := rsTrue;

There is usually no need to ever use ReuseSocket on a client, unless you use its BoundIP and BoundPort properties to make it connect from a specific local IP/Port pair, AND you are connecting to the same remote IP/Port pair as a previous connection from the same local IP/Port.  By default, a client socket connects from a random local port.

 

ReuseSocket is more commonly used only on servers instead. When a server gets shutdown and restarted quickly, ReuseSocket can can allow it to listen on the same local IP/Port as the previous run, in case the OS hasn't released the local IP/Port yet.

Quote

I do get only one line in the memo:


Buffer size: 0

What am i doing wrong? How can i fix it?

You are using the InputBuffer the wrong way.


After you Write() out your wBuffer, you are waiting in an endless loop until a reply arrives. Why are you using a loop at all?  That is not necessary.

 

Indy's reading behavior is designed to block the calling thread waiting for requested data to arrive.  Most of the IOHandler's reading methods get their data from the InputBuffer only, not from the socket directly.  CheckForDataOnSource() reads directly from the socket and saves whatever it receives into the InputBuffer.  So, if you ask a reading method (ie, in this case, ReadBytes()) to read something (ie, in this case, 65 bytes), the method does not exit until all of the bytes for that something are available in the InputBuffer, or until the ReadTimeout elapses (which is infinite by default).

 

In your case, when you do eventually get a reply, you are reading it from the InputBuffer into your rBuffer, but then you are ignoring rBuffer that you just read into and instead you are checking the InputBuffer directly to see if it has any buffered bytes that you have NOT read yet, and only if it DOES then you are flagging yourself to make the next Write() call.  But if the InputBuffer is EMPTY (because you have already read everything that was in it) then you are NEVER calling Write() again, and you end up stuck in your reading loop waiting for CheckForDataOnSource() to receive new bytes from the socket which you are NEVER requesting the server to send.

 

You have over-complicated your thread logic.  You don't need all of that InputBuffer handling at all.  Just call Write(), then ReadBytes() (letting it block), and repeat.

 

That being said, there are some other issues with your code, too.  You are not calling Disconnect() after Connect() is successful.  You are accessing the InputBuffer in the main threadd while your worker thread may also be accessing it at the same time.  And you are not capturing the Exception when calling TThread.Queue(), so the Exception will be destroyed long before the main thread has a chance to access its Message.

 

With all of that said, try this instead:

  inherited Create(True);
  TCPClient := TIdTCPClient.Create;
  TCPClient.Host := AHost;
  TCPClient.Port := APort;
  TCPClient.ConnectTimeout := 5000;
  TCPClient.ReadTimeout := ...; // infinite by default

...

// a separate procedure is needed so that TThread.Queue() can
// capture and extend the lifetime of the String. See the
// documentation for more details:
// https://docwiki.embarcadero.com/RADStudio/Sydney/en/Anonymous_Methods_in_Delphi
//
procedure DisplayMessageInUI(const AMsg: string);
begin
  TThread.Queue(nil,
    procedure
    begin
      Form2.mmo1.Lines.Add(AMsg);
    end);
end;

...

  SetLength(wBuffer, 6);
  //write some bytes into wBuffer
  SetLength(rBuffer, 65);
  while not Terminated do
  begin
    try
      TCPClient.Connect;
    except
      on E: Exception do
      begin
        DisplayMessageInUI('Exception: ' + e.Message);
        for i := 1 to 5 do
        begin
          if Terminated then Exit;
          Sleep(1000);
        end;
        Continue;
      end;
    end;
    try
      try
        i := 1;
        while not Terminated do
        begin
          TCPClient.IOHandler.Write(wBuffer);

          TCPClient.IOHandler.ReadBytes(rBuffer, 65, False); // waits for reply
          //
          {
          Alternatively, if you want to keep checking Terminated while waiting:
          
          while TCPClient.IOHandler.InputBufferIsEmpty do
          begin
            TCPClient.IOHandler.CheckForDataOnSource(100);
            TCPClient.IOHandler.CheckForDisconnect;
            if Terminated then Exit;
          end;
          TCPClient.IOHandler.ReadBytes(rBuffer, 65, False);
          }

          DisplayMessageInUI('Reply received');
          //do some stuff with rBuffer
          Inc(i);
          Sleep(1000);
        end;
      finally
        TCPClient.Disconnect;
      end;
    except
      on E: Exception do
      begin
        DisplayMessageInUI('Exception: ' + e.Message);
        if Terminated then Exit;
		Sleep(1000);
      end;
    end;
  end;

 

Edited by Remy Lebeau
  • Thanks 2

Share this post


Link to post

Hello Remy,

 

Thank you so much for the explanation. It is highly appreciated.

 

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×