Search code examples
delphitcpfiremonkeyindy

TCP/IP Indy Chat Delphi 10.3.3 FMX (using internet, not only LAN)


I tried making a simple chat system in Delphi 10.3.3 Community Edition using FireMonkey and the Indy components TIdTCPClient and TIdTCPServer.

It works fine if the Client and Server are located in the same WiFi network (Server = Windows 10 and Client = Android 10). I used the computer's IPv4 address shown in ipconfig to get the computer's IP, and my mobile phone successfully connects.

But, if I use the internet IP (got it from https://www.whatismyip.com/de/), the client shows the german equivalent to "socket error # 111 connection refused", so what am I missing? I entered the IP in the edit box to connect to - so if the local IP works, why doesn't any other IP work as well?

Here is the code I used:

unit Unit1;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes,
  System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.StdCtrls,
  FMX.Controls.Presentation, IdCustomTCPServer, IdTCPServer, IdBaseComponent,
  IdComponent, IdTCPConnection, IdTCPClient, IdContext, FMX.ScrollBox, FMX.Memo,
  FMX.Edit;

type

  TForm1 = class(TForm)
    GroupBox1: TGroupBox;
    GroupBox2: TGroupBox;
    Button1: TButton;
    IdTCPClient1: TIdTCPClient;
    IdTCPServer1: TIdTCPServer;
    Edit1: TEdit;
    Edit2: TEdit;
    Memo1: TMemo;
    Edit3: TEdit;
    procedure Button1Click(Sender: TObject);
    procedure IdTCPServer1Connect(AContext: TIdContext);
    procedure IdTCPServer1Disconnect(AContext: TIdContext);
    procedure IdTCPServer1Execute(AContext: TIdContext);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}

procedure TForm1.Button1Click(Sender: TObject);
begin
  IdTCPClient1.Host := Edit1.Text;
  IdTCPClient1.Port := StrToInt(Edit2.Text);
  IdTCPClient1.Connect;
  if IdTCPClient1.Connected then
  begin
    IdTCPClient1.IOHandler.WriteLn(Edit3.Text);
    IdTCPClient1.Disconnect;
  end;
end;

procedure TForm1.IdTCPServer1Connect(AContext: TIdContext);
var
ip:String;
begin
  ip:=AContext.Binding.PeerIP;
  TThread.Synchronize(nil,
    procedure
    begin
      Memo1.Lines.Add('connect: ' + ip)
    end);
end;

procedure TForm1.IdTCPServer1Disconnect(AContext: TIdContext);
var
ip:String;
begin
  ip:=AContext.Binding.PeerIP;
  TThread.Synchronize(nil,
    procedure
    begin
      Memo1.Lines.Add('disconnect: ' + ip)
    end);
end;

procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);
var
msg:String;
begin
  msg:=AContext.Connection.IOHandler.ReadLn;
  TThread.Synchronize(nil,
    procedure
    begin
      Memo1.Lines.Add('message: ' + msg)
    end);
end;

end.

Solution

  • First off, TIdTCPServer is a multi-threaded component. Its events are fired in the context of worker threads. As such, you must synchronize with the main UI thread when accessing UI controls, like your TMemo. Without that synchronization, bad things can happen, including but not limited to: crashes, deadlocks, corrupting UI controls, making the UI unresponsive to the user, etc.

    That being said, the TIdTCPServer.Bindings collection can bind listening sockets only to local IPs that belong to the PC that TIdTCPServer is running on.

    If the PC is connected directly to the Internet modem, then the modem's public Internet IP is assigned directly to the PC, so TIdTCPServer will be able to bind to the Internet IP.

    However, if the PC is connected to a LAN network instead (via Ethernet or WiFi), then TIdTCPServer cannot bind directly to the Internet IP, only to the PC's LAN IP. As such, you will have to setup port forwarding on the network router (via the router's administration site/app, or through uPNP if enabled) to forward inbound traffic from the router's WAN IP/Port to the Server PC's LAN IP/Port. Then clients can connect to the rouer's Internet IP and be forwarded to TIdTCPServer.