Sending data between Embarcadero Delphi FMX apps by using TIdTCPServer and TIdTCPClient components
Sending data between Embarcadero Delphi FMX apps by using TIdTCPServer and TIdTCPClient components

Sending data between Embarcadero Delphi FMX apps by using TIdTCPServer and TIdTCPClient components

The transfer of data between applications on a network is a very important and often necessary task. The implementation of various chats becomes possible, namely thanks to the ability to send data between applications.

For example, in one application, we can write text and transmit it over the network to another application. In addition to any textual data, we can also transmit other information, such as images, media files, etc.

Today, we can’t even imagine our daily lives without using various messaging apps. Quite often, it’s much easier for us to send a message than to make a voice or video call. Along with text messages, we also frequently share various photos with our friends and colleagues.

For data transmission in apps, sockets are used. They serve as interfaces and allow us to simplify the task of data transmission. To send data via sockets, we rely on special protocols for data transmission.

The most widely used ones are TCP (Transmission control protocol) and UDP (User datagram protocol). TCP ensures the reliability of data transmission over the network. Moreover, during the data transmission, the connection between the two apps is established.

UDP works without establishing any connection and no data checks are performed. UDP operates faster but data delivery is not guaranteed while TCP provides this guarantee.

However, in general, establishing a connection and data verification slow down the process. TCP is used for transferring documents, email messages, etc. Meanwhile, UDP is used for streaming data in video conferencing, where maximum data transmission speed is crucial, while the loss of a data segment is not critical.

In such a scenario, we will simply observe temporary distortion or interruption in speech. When transferring documents, images, or other important data, it is definitely better to use the Transmission control protocol.

In this article, we will explore the capabilities of the TIdTCPClient and TIdTCPServer components for data transmission between our Embarcadero Delphi FMX applications over the network.

To do this, we will create two Embarcadero Delphi FMX applications (Client and Server apps).

In the Server app, for data reception, we will use the TIdTCPServer component (under the Indy Servers tab). In the settings of the IdTCPServer1 component, the DefaultPort property will get a value of 5000. Also, we will set the value of the Active property to True.

Since we will encode the image into a BASE64 string in the Client application for transmission, we will need a component for decoding this image to further display it to the user. For this purpose, we will use the TIdDecoderMIME component.

To display the text message from the Client application, we will use the TMemo component.

To display the image received from the Client application, we will use the TImage component.

In the Client application, we will use the TIdTCPClient component (the Indy Clients tab) for data transmission.

To encode the image into a BASE64 string for transmission to the Server application, we will use the TIdEncoderMIME component (the Indy Misc tab).

To input the IP address of the host where the Server application will run, the port number (in our case, it will be 5000), and the name of the transmitted image file, we will use the TEdit components.

To input and subsequently transmit a text message, we will use the TMemo component.

The transmission of a message with an image will be implemented in the “Send” button handler. Both the Client and Server applications can run on different platforms (Windows, Android, etc.).

When running the Client application on Android, it is necessary to set the “Uses Permissions” for accessing the internet and for reading and writing to external storage (Write External Storage). It is necessary because we will load the image file from the Documents folder of the Android device for further transmission to the Server application. When running the Server application on Android, only permission for accessing the internet is required.

The “Write External Storage” permission falls into the Dangerous category. This means that the user must accept or reject it at runtime. The confirmation dialog for permissions is implemented in the onShow method of the main form.

In case of using Embarcadero Delphi 12, the program code has the following form

For the proper functioning of the permission request dialog in runtime mode, you will need to include the System.Permissions library and several other platform-specific libraries for Android.

We will also declare the FPermissionWriteExternalStorage field of the string type to pass the Write External Storage permission to the RequestPermissions method.

To store the path to our image file, we will declare a string field FImageFilePath.

Now let’s take a closer look at the “Send” button handler. We will select the path to the image file depending on the platform. In the case of Windows and Android, this will be the Documents folder. We will read the file name with its extension from the TEdit input field.

To work correctly with files, you will need to connect the System.IOUtils library.

Next, we will use the FileExists function to check if the image file exists at the specified path. If it doesn’t exist, we will need to exit the handler.

Now we should pass the value of the host (IP address of the device where our Server application is running) and the port (in our case, we set the value to 5000 for the Server) to the IdTCPClient1 object.

Next, we will establish a TCP connection (IdTCPClient1.Connect). Additionally, we will need to check whether a connection has already been established. If it has, we will disconnect it (IdTCPClient1.Disconnect).

We will need to load our image into a MemoryStream object (the TMemoryStream class) as a stream of bytes (with the LoadFromFile method). Then, we will encode the image in the TMemoryStream format into a BASE64 string (the IdEncoderMIME1.Encide method). The BASE64 encoded image will be stored in a StringStream object of the TStringStream class.

