【问题标题】:Delphi - How Indy TCPServer manage clients connections?Delphi - Indy TCPServer 如何管理客户端连接?
【发布时间】:2024-01-19 11:59:01
【问题描述】:

我正在创建一个仅在 Windows 操作系统上运行的客户端-服务器应用程序,每个 Windows 用户将一次连接到服务器。局域网使用DHCP,所以IP不固定。

我需要将字符串消息从 IdTCPServer 发送到特定的 IdTCPClient。一开始我使用的是列表框,所以我在连接时将主机名添加到列表框,并在断开连接时删除。当时,Remy Lebeau 给了我这个提示:

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;

但现在我使用的是带有预添加主机名的 ListView。所以我只是在客户端连接或断开连接时更改列表框项目图像状态。现在我正在尝试这样的事情:

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;

一旦我将 Listview 项与上下文数据链接起来,我就会尝试将消息直接发送给客户端:

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;

我可以编译这段代码,但消息永远不会到达客户端。请问,我做错了什么?

谢谢!

【问题讨论】:

  • 如果同一个客户端多次连接到您的服务器会怎样?还是同一互联网连接后面的两台计算机(因此使用相同的 IP)?你想让你的信息传达给所有人吗?还是只是其中之一?无论哪种方式,您都必须循环,就像您在代码中所做的那样。 Indy 中没有内置这样的东西供服务器向特定 IP 或主机(作为连接的客户端之一)发送消息。这就是服务器上下文的全部内容。你可能想考虑继承你自己的TIdServerContext
  • @Jerry,OP 已经使用了TClient 对象(可以唯一标识客户端连接)。我认为是时候给该对象某种唯一的会话标识符了。
  • @TLama 我确实看到了,但不清楚 OP 是否熟悉如何使用它,因为从它获得的唯一东西是您可以直接从上下文本身获得的东西。是的,引入一个唯一的会话标识符是合适的做法,但仍然需要一个循环,例如 OP 的代码,只需匹配此会话 ID 而不是主机名。
  • 感谢您的回复!我忘了说我是 Indy 和 TCP 通信的新手。好吧,我的应用程序将在 LAN 内运行,并且每个客户端(计算机)都可以连接到服务器一次。在服务器端,我有一个列表视图,上面有预先添加的主机名。每次客户端连接时,列表视图中的主机名都会更改其状态。我的想法是为每个主机提供一些连接 ID,以便可以轻松识别/访问它。我想在列表视图中选择一个主机名,然后单击一个按钮以发送一个简单的字符串。感谢您的帮助!
  • 您的实际实现可能不允许一个客户端多次连接,但您必须假设它仍然是可能的。没有办法绕过这个概念。假设一个 Windows 设备,其中一个用户登录并连接到您的服务器,然后另一个用户另外登录到同一台计算机(通过 Windows),从而产生第二个连接。这仍然很有可能,您应该考虑到这一点。

标签: delphi tcp indy


【解决方案1】:

您应该在 TIdContext 继承中实现唯一的会话 ID。在每个新连接上生成一个新的唯一 ID。例如,您可以使用递增的整数,或生成 GUID。在任何情况下,您都有自己唯一的“会话 ID”,它唯一地标识客户端/服务器“会话”。这是假设您希望完成某种会话管理。

至于循环,您仍然需要执行循环才能找到相应的客户端。但是这样的循环是微不足道的,并且可以在几分之一毫秒内执行,即使有数千个连接的客户端(这已经在任何服务器上进行了大修)。即使您要依赖内置的东西,该内置函数本质上仍会执行循环。

您不能简单地通过主机名或 IP 地址来引用已连接的客户端,因为可能有来自同一个客户端的多个连接。由于这个事实,没有“通过主机或 IP 向客户端发送消息”这样的事情。您需要明确引用具体的连接。不要担心您的连接是否来自同一台计算机/设备,除非您打算严格禁止来自同一设备的多个连接但是即使那样,如果您决定通过互联网使用它,所有客户端都在后面相同的网络将被视为相同的 IP。这同样适用于 Windows 以外的其他设备的想法。将来,您可能希望支持其他设备,并且您需要确保您的结构已为此做好准备,如果您计划有一天走这条路。

【讨论】:

  • 我正在考虑的一个技巧是使用TIdContext 实例的指针作为“会话ID”......这是我能想到的避免循环的唯一方法。
  • 我认为这个“会话 ID”是我正在寻找的。拜托,你能给我一个简单的代码示例吗?谢谢!
  • 我会接受你的回答,因为你帮助我解决了很多问题。但我认为用正确的文字发布另一个问题会更好。谢谢!