Typically, in a client / server setup, the client initiates a contact and the server responds. Using events opened by IdTCPServer is always a context (connection), so you do not need to do anything special.
To start contact with the server on the client, you will need to track connected clients and use the connection of the desired client to send him a message. To do this, you need a list where you need to keep connected clients and you need to implement handlers for OnConnect and OnDisconnect events.
type TForm1 = class(TForm) private FClients: TThreadList; procedure TForm1.HandleClientConnect(aThread: TIDContext); begin FClients.Add(aThread); end; procedure TForm1.HandleClientDisconnect(aThread: TIDContext); begin FClients.Remove(aThread); end;
If you want to send data to a specific client, you can do this using the usual methods of sending data over a TCP connection. But first you need to find the specific client you need in the FClients list.
How you identify specific customers is entirely up to you. This will depend entirely on the information that you exchange between the client and the server when the client first connects and identifies itself. Having said that, the mechanism will be the same regardless of this information.
TIDContext is the ancestor of the TIdServerContext class used by Indy to store connection information. You can go down from the TIdServerContext to have a place where you can store your own connection data.
type TMyContext = class(TIdServerContext) private // MyInterestingUserDetails...
Tell Indy to use the native TIdServerContext descendant using its ContextClass property. Of course, you need to do this before activating your server, for example, in OnCreate.
procedure TForm1.HandleTcpServerCreate(Sender: TObject); begin FIdTcpServer1.ContectClass = TMyContext; end;
And then you can use your own class wherever you have the TIdContext parameter, dropping it into your own class:
procedure TForm1.HandleClientConnect(aThread: TIDContext); var MyContext: TMyContext; begin MyContext := aThread as TMyContext; end;
Finding a specific client’s connection then becomes a matter of repeating the list of your FClients and checking if it contains the TMyContext that you want:
function TForm1.FindContextFor(aClientDetails: string): TMyContext; var LockedList: TList; idx: integer; begin Result := nil; LockedList := FClients.LockList; try for idx := 0 to LockedList.Count - 1 do begin MyContext := LockedList.Items(idx) as TMyContext; if SameText(MyContext.ClientDetails, aClientDetails) then begin Result := MyContext; Break; end; end; finally FClients.UnlockList; end;
Change As Remy notes in the comments: to ensure thread safety, you must keep the list blocked when writing to the client (which is not so good for bandwidth and performance), or in Remy the words:
"The best option is to provide TMyContext with your own TIdThreadSafeStringList for outgoing data, and then provide this client with the OnExecute event for this list to the client when it is safe. Other OnExecute client events can then insert data into it if necessary."