Next, we will construct a JSON object that contains the text message and the image encoded in BASE64. We will use an object JObj of the TJSONObject class for this purpose. We also should pass parameters such as image_encoded and message.

Data transmission to the Server application should be performed using the IdTCPClient1.Socket.WriteLn(JObj.ToJSON) method. To prevent blocking the main application thread during network data transmission, we will use TTask.Run. After successfully transmitting the data, we will disconnect the TCP connection and release resources.

The full source code of the Main module is provided below:

unit Main;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Memo.Types,
  FMX.StdCtrls, FMX.Controls.Presentation, FMX.ScrollBox, FMX.Memo, System.Permissions,
{$IFDEF ANDROID}
  AndroidApi.Helpers, FMX.Platform.Android,
  Androidapi.JNI.Widget, FMX.Helpers.Android, Androidapi.JNI.Os,
{$ENDIF}
  IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient, FMX.Edit, IdCoder,
  IdCoder3to4, IdCoderMIME, JSON, System.Threading, IdStack, System.IOUtils;

type
  TForm1 = class(TForm)
    IdTCPClient1: TIdTCPClient;
    SendButton: TButton;
    Label2: TLabel;
    EditHost: TEdit;
    IdEncoderMIME1: TIdEncoderMIME;
    Label1: TLabel;
    EditImage: TEdit;
    Label3: TLabel;
    MessageMemo: TMemo;
    EditPort: TEdit;
    Label4: TLabel;
    ToolBar1: TToolBar;
    Label5: TLabel;
    procedure SendButtonClick(Sender: TObject);
    procedure FormShow(Sender: TObject);
  private
    { Private declarations }
    FImageFilePath: string;
{$IFDEF ANDROID}
    FPermissionWriteExternalStorage: string;
{$ENDIF}
  public
    { Public declarations }
  end;
var
  Form1: TForm1;

implementation

{$R *.fmx}





procedure TForm1.FormShow(Sender: TObject);
begin
{$IFDEF ANDROID}
  FPermissionWriteExternalStorage :=
    JStringToString(TJManifest_permission.JavaClass.WRITE_EXTERNAL_STORAGE);
  PermissionsService.RequestPermissions([FPermissionWriteExternalStorage],
    procedure(const APermissions: TArray<string>; const AGrantResults:
      TArray<TPermissionStatus>)
    begin
    if AGrantResults[0] = TPermissionStatus.Granted then
      ShowMessage('External storage allowed!')
    else
      ShowMessage('External storage not allowed!');
    end)
{$ENDIF}
end;

procedure TForm1.SendButtonClick(Sender: TObject);
var
  MemoryStream: TMemoryStream;
  StringStream: TStringStream;
  JObj: TJSONObject;
begin
{$IFDEF MSWINDOWS}
  FImageFilePath := TPath.Combine(TPath.GetDocumentsPath, EditImage.Text);
{$ENDIF}
{$IFDEF ANDROID}
  FImageFilePath := TPath.Combine(TPath.GetSharedDocumentsPath, EditImage.Text);
{$ENDIF}
  if not FileExists(FImageFilePath) then
  begin
    ShowMessage('No such file at ' + FImageFilePath + '!');
    Exit;
  end;
  IdTCPClient1.Host := EditHost.Text;
  try
    IdTCPClient1.Port := Integer.Parse(EditPort.Text);
  except on EConvertError do
    begin
      ShowMessage('Wrong port value!');
      Exit;
    end;
  end;
  if not IdTCPClient1.Connected then
  begin
    try
      IdTCPClient1.Connect;
    except on EIdSocketError do
      begin
        ShowMessage('Connection error!');
        Exit;
      end;
    end;
  end
  else
  begin
    IdTCPClient1.Disconnect;
    Exit;
  end;
  TTask.Run(
    procedure
    begin
      MemoryStream := nil;
      StringStream := nil;
      JObj := nil;
      try
        MemoryStream := TMemoryStream.Create;
        MemoryStream.LoadFromFile(FImageFilePath);
        StringStream := TStringStream.Create(IdEncoderMIME1.Encode(MemoryStream),
          TEncoding.ASCII);
        JOBJ := TJSONObject.Create;
        JOBJ.AddPair('image_encoded', StringStream.DataString);
        JOBJ.AddPair('message', MessageMemo.Text);
        IdTCPClient1.Socket.WriteLn(JObj.ToJSON);
        TThread.Synchronize(nil,
          procedure
          begin
            ShowMessage('BASE64 Image and message successfully sent to server!');
          end);
      finally
        IdTCPClient1.Disconnect;
        JObj.Free;
        StringStream.Free;
        MemoryStream.Free;
      end;
    end);
end;
end.        

Let’s consider the functionality of our Embarcadero Delphi FMX Server application. Receiving and displaying the data sent by our Client application (text message and image) will be handled in the IdTCPServer1.OnExecute event handler.

