^ What's the point?
^ Usage
^ Using the events
^ Searching for a specific client
^ Resuming binary transfers
^ Using the server component on Android
^ Notes for TMessageCommand
^ TCP Server-Client Library in shareware and commercial software?
^ Useful information

Download :: Topics on this subject on the Forum :: Top
Delphi TCP Server-Client Library

TCP Server-Client Library is a component for use in Win32 (XP/Vista/7/8), Win64, OSX, iOS and Android software.
Easily create server-client applications with TCP communication, control and/or send information from one application to another easily over internet or local area network.

Features:
  • Authentication with user list support
  • Multiple parallel binary data (file) transfers (server to client - client to server)
  • Synchronous or asynchronous binary transfer modes
  • Option to resume binary transfers
  • Built-in chat function
  • Send/process custom commands
  • Clients can receive the connected clients list from server
  • Secure SSL TLS connection mode
  • Full unicode support
  • Delphi XE2 64bit and OSX compatible
  • Delphi XE5 iOS and Android compatible
  • Cross platform FireMonkey example included


Usage

Both the client and the server component has an incoming (IncomingMessageQueue) and an outgoing (ActionMessageQueue) message list. These lists are thread safe. Basically you add an action to the action queue, and receive a response in the result message queue.
Action classes:
  • TMessageChat - chat strings
  • TMessageCommand - command class for simple one line string commands
  • TMessageBinary - binary transfer class, can be TMessageBinaryMemory (data is in memory) or TMessageBinaryFile (data is read from file)
  • TMessageTransferRequest - you get this class in the result message queue if the other side wants to send a binary, fill in the wanted settings and send it back with adding it back to the action message queue
  • TMessageTransferCancel - send this action to cancel a binary transfer mid transfer
  • TMessageClientKick - disconnect a client
Binary transfers supports 2 modes:
- Asynchronous (default): multiple transfers which are asynchronous are transfered in paralell - different transfers may finish in any order (usually smallest first). All transfers are paralell (round robin type), the smaller sized transfer finishes before the bigger one.
- Synchronous: multiple transfers which are synchronous are transfered one after the other in the order they are added to the ActionMessageQueue - usefull for sending small data packets, which are the same data stream, like streaming audio, or to be sure the transfers follow each other explicitly. Binary transfer's 'RequireAgreement' is 'raTrue' by default. For memory transfers, to send as fast as possible, set to 'raFalse'. Transfer will not handshaked for the transfer to start, it starts instantly. In case of TMessageBinaryFile it should be 'raTrue', if needed to specify a file name where to save the file.
To start/stop the transfer use the TMessageCommand class to communicate the wanted behaviour between the parties. 'RequireAgreement' is 'raFalse' transfers are not saved to file automatically, as there is no notification of the start of the transfer, to save them to a file write (append) the data packets received to a TFileStream. Identify the "stream" with an ID if needed.
Synchronous mode is basically an UDP style mode but with the benefits of the TCP method, like all the packets arrive for sure and in sent order but still the transfer is as fast as possible.


Note for user and login names

