Search code examples
androiddelphiindydelphi-2007delphi-xe7

How to solve Indy socket error #111 connection refused between Delphi 2007 server and RAD XE7 client?


When I click on the button in the server the debugger shows two threads are started, I'm assuming one is the main thread and the other one is the server's thread, but the ServerExecute procedure is never executed, which I think causes the connection refused error.

How can I fix it?

Server code:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, IdBaseComponent, IdComponent, IdCustomTCPServer, IdTCPServer, IdContext,
  StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    IdTCPServer1: TIdTCPServer;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
    procedure ExecuteServer(AContext : TIdContext);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  IdTCPServer1.Bindings.Add.IP := '0.0.0.0';
  IdTCPServer1.Bindings.Add.Port := 2811;
  IdTCPServer1.OnExecute := ExecuteServer;
  IdTCPServer1.Active := True;
end;

procedure TForm1.ExecuteServer(AContext: TIdContext);
begin
  Sleep(Random(3000));
  Memo1.Lines.Add('Hello World');
  AContext.Connection.IOHandler.WriteLn('Hello World');
end;

end.

Client code:

unit Unit1;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,
  IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient, FMX.Layouts,
  FMX.Memo, FMX.StdCtrls, IdGlobal, IdIntercept;

type
  TpocForm1 = class(TForm)
    ButtonConnect: TButton;
    ButtonDisconnect: TButton;
    Memo1: TMemo;
    procedure ButtonConnectClick(Sender: TObject);
    procedure ButtonDisconnectClick(Sender: TObject);
    procedure AddLine(text : String);

  private

  public
    { Public declarations }
  end;

  TpocTCPClientThread = class(TThread)
    TCPClient: TIdTCPClient;
  protected
    procedure Execute; override;
    procedure AddLineToMemo;
    procedure Connect;
    procedure Disconnect;
  end;

var
  pocForm1: TpocForm1;

implementation
{$R *.fmx}
Const
  PC_IP = '192.168.32.252';
  PORT = 2811;

var
  thread: TpocTCPClientThread;

procedure TpocForm1.ButtonConnectClick(Sender: TObject);
begin
  Memo1.Lines.Add('Client connected with server');
  thread:= TpocTCPClientThread.Create(False);
end;

procedure TpocForm1.ButtonDisconnectClick(Sender: TObject);
begin
  thread.Terminate;
  thread.WaitFor;
  FreeAndNil(thread);
  Memo1.Lines.Add('Client disconnected from server');
end;

procedure TpocForm1.AddLine(text : String);
begin
  Memo1.Lines.Add(text);
end;


procedure TpocTCPClientThread.Execute();
begin
  Connect;

  while not Terminated do
  begin
    Synchronize(AddLineToMemo);
  end;

  Disconnect;
end;

procedure TpocTCPClientThread.AddLineToMemo;
begin
  pocForm1.AddLine(TCPClient.IOHandler.ReadLn(IndyTextEncoding_OSDefault()));
end;

procedure TpocTCPClientThread.Connect;
begin
  TCPClient := TIdTCPClient.Create;
  TCPClient.Host := PC_IP;
  TCPClient.Port := PORT;
  TCPClient.Connect;
end;

procedure TpocTCPClientThread.Disconnect;
begin
  TCPClient.Disconnect;
  TCPClient.Free;
end;


end.

edit I forgot to mention, the client is supposed to run on android.


Solution

  • This is wrong:

    IdTCPServer1.Bindings.Add.IP := '0.0.0.0';
    IdTCPServer1.Bindings.Add.Port := 2811;
    

    You are creating two separate bindings (which is why you get two threads started) - one bound to 0.0.0.0:0 and one bound to 0.0.0.0:2811.

    Change it to this:

    with IdTCPServer1.Bindings.Add do begin
      IP := '0.0.0.0';
      Port := 2811;
    end;
    

    Or this:

    IdTCPServer1.Bindings.Add.SetBinding('0.0.0.0', 2811, IdIP_v4);
    

    Or, simply set the TIdTCPServer.DefaultPort to 2811 and do not fill in the Bindings at all, then the server will create a default item bound to 0.0.0.0:2811 for you when activated.

    With that said, there are some other problems with your code.

    Server:

    procedure TForm1.ExecuteServer(AContext: TIdContext);
    begin
      Sleep(Random(3000));
      Memo1.Lines.Add('Hello World'); // <- must be synchronized!
      AContext.Connection.IOHandler.WriteLn('Hello World'); // <- default ASCII encoding used
    end;
    

    Client:

    procedure TpocTCPClientThread.Execute();
    begin
      Connect;
    
      while not Terminated do
      begin
        Synchronize(AddLineToMemo); // <- calling ReadLn() inside of Synchronize()
      end;
    
      Disconnect; // <- not called if an exception is raised
    end;
    
    TCPClient.IOHandler.ReadLn(IndyTextEncoding_OSDefault()) // <- OSDefault is not consistent across platforms
    

    Change them to something more like this instead:

    Server:

    procedure TForm1.Button1Click(Sender: TObject);
    begin
      ...
      IdTCPServer1.OnConnect := ConnectServer;
      ...
    end;
    
    procedure TForm1.ConnectServer(AContext: TIdContext);
    begin
      AContext.Connection.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8();
    end;
    
    procedure TForm1.ExecuteServer(AContext: TIdContext);
    begin
      Sleep(Random(3000));
      TThread.Synchronize(nil,
        procedure
        begin
          Memo1.Lines.Add('Hello World');
        end
      );
      AContext.Connection.IOHandler.WriteLn('Hello World');
    end;
    

    Client:

    procedure TpocForm1.ButtonConnectClick(Sender: TObject);
    begin
      thread := TpocTCPClientThread.Create(False);
    end;
    
    procedure TpocForm1.ButtonDisconnectClick(Sender: TObject);
    begin
      thread.Terminate;
      thread.WaitFor;
      FreeAndNil(thread);
    end;
    
    procedure TpocTCPClientThread.Execute();
    begin
      Connect;
      try
        AddLineToMemo('Client connected with server');
    
        TCPClient.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8();
    
        while not Terminated do
        begin
          AddLineToMemo(TCPClient.IOHandler.ReadLn());
        end;
      finally    
        Disconnect;
        AddLineToMemo('Client disconnected from server');
      end;
    end;
    
    procedure TpocTCPClientThread.AddLineToMemo(text: string);
    begin
      Synchronize(
        procedure
        begin
          pocForm1.AddLine(text);
        end
      );
    end;