We will declare a JSON object (TJSONObject class) to store the received data from the Client application (image encoded in BASE64 and text message) as key-value pairs. Additionally, we will declare a StringStream object (TStringStream class) to store the client data in string format and a MemoryStream object (TMemoryStream class) to store the decoded image from the BASE64 string as a TMemoryStream.

With the help of the ReadStream method, we will read the data from the server application and save it into a string stream (StringStream object). This method will take parameters such as StringStream, the size of the data in bytes, and a boolean value (in our example, True indicates reading data from the network until the TCP connection is not stopped).

After that, we will load the string data from StringStream into the JSON object.

With IdDecoderMIME1 (the DecodeStream method), we will decode the image from the BASE64 string and save it into a TMemoryStream for its further display to the user with the help of the TImage component. We will load the image into Image1 from TMemoryStream using the LoadFromStream method (Image1.Bitmap.LoadFromStream).

Let’s upload our message from the Server app to TMemo.

The full code of the Main module of the Server app is offered below:

unit Main;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, IdContext,
  FMX.Memo.Types, FMX.StdCtrls, FMX.Controls.Presentation, FMX.ScrollBox,
  FMX.Memo, IdBaseComponent, IdComponent, IdCustomTCPServer, IdTCPServer,
  FMX.Objects, IdCoder, IdCoder3to4, IdCoderMIME, JSON, IdGlobal, System.Threading,
  IdThread, IdIPWatch, IdIPAddrMon, IdCustomTransparentProxy, IdSocks;

type
  TForm1 = class(TForm)
    IdTCPServer1: TIdTCPServer;
    Memo1: TMemo;
    IdDecoderMIME1: TIdDecoderMIME;
    Image1: TImage;
    ToolBar1: TToolBar;
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    procedure IdTCPServer1Execute(AContext: TIdContext);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}

procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);
var
  JSON: TJSONObject;
  StringStream: TStringStream;
  MemoryStream: TMemoryStream;
begin
  MemoryStream := nil;
  StringStream := nil;
  JSON := nil;
  try
    StringStream := TStringStream.Create('', TEncoding.ASCII);
    AContext.Connection.IOHandler.LargeStream := True;
    AContext.Connection.IOHandler.ReadStream(StringStream, SizeOf(StringStream), True);
    JSON := TJSONObject.ParseJSONValue(StringStream.DataString) as TJSONObject;
    MemoryStream := TMemoryStream.Create;
    IdDecoderMIME1.DecodeStream(JSON.GetValue('image_encoded').Value, MemoryStream);
    TThread.Synchronize(nil,
      procedure
      begin
        Memo1.lines.Clear;
        Memo1.Lines.Add(JSON.GetValue('message').Value);
        Image1.Bitmap.LoadFromStream(MemoryStream);
      end);
  finally
    MemoryStream.Free;
    StringStream.Free;
    JSON.Free;
  end;
end;

end.        

Let’s demonstrate the operation of our Embarcadero Delphi FMX Client and Server applications. In the first scenario, we will run the Client on Android, and the Server on Windows. We will monitor TCP’s operation during the data transmission over the network using the WireShark network traffic analyzer.

To send a message to the Server application running on Windows, we will need to determine the IP address of the host where it is running. To do this, we can use the “ipconfig /all” command.

The port value for the Server was previously set to 5000. In the interface of the Client application on Android, we will input the IP address value, which is 192.168.0.103 in our case. The port value is 5000. The image name will be Animal_078.jpg (located in the Documents folder of the external memory on our Android device). We also should input a text message. In our example, it will be “Hello Embarcadero Delphi Server! :)”.

With the help of a network traffic monitoring application, we can observe the establishment of the TCP connection, the transmission of our data in segments, and finally, the termination of the connection. To monitor the necessary data, we will apply a filter tcp.port == 5000. This way, we will monitor TCP’s activity and the connection using port 5000.

Establishing of the TCP connection.

Transmission of our encoded image and text message (a separate data segment). Here we can see our text message.

After the data is successfully transmitted, the connection is terminated.

You can also determine the IP address of the sender (Src) and receiver (Dst).

And the most interesting part here is the interface of the Server application with the received image and message from the client.

Now let’s run the Client application on the Windows platform and the Server application on Android. To connect, we need the IP address of the host (in this case, the mobile device). Using WireShark, we have already determined the sender’s IP address (Src: 192.168.0.104). However, let’s also check it in the smartphone settings for confirmation.

Now let’s send an image in the Document folder to the Server app over the network.

Also, let’s send the text message “Hello! :)”

The interface of the Server app on the Android platform


To view or add a comment, sign in

More articles by Serge Pilko

Insights from the community

Others also viewed

Explore topics