Search code examples
delphisocketstcpclientindy

How can I wait for a string from a server with IdTCPClient?


I have a problem with IdTelnet (indy 10.1). I can't read the data from a server in Unicode mode. and now I want to write the telnet terminal with IdTCPClient.

The server sometimes send one line and sometimes more and more lines. But there is not a fixed time between sending.

Now my problem is that when I must read data from InBuffer.

Or when I must use the ReadLn function to read the data from the server, how many times must I run the ReadLn?


Solution

  • TIdTelnet is a multithreaded component. It has an internal thread that continuously reads from the socket, firing the TIdTelnet.OnDataAvailable event whenever a buffer of data is available.

    TIdTelnet is a TIdTCPClient descendant. Look in the IdTelnet.pas source file to see how it is implemented. You can do something similar in your own code, calling TIdIOHandler.ReadLn() in your own thread, eg:

    type
      TMyThread = class(TThread)
      private
        FConn: TIdTCPConnection;
      protected
        procedure Execute; override;
      public
        constructor Create(AConn: TIdTCPConnection); reintroduce;
      end;
    
    constructor TMyThread.Create(AConn: TIdTCPConnection);
    begin
      inherited Create(False);
      FConn := AConn;
    end;
    
    procedure TMyThread.Execute;
    var
      S: String;
    begin
      while not Terminated do
      begin
        S := FConn.IOHandler.ReadLn(...);
        ...
      end;
    end;
    
    var
      Thread: TMyThread = nil;
    
    procedure TForm1.ConnectButtonClick(Sender: TObject);
    begin
      IdTCPClient1.Connect;
      try
        Thread := TMyThread.Create(IdTCPClient1);
      except
        IdTCPClient1.Disconnect;
        raise;
      end;
    end;
    
    procedure TForm1.DisconnectButtonClick(Sender: TObject);
    begin
      if Assigned(Thread) then Thread.Terminate;
      try
        IdTCPClient1.Disconnect;
      finally
        if Assigned(Thread) then
        begin
          Thread.WaitFor;
          FreeAndNil(Thread);
        end;
      end;
    end;
    

    If you don't want to use a thread, then you can use a timer instead. To make sure your timer thread (such as the main thread) is not blocked unnecessarily, use the TIdIOHandler.CheckForDataOnSource() method with a small timeout whenever the TIdIOHandler.InputBuffer is empty, before then calling TIdIOHandler.ReadLn() only when data is available, eg:

    procedure TForm1.ConnectButtonClick(Sender: TObject);
    begin
      IdTCPClient1.Connect;
      ReadTimer.Enabled := True;
    end;
    
    procedure TForm1.DisconnectButtonClick(Sender: TObject);
    begin
      ReadTimer.Enabled := False;
      IdTCPClient1.Disconnect;
    end;
    
    procedure TForm1.ReadTimerElapsed(Sender: TObject);
    var
      S: String;
    begin
      if IdTCPClient1.IOHandler.InputBufferIsEmpty then
      begin
        IdTCPClient1.IOHandler.CheckForDataOnSource(10);
        if IdTCPClient1.IOHandler.InputBufferIsEmpty then Exit;
      end;
      S := IdTCPClient1.IOHandler.ReadLn(...);
      ...
    end;