^ 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
  • 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.


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 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.

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 to buy a license.


Useful information
  • None


[Top]