Do not specify and do not allow for the user to specify a login and/or user name with control characters like newline (#13#10). Also not permited names are that contain: ',LOGINNAME=', ',USERNAME=' and ',IPADDRESS='.


Note for rejected login

If LoginHashingMode = hmOpenSSL for the password hashing function to work correctly on Windows, include 'libeay32.dll' and 'ssleay32.dll' (from Delphi install folder eg. 'C:\Program Files\Embarcadero\RAD Studio\12.0\bin\subversion') in your installation package when you distribute your software and make sure to use the version according to the Indy version used or else you will get a rejected login always.

To avoid using external libraries specify 'LoginHashingMode' to 'hmMD5' (default).
If you want to use the OpenSSL libraries under iOS, edit the 'TCPSCLCompilerDefines.inc' file and define 'TCPSCL_OPENSSSL_IOS' compiler directive - 'libcrypto.a' and 'libssl.a' will be compiled into the executable and it's possible to use 'LoginHashingMode' as 'hmOpenSSL'.

Note that using hmOpenSSL is more secure.


Note about logging

Set flag 'LoggingOn' to 'False' for both the server and client class when no logging is needed, for example for release build of your application. Turning off loggin will gain a little speed.


Note for Android

Do not forget to add 'Internet' permission for the app.


Note for secure TLS connection mode

Running a TLS secured server requires an SSL certificate. For example: https://secure.ksoftware.net/ssl_certs.html


Using the events

Server:
  • procedure OnClientConnected(Sender: TObject; AContext: TIdContext; Client: TConnectedClient; Encoding: TEncoding);

  • This procedure is running in the connection's thread context (no VCL access allowed!), 'AContext' is the client's context, use it for sending commands, etc. usefull for initializing a client.
    'Client' is the library's client management class's client object.
    You have to add 'IdContext' to the uses list for this function to compile.

  • procedure OnClientDisconnected(Sender: TObject; AContext: TIdContext; Client: TConnectedClient);

  • This procedure is running in the connection's thread context also, 'AContext' is the client's context usefull for cleaning up after a client.
    You have to add 'IdContext' to the uses list for this function to compile.

  • procedure OnCustomSend(Sender: TObject; AContext: TIdContext; Client: TConnectedClient; Encoding: TEncoding);

  • This procedure is running in the connection's thread context (no VCL access allowed!), 'AContext' is the client's context, use it for sending commands, etc. that you can process in the OnCommandReceive() event.
    'Client' is the library's client management class's client object.
    You have to add 'IdContext' and 'TCPLibraryClasses' (and 'IdGlobal' when using Delphi XE4 or above) to the uses list for this function to compile.

  • procedure OnLoginQuery(Sender: TObject; const LoginName: String; var PassWord: String; var Deny: Boolean);

  • This procedure is running in the connection's thread context (no VCL access allowed!), happens when a client connects to the server. 'LoginName' is the client's login name attempt. Look-up the according pass word and if exists set 'PassWord' to the value. Set 'Deny' to 'True' if you want to deny the connection.
    If no logins are added to the login list, you must implement this event.
    This event can be used to look-up logins from an SQL database for example.

  • procedure OnReceive(Sender: TObject; AContext: TIdContext; Client: TConnectedClient; Command: string; Encoding: TEncoding; var Handled: Boolean);

  • This procedure is running in the connection's thread context also, 'AContext' is the client's context, 'Command' is the string which is received from the client. Act accordingly and if the 'Command' was a proper one and processed set 'Handled' to True, this will result that the library will not process it. If you want the library to process it leave (set) 'Handled' to False.
    Do not use this event to receive commands! Use 'OnReceiveMessage'. This event is mainly usable for debugging purposes to process all incomming communication.
    You have to add 'IdContext' and 'TCPLibraryClasses' (and 'IdGlobal' when using Delphi XE4 or above or Indy 10.6 and above) to the uses list for this function to compile.

  • procedure OnReceiveMessage(Sender: TObject; TCPServerMessage: TTCPServerMessage);

  • This procedure is running the thread where the TCPServerLibraryServer1 . CheckIncommingMessages; was called, so if you call CheckIncomingMessages; in the main thread this procedure is running in the main thread. Examine the 'TCPServerMessage' object's class type and act accordingly (as in the tutorial).
    Do not free the 'TMessageTransferRequest' type object, if you want to accept the transfer, but send it back to the action message queue after setting it's parameters, all other objects need to be freed in your code or else the queue will fill up and memory usage will increase.
    You have to add 'TCPLibraryClasses' to the uses list for this function to compile.

  • procedure OnServerAfterActivate(Sender: TObject);

  • This event is running in the server thread (no VCL access allowed!). Happens after the server is successfully activated.

  • procedure OnServerAfterDeActivate(Sender: TObject);

  • This event is running in the server thread (no VCL access allowed!). Happens after the server is deactivated.

  • procedure OnServerBeforeActivate(Sender: TObject);

  • This event is running in the server thread (no VCL access allowed!). Happens before the server is activated.

  • procedure OnServerBeforeDeActivate(Sender: TObject);

  • This event is running in the server thread (no VCL access allowed!). Happens before the server is deactivated.

  • procedure OnServerThreadEnd(Sender: TObject);

  • This event is running in the server thread (no VCL access allowed!). Happens before the server thread terminates.

  • procedure OnServerThreadStarts(Sender: TObject);

  • This event is running in the server thread (no VCL access allowed!). Happens when the server thread starts to run.

Client:
  • procedure OnClientConnected(Sender: TObject; Client: TClient);

  • If connected clients list is enabled (see below) this event is called when a new client connects to the server.
    You have to add 'TCPSCLClientList' to the uses list for this function to compile.

  • procedure OnClientDisconnected(Sender: TObject; Client: TClient);

  • If connected clients list is enabled (see below) this event is called when an existing client disconnects from the server.
    You have to add 'TCPSCLClientList' to the uses list for this function to compile.

  • procedure OnConnected(Sender: TObject; IdTCPClient: TIdTCPClient; Encoding: TEncoding);

  • This procedure is running in the connection's thread context also, 'IdTCPClient' is the Indy TCP client class, use it for sending commands, etc. usefull for initializing a connection.
    You have to add 'IdTCPClient' (and 'IdGlobal' when using Delphi XE4 or above or Indy 10.6 and above) to the uses list for this function to compile.

  • procedure OnCustomSend(Sender: TObject; IdTCPClient: TIdTCPClient; Encoding: TEncoding; MessageObject: TObject);

  • This procedure is running in the connection's thread context also, 'IdTCPClient' is the Indy TCP client class, use it for sending commands, which is described by MessageObject.
    To use this event process the custom object that you added to the outgoing massage queue like:
    TCPServerLibraryClient1 . ActionMessageQueue.Add(MyClass);
    You get this MyClass as MessageObject. You have te free MessageObject before exiting this event.
    You have to add 'IdTCPClient' (and 'IdGlobal' when using Delphi XE4 or above) to the uses list for this function to compile.

  • procedure OnDisconnected(Sender: TObject; IdTCPClient: TIdTCPClient);

  • This procedure is running in the connection's thread context also, usefull for cleaning up after the connection has ended.
    You have to add 'IdTCPClient' (and 'IdGlobal' when using Delphi XE4 or above) to the uses list for this function to compile.

  • procedure OnReceive(Sender: TObject; IdTCPClient: TIdTCPClient; Command: string; Encoding: TEncoding; var Handled: Boolean);

  • This procedure is running in the connection's thread context (no VCL access allowed!), 'IdTCPClient' is the Indy TCP client class, 'Command' is the string which is received from the server. Act accordingly and if the 'Command' was a proper one and processed set 'Handled' to True, this will result that the library will not process it. If you want the library to process it leave (set) 'Handled' to False.
    Do not use this event to receive commands! Use 'OnReceiveMessage'. This event is mainly usable for debugging purposes to process all incomming communication.
    You have to add 'IdTCPClient' (and 'IdGlobal' when using Delphi XE4 or above) to the uses list for this function to compile.

  • procedure OnReceiveMessage(Sender: TObject; TCPClientMessage: TTCPServerMessage);

  • The same applies here as for the server.
    You have to add 'TCPLibraryClasses' to the uses list for this function to compile.

'Encoding' is now always an UTF-8 encoding class object, used for communication (all commands/communication are UTF-8 internally).

Hint: You can use the result message queue to send a class object to the main thread from within the above procedures. Just declare a class create an instance and use ".IncomingMessageQueue.Add(MyMessage)" to add it to the queue and process it after in the main thread in 'OnReceiveMessage' together with the library's own message objects.


Searching for a specific client

var
    i, k: Integer;
    ContextList: TList;
    Context: TIdContext;
    ConnectedClient: TConnectedClient;
    UserNameIs: String;
begin
    with TCPServerLibraryServer.TCPServerThread do begin
    	ContextList := IdTCPServer.Contexts.LockList;
    	try
            for i := 0 to ContextList.Count - 1 do begin
            	Context := TObject(ContextList[i]) as TIdContext;
            	//* Do what you want with the "Context" here
            	ConnectedClients.Lock;
                try
                    ConnectedClient := ConnectedClients.Find(Context);
                    //* Get user name
                    UserNameIs := ConnectedClient.UserName;
                    //* Iterate over the send list and change all file names
                    ConnectedClient.SendList.Lock;
                    try
                        for k := 0 to ConnectedClient.SendList.Count - 1 do begin
                            if ConnectedClient.SendList.List[k] is TMessageBinary then begin
                                TMessageBinary(ConnectedClient.SendList.List[k]).FileName := 'New file name';
                    	    end;
                        end;
                    finally
                        ConnectedClient.SendList.UnLock;
                    end;
                finally
                    ConnectedClients.UnLock;
                end;
            end;
        finally
            IdTCPServer.Contexts.UnlockList;
        end;
    end;
end;

Resuming binary transfers

To resume a binary transfer send a command describing the request. It should include the identification of the binary (eg. file name) and a position to resume from. For example:
var
    MessageCommand: TMessageCommand;
begin
    MessageCommand := TMessageCommand.Create;
    MessageCommand.Command := 'RESUME';
    MessageCommand.Arguments := IntToStr(NGetFileSize('C:\Pic.jpg')) + ' "C:\Pic.jpg"';
    TCPServerLibraryClient1.ActionMessageQueue.Add(MessageCommand);
When this command is received on the other side, start a binary transfer like this:
procedure TForm1.ArrivedCommand(MessageCommand: TMessageCommand);
var
    Arguments: TStringList;
    ResumeFromPosition: Int64;
    FileName: String;
begin
    if MessageCommand.Command = 'RESUME' then begin
    	Arguments := TStringList.Create;
    	try
    	    Arguments.Delimiter := ' ';
    	    Arguments.DelimitedText := MessageCommand.Arguments;
    	    ResumeFromPosition := Arguments[0];
    	    FileName := Copy(MessageCommand.Arguments,
    	    	Pos('"', MessageCommand.Arguments) + 1,
    	    	PosEx('"', MessageCommand.Arguments,
    	    	Pos('"', MessageCommand.Arguments) + 1) - Pos('"', MessageCommand.Arguments) - 1);
    	    SendResumedBinary(FileName, ResumeFromPosition);
    	finally
    	    FreeAndNil(Arguments);
    	end;
    end;
end;

procedure TForm1.SendResumedBinary(FileName: String; FromPosition: Int64);
var
    MessageBinary: TMessageBinary;
begin
    MessageBinary := TMessageBinaryFile.Create(FileName, True);
    MessageBinary.DataType := TCP_SOCKET_DATA_TYPE_FILE;
    MessageBinary.FileName := FileName;
    MessageBinary.Resume := True;
    MessageBinary.StartPosition := FromPosition;
    TCPServerLibraryServer1.ActionMessageQueue.Add(MessageBinary);
end;
and receive the resumed binary:
procedure TForm1.ArrivedTransferRequest(MessageTransferRequest: TMessageTransferRequest);
begin
    if MessageTransferRequest.DataType = TCP_SOCKET_DATA_TYPE_FILE then begin
        if MessageTransferRequest.FileName = 'C:\Pic.jpg' then begin
            TransferID := MessageTransferRequest.ID;
            MessageTransferRequest.Accept := True;
            MessageTransferRequest.SaveToFile := True;
            MessageTransferRequest.SaveToFileName := 'C:\Pic.jpg';
            MessageTransferRequest.Resume := True;
            MessageTransferRequest.StartPosition := NGetFileSize('C:\Pic.jpg');
            TCPServerLibraryClient1.ActionMessageQueue.Add(MessageTransferRequest);
        end;
    end;
end;
Note that you probably need to implement an identification scheme for the file/binary, it's not a good idea to use local file names.
NGetFileSize() is defined in Helper3delite unit available at: the download page.


Using the server component on Android

There is a bug in Indy included with Delphi XE5 and XE6 (it's fixed in XE7). Install the latest Indy to use the server component on iOS/Android!
Also at least one binding needs to be specifed before activating the server:
    TCPServerLibraryServer.Bindings.Clear;
    with TCPServerLibraryServer.Bindings.Add do begin
        IP := '192.168.0.101';
        Port := 2014;
    end;
Replace IP address to use the Android device's address. You can find the address in Settings/Wifi.
Also update 'libeay32.dll', 'ssleay32.dll' and 'msvcr100.dll' according to the Indy version used (on Windows).

Up to XE7 Indy doesn't parse the local IP address, if you don't want to enter it manually (as above), there is a solution.
Download 'D.P.F Delphi Android Native Components'.
Extract the files to a folder. In your project add to the search path the folder where you extracted the files, and add to Uses list:
Uses
    Androidapi.Helpers,
    DPF.Android.DPFUtils;
Before issueing the TCPServerLibraryClient1.Connect command assign the IP:
	TCPServerLibraryServer.Bindings.Clear;
	with TCPServerLibraryServer.Bindings.Add do begin
		IP := JStringToString(TJDPFUtils.JavaClass.getIPAddress(True));
		Port := 2014;
	end;
To use this Java class the default 'classes.dex' file needs to be replaced. Go to deployment, uncheck the classes.dex file, and add the one from the folder 'DPF.Android.Native.Components.v2.8.6 \ Classes \ classes.dex', change the path (2nd button from the right) to 'classes\'. This 'classes.dex' file is for Delphi XE7, if you are using other version of Delphi consult the 'D.P.F Delphi Android Native Components' manual on how to make a proper version of it.


Notes for TMessageCommand
  • 'Command': Do not use numbers (as strings) in the range 100-999 as they are already used and reserved by the library.

Getting the connected clients list on client side

First of all on the server side enable this functionality with 'TCPServerLibraryServer1.SendUserListToClients := True;'.
When a client connects to the server, on the server side enable him/her to receive the client list within the 'OnClientConnected()' event:
procedure TForm1.TCPServerLibraryServer1ClientConnected(Sender: TObject; AContext: TIdContext; Client: TConnectedClient; Encoding: TEncoding);
begin
    //* This is for the connected client list for the clients to receive, turn off if not needed, generates extra trafic. Also requires that TCPServerLibraryServer1.SendUserListToClients is True.
    //* You can decide from eg. login name if the user has right for this.
    Client.ExpectsClientList := True;
end;
On the client side you receive new and disconnected clients within these 2 events:
    procedure OnClientConnected(Sender: TObject; Client: TClient);
    procedure OnClientDisconnected(Sender: TObject; Client: TClient);
To access the full client list use the 'TCPServerLibraryClient1.ClientList.Clients' list if needed.

The server tutorial is configured to send the list, turn it off if this functionality is not needed as it generates extra trafic.

Starting transfers from one client to the other client is not supported.
You can get the IP address from the 'OnClientConnected()' event and you can have another TCP Server-Client Library server-client pair for this task within the clients. The connection will be direct from one client to the other client and the transfer will be fast and won't stress the server. Although it's possible to send binary transfers to the server with a destination GUID, and the server can forward the transfer to the other client specified by the GUID.


TCP Server-Client Library in shareware and commercial software?

The component is free for use in free software. If you like it and use it in a shareware or commercial (or any other money making - advertising, in app. selling, etc.) product you need one of the licenses.


Useful information


[Top]