Guybrush
Guybrush

Reputation: 1611

Delphi and Indy - How can I send something from IdTCPServer to a specific IdTCPClient

I started playing with Indy 10 (from Delphi XE3) and TCP connections recently. I already could, with your help (special thanks to Remy Lebeau), build a simple server application for manage client connections (see here Delphi - Simple TCP client / server using Indy to check clients status). I am using a listbox to add connected clients. See the code:

procedure TfrmMain.TCPServerConnect(AContext: TIdContext);
var
  Host: String;
begin
  Host := UpperCase(GStack.HostByAddress(AContext.Binding.PeerIP));
  TThread.Queue(nil,
    Procedure
    begin
      ListBox.Itens.Add(Host);
      Log('Connected - ' + Host);
      With TCPServer.Contexts.LockList Do
      Try
        StatusBar.Panels[0].Text := 'Connected Clients: ' + IntToStr(Count);
      Finally
        TCPServer.Contexts.UnlockList;
      end;
    end
  );
end;

Now I am trying to send a "hello" from server to a specific client in the list. My idea was to click to select a client hostname in the listbox, then click a button to send the message. But what I am researching is that things are not so easy as I am thinking...

Please, can some Indy expert point me the correct direction (with Indy 10)?

Thanks!

Upvotes: 2

Views: 5467

Answers (1)

Remy Lebeau
Remy Lebeau

Reputation: 595305

A hello would most commonly be sent from the OnConnect event, where you have direct access to the TIdContext for the client that connected.

That being said, if you want to send data to a specific client from outside of the server events, then you need to keep track of the TIdContext object for the desired client, or look for it in the server's Contexts list. In this particular example, you can store the TIdContext object pointer in the ListBox itself, then retrieve it when needed, eg:

procedure TfrmMain.TCPServerConnect(AContext: TIdContext);
var
  Host: String;
begin
  Host := UpperCase(GStack.HostByAddress(AContext.Binding.PeerIP));
  TThread.Queue(nil,
    procedure
    begin
      ListBox.Items.AddObject(Host, AContext);
      Log('Connected - ' + Host);
      With TCPServer.Contexts.LockList Do
      Try
        StatusBar.Panels[0].Text := 'Connected Clients: ' + IntToStr(Count);
      Finally
        TCPServer.Contexts.UnlockList;
      end;
    end
  );
end;

procedure TfrmMain.TCPServerDisconnect(AContext: TIdContext);
var
  Host: String;
begin
  Host := UpperCase(GStack.HostByAddress(AContext.Binding.PeerIP));
  TThread.Queue(nil,
    procedure
    var
      Index: Integer;
    begin
      Index := ListBox.Items.IndexOfObject(AContext);
      if Index <> -1 then
        ListBox.Items.Delete(Index);
      Log('Disconnected - ' + Host);
      With TCPServer.Contexts.LockList Do
      Try
        StatusBar.Panels[0].Text := 'Connected Clients: ' + IntToStr(Count);
      Finally
        TCPServer.Contexts.UnlockList;
      end;
    end
  );
end;

procedure TfrmMain.sendButtonClick(Sender: TObject);
var
  Index: Integer;
  Ctx: TIdContext;
begin
  Index := ListBox.ItemIndex;
  if Index = -1 then Exit;
  Context := TIdContext(ListBox.Items.Objects[Index]);
  // use Context as needed...
end;

Granted, this is not the safest approach, but it will get you started. Things you need to consider:

  1. A client might disconnect and free its TIdContext object before you have removed it from the ListBox. You should make sure the object is still in the server's Contexts list before using it.

  2. Sending unsolicited data from a button event (or any other non-server event) is not thread-safe. Your communications can become corrupted if you send data to the same client from other threads, in particular from the server's events, without syncing the threads. You should be managing the communications from within the server events only. If you need to send data from outside the events, it is safer to put the data into a per-client thread-safe queue, and then have the OnExecute event send the content of the queue when it is safe to do so. I have posted examples of that many times before in several different forums, so it should not be hard to find them online.

  3. The OnDisconnect event is not a good place to look up client details, like its hostname. You should do that in the OnConnect and/or OnExecute event and then cache the value for later use. You can use the TIdContext.Data property for that purpose, or derive a new class from TIdServerContext and set the server's ContextClass property before activating the server.

Upvotes: 4

Related Questions