1

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.
qGold
  • 341
  • 2
  • 14

1 Answers1

2

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.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thanks. I guess this is the solution, but although I configured the router to allow port forwarding on my device it still doesnt connect and shows error #111 as it did before. – qGold Nov 24 '20 at 19:45
  • @qGold Then you did not setup either the port forwarding or the listening server correctly. Please provide the actual IP/port pair(s) that the server is listening on, and the actual WAN IP/port -> LAN IP/port mapping that you setup for port forwarding. – Remy Lebeau Nov 24 '20 at 19:58
  • Now I added synchronized threading in my code above. Does this look correct? It works fne and now without any errors or UI corruptions. – qGold Nov 25 '20 at 18:09
  • is there a way for me to get a client server connection without port forwarding on my router? (actually I can modify the routers settings, but it would be nice to be able to let any user act as client AND server to exchange data without having to edit the routers configuration) – qGold Nov 25 '20 at 18:24
  • "*Does this look correct?*" - almost. Don't perform the `AContext` operations inside the synced code. Perform them in the calling thread, and save the results to local variables that the anonymous procedures can then capture, eg: `var s: string; s := AContext.Binding.PeerIP; TThread.Synchronize(nil, procedure begin Memo1.Lines.Add('connect: ' + s); end);` ... `var s: string; s := AContext.Connection.IOHandler.ReadLn; TThread.Synchronize(nil, procedure begin Memo1.Lines.Add('message: ' + s); end);` – Remy Lebeau Nov 25 '20 at 18:31
  • "*is there a way for me to get a client server connection without port forwarding on my router?*" - Make your LAN app use a client socket that connects to a server socket in the remote app. That way the connection through the router is outbound LAN->WAN rather than inbound WAN->LAN. Otherwise, if the router has uPNP enabled, you can configure port forwarding directly from your server code. Otherwise, there are various "hole punching" techniques you can try to employ to open ports in the router dynamically, but that is an advanced topic that is out of the scope of Indy itself. – Remy Lebeau Nov 25 '20 at 18:34
  • okay, now I outsourced `AContext` operations into variables. I tried using `TClientSocket` and `TServerSocket`, but in my FMX they dont appear to be available. I can just use them for VCL projects without problems, but in FMX they are not working. I also tried loading the `dclsockets260.bpl` using "components>install package" but this didnt work either. Can you tell me how to use client and server sockets in fmx multi device applications as well? Is this maybe a limitation of the community edition? – qGold Nov 26 '20 at 14:19
  • @qGold `TClientSocket`/`TServerSocket` work only on Windows, and are available only in VCL projects, not FMX projects. Indy is cross-platform, and is available in both VCL and FMX projects. – Remy Lebeau Nov 26 '20 at 17:44
  • I see, but with the Indy solution I dont get it to work as I can just send messages in my own LAN/WiFi network and not across the internet. I tried activating port forwarding but this doesn't fix it. – qGold Nov 27 '20 at 17:43
  • @qGold then you didn’t setup the forwarding correctly, like I mentioned 2 days ago in my very 1st comment, which you still have not answered. TIdTCPServer works just fine with clients over the internet when everything is configured properly. This is a general TCP routing issue, not an Indy issue. – Remy Lebeau Nov 27 '20 at 18:06
  • Then I'll double check my routers settings again, but as I already set up everything my FritzBox offers I dont think thats the problem. I will check my routers settings anyhow, but everything i tried yet didn't work.. – qGold Dec 25 '20 at 22:15
  • @qGold and yet, you still have not provided any DETAILS about what you have actually tried, so I/we can’t tell you how you are setting them up incorrectly. Which you obviously are, or else you wouldn’t be having this problem in the first place. – Remy Lebeau Dec 25 '20 at 23:03
  • I'll screenshot my routers settings the next days and upload them here so you can see what I already tried – qGold Dec 25 '20 at 23:15