3
votes

I am creating a client-server application to run only on Windows OS, and each Windows user will connect to server at a time. The LAN uses DHCP, so IP is not fixed.

I need to send a string message from IdTCPServer to a specific IdTCPClient. In the beginning I was using a Listbox, so I added the hostname to the listbox when connected and remove when disconnect. At that time, Remy Lebeau give me this tip:

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;

But now I am using a ListView, with pre-added hostnames. So I just change the listbox item image status when clients connect or disconnect. Now I am trying something like this:

procedure TfrmMain.TCPServerConnect(AContext: TIdContext);
begin
  TThread.Queue(nil,
    procedure
    var
      Host: String;
      LItem: TListItem;
    begin
      Host := UpperCase(GStack.HostByAddress(Ctxt.Binding.PeerIP));
      LItem := lvwPCList.FindCaption(0, Host, False, True, False);
      if (LItem <> nil) then LItem.Data := AContext.Data;
    end
  );
end;

And once I linked the Listview Item with the Context data, I am trying to send the message direct to client:

procedure TfrmMain.SendMessage(const Item: TListItem; const Msg: String);
var
  Ctx: TIdContext;
begin
  if (Trim(Msg) = '') then Exit;
  Ctx := TIdContext(Item.Data);
  try
    Ctx.Connection.IOHandler.WriteLn(Msg);
  except
  end;
end;

I can compile this code, but the message never reachs client. Please, what I am doing wrong?

Thanks!

1
What if the same client is connected to your server many different times? Or two computers behind the same internet connection (thus using the same IP)? Do you want your message to go to all of them? Or just one of them? And either way, you'd have to loop, exactly as you're doing in your code. There's no such thing built into Indy for a server to send a message to a particular IP or Host (being one of the connected clients). That's what the server context is all about. You may want to look into inheriting your own TIdServerContext. - Jerry Dodge
@Jerry, OP already uses a TClient object (which can uniquely identify a client connection). I think it's time to just give that object some sort of unique session identifier. - TLama
@TLama I do see that, but it's not clear if OP is familiar with how to use it, seeing as the only thing obtained from it is something you can obtain directly from the context itself. Yes, introducing a unique session identifier would be the appropriate thing to do, but would still require a loop such as OP's code, just matching this session ID instead of host name. - Jerry Dodge
Thank you for reply! I forgot saying I am new with Indy and TCP communication. Well, my app will work inside LAN and each client (computer) is allowed to connect once to server. In the server side I have a listview, with pre-added hostnames on it. Each time a client connect, hostname in the listview change it status. My idea is to vinculate some connection Id with each host, so it can be easily identified / acessed. I want to select a hostname in the listview, and click a button to send a simple string. Thanks for any help! - Guybrush
Your actual implementation may not allow one client to connect more than once, but you have to assume that it's still possible. There's no way around that concept. Suppose, one windows device, where one user logs in and connects to your server, then another user logs into the same computer (through Windows) in addition, thus spawning a second connection. It's still very possible, and you should keep that in consideration. - Jerry Dodge

1 Answers

1
votes

You should implement a unique session ID within your TIdContext inheritance. Generate a new unique ID upon each new connection. For example, you could use an incremented integer, or produce a GUID. In any case, you have your own unique "session id" which uniquely identifies a client/server "session". This is assuming you wish to accomplish some sort of session management.

As for the loop, you would still need to perform a loop to find the corresponding client. But such a loop is trivial, and can perform in a fraction of a millisecond, even with thousands of connected clients (which is already overhaul on any server). Even if you were to rely on something built-in, that built-in function would still essentially be performing a loop.

You can't simply refer to a connected client by its host name or IP address, because there may be more than one connection from the same client. There's no such thing as "send a message to a client by host or IP" because of that fact. You need to explicitly refer to the specific connection. Don't worry about if your connections come from the same computer/device, unless you intend to strictly prohibit more than one connection from the same device But even then, if you were to decide to use this over the internet, all the clients behind the same network would be seen as the same IP. The same applies to the idea of other devices than just Windows. In the future, you may wish to support other devices, and you need to make sure your structure is ready for so, if you plan on some day going that route.