Trevor
Trevor

Reputation: 3

How to use Indy component to Send from a IDTCPServer socket with Multiple Bindings

I wish to use multiple bindings on a TIdTCPServer socket component to send data to multiple different clients. I can successfully create the bindings and assign a different port number to each. Here is my code to create the bindings:

IdTCPServer1.Active := False;
IdTCPServer1.Bindings.Clear;
{create the new sockets}
for i := 0 to NumberOfItemsSpin.Value-1 do
begin
  IdTCPServer1.Bindings.Add;
  IdTCPServer1.Bindings.Items[i].Port := StartingPortSpin.Value+i;
end;
SocketsCreatedCount := IdTCPServer1.Bindings.Count;
Timer1.Enabled      := SocketsCreatedCount > 0;
IdTCPServer1.Active := True;

The clients know the IP address and port to connect to, and are able to make a successful connection. However, I need to asynchronously send data to any one of the connected clients based on certain events.

I know I can detect a OnExecute event, get the Context and reply, but my server socket needs to work asynchronously. It will not be polled by the Client.

I know the client IP Address and port number from the connection info. However, I cannot find a way to send data to a specific client. How do I use the component to correctly map the binding and send data to the correct connected client?

Upvotes: 0

Views: 1609

Answers (1)

Remy Lebeau
Remy Lebeau

Reputation: 596176

You seem to have a bit of a misunderstanding of how TIdTCPServer actually works.

The Bindings collection has nothing to do with the individual clients. The Binding items are simply the IP/Port pairs that the server listens on for connections. There is no mapping between a Binding and a client. When a client connects, a TIdContext object is created for it and stored in the server's Contexts list, and then the OnConnect/OnDisconnect/OnExecute events are triggered for that TIdContext, independently of the other clients. TIdTCPserver is multi-threaded, each client is run in its own dedicated thread, and the events for a given client are triggered within that client's thread, so events for multiple clients can be running in parallel.

The OnExecute event is NOT dependent on a client polling the server. The event is simply called in a continuous loop for the lifetime of the client's connection. The event handler decides what to do with the client on each loop iteration. However, you are required to assign a handler, or else an exception will be raised when the server is activated (to avoid that, you can derive a new class from TIdTCPServer and override its CheckOkToBeActive() method to not raise an exception).

To send data to any particular client, simply locate that client's TIdContext object within the server's Contexts list, and then you will have access to its associated TIdTCPConnection object for exchanging data with that client.

However, performing the send from inside the same loop that searches the Contexts list is inheritantly unsafe, so I would suggest instead that you create a per-client thread-safe queue (such as a TIdThreadSafe(String|Object)List instance) to hold your outbound data, and then when you locate the client you can put the data into that client's associated queue. Then you can have the OnExecute event handler send the content of its client's queue whenever it is not empty. This way, you avoid clients blocking each other, error handling is localized to each client, and you can send data to multiple clients in parallel.

To associate a queue with each client, you can either:

  • use the public TIdContext.Data property.

  • derive a new class from TIdServerContext, add your own custom fields to it as needed, and then assign that class to the server's ContextClass property before activating the server. You can then type-cast any TIdContext object pointer to access your custom fields.

For a code example, see this answer I posted to an earlier question:

How do I send a command to a single client instead of all of them?

Upvotes: 3

Related Questions