Search code examples
delphifiremonkeytcpclientindy

FMX, Delphi, TIdTCPClient


I have created two TIdTCPClient objects and connected them to the same server.

The server receives bytes from one client and sends them to the other. This client sends it to the first client and so on in a circle.

The problem is that it doesn't work after I push the button while the timer is disabled. But if I activate the timer, it works with the timer's frequency.

type
  TForm2 = class(TForm)
    Client1: TIdTCPClient;
    Client2: TIdTCPClient;
    Timer1: TTimer;
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
  private
    idThreadComponent : TIdThreadComponent;
    procedure IdThreadComponentRun(Sender: TIdThreadComponent);
    procedure IdTCPClientConnected(Sender: TObject);
  end;

procedure TForm2.FormCreate(Sender: TObject);
  begin
    idThreadComponent:= TIdThreadComponent.Create();
    idThreadComponent.OnRun := IdThreadComponentRun;

    Client1.Port:= 1202;
    Client1.Host:= 'localhost';
    Client2.Port:= 1203;
    Client2.Host:= 'localhost';

    Client1.OnConnected:= IdTCPClientConnected;
    Client2.OnConnected:= IdTCPClientConnected;

    Client1.Connect;
    Client2.Connect;
  end;

procedure TForm2.IdThreadComponentRun(Sender: TIdThreadComponent);
  var
    Rx: TIDBytes;
  begin
    if not Client1.IOHandler.InputBufferIsEmpty then
      begin
        Client1.IOHandler.ReadBytes(Rx, Client1.IOHandler.InputBuffer.Size);
        if Length(Rx) > 0 then
          begin
            Client2.IOHandler.Write(Rx);
          end
      end;

    if not Client2.IOHandler.InputBufferIsEmpty then
      begin
        Client2.IOHandler.ReadBytes(Rx, Client2.IOHandler.InputBuffer.Size);
        if Length(Rx) > 0 then
          begin
            Client1.IOHandler.Write(Rx);
          end;
      end;
  end;

procedure TForm2.IdTCPClientConnected(Sender: TObject);
  begin
    IdThreadComponent.Active  := True;
  end;

procedure TForm2.Button1Click(Sender: TObject);
  var
    Tx: TIDBytes;
  begin
    SetLength(Tx, 1);
    Client1.IOHandler.Write(Tx,length(Tx));
  end;

procedure TForm2.Timer1Timer(Sender: TObject);
  begin
    if not Client1.Connected then
      begin
      end;
    if not Client2.Connected then
      begin
      end;
  end;

Solution

  • The problem is that the only place you are actually reading incoming data into the two InputBuffers is in the timer event. Without the two calls to Connected() (which perform reads internally), the InputBuffers will always be empty, so there is no work for the thread to do.

    Get rid of the timer, and have the thread use CheckForDataOnSource() instead, eg:

    procedure TForm2.IdThreadComponentRun(Sender: TIdThreadComponent);
      var
        Rx: TIdBytes;
      begin
        Client1.IOHandler.CheckForDataOnDource(0);
        Client1.IOHandler.CheckForDisconnect;
        if not Client1.IOHandler.InputBufferIsEmpty then
          begin
            Client1.IOHandler.ReadBytes(Rx, -1);
            Client2.IOHandler.Write(Rx);
            Rx := nil;
          end;
    
        Client2.IOHandler.CheckForDataOnSource(0);
        Client2.IOHandler.CheckForDisconnect;
        if not Client2.IOHandler.InputBufferIsEmpty then
          begin
            Client2.IOHandler.ReadBytes(Rx, -1);
            Client1.IOHandler.Write(Rx);
            Rx := nil;
          end;
      end;
    

    Alternatively, just use 2 separate reading threads and let ReadBytes() block normally, eg:

    procedure TForm2.IdThreadComponent1Run(Sender: TIdThreadComponent);
      var
        Rx: TIdBytes;
      begin   
        Client1.IOHandler.ReadBytes(Rx, -1);
        Client2.IOHandler.Write(Rx);
      end;
    
    procedure TForm2.IdThreadComponent2Run(Sender: TIdThreadComponent);
      var
        Rx: TIdBytes;
      begin   
        Client2.IOHandler.ReadBytes(Rx, -1);
        Client1.IOHandler.Write(Rx);
      end;