【问题标题】:Delphi and Indy - How can I send something from IdTCPServer to a specific IdTCPClientDelphi 和 Indy - 如何从 IdTCPServer 向特定的 IdTCPClient 发送内容
【发布时间】:2014-11-30 21:54:38
【问题描述】:

我最近开始使用 Indy 10(来自 Delphi XE3)和 TCP 连接。在您的帮助下(特别感谢 Remy Lebeau),我已经可以构建一个简单的服务器应用程序来管理客户端连接(参见此处Delphi - Simple TCP client / server using Indy to check clients status)。我正在使用列表框来添加连接的客户端。见代码:

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;

现在我正在尝试从服务器向列表中的特定客户端发送“hello”。我的想法是单击以在列表框中选择一个客户端主机名,然后单击一个按钮以发送消息。但我正在研究的是,事情并没有我想的那么容易......

请问,Indy 专家能否指出正确的方向(使用 Indy 10)?

谢谢!

【问题讨论】:

    标签: delphi tcp indy


    【解决方案1】:

    问候语最常从OnConnect 事件发送,您可以直接访问连接的客户端的TIdContext

    话虽如此,如果您想从服务器事件之外向特定客户端发送数据,那么您需要跟踪所需客户端的TIdContext 对象,或者在服务器的@987654324 中查找它@ 列表。在这个特定的示例中,您可以将 TIdContext 对象指针存储在 ListBox 本身中,然后在需要时检索它,例如:

    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;
    

    当然,这不是最安全的方法,但它可以帮助您入门。您需要考虑的事项:

    1. 在您将其从 ListBox 中删除之前,客户端可能会断开连接并释放其 TIdContext 对象。在使用它之前,您应该确保该对象仍在服务器的Contexts 列表中。

    2. 从按钮事件(或任何其他非服务器事件)发送未经请求的数据不是线程安全的。如果您从其他线程(尤其是服务器事件)向同一个客户端发送数据,而没有同步线程,您的通信可能会损坏。您应该只管理来自服务器事件的通信。如果您需要从事件外部发送数据,将数据放入每个客户端的线程安全队列中会更安全,然后在安全的情况下让OnExecute 事件发送队列的内容。我之前在几个不同的论坛上发布过很多次这样的例子,所以在网上找到它们应该不难。

    3. OnDisconnect 事件不是查找客户端详细信息(例如其主机名)的好地方。您应该在OnConnect 和/或OnExecute 事件中执行此操作,然后缓存该值以供以后使用。您可以为此目的使用TIdContext.Data 属性,或者从TIdServerContext 派生一个新类并在激活服务器之前设置服务器的ContextClass 属性。

    【讨论】:

    • 再次感谢您的帮助和提示!现在快凌晨 1 点了,我明天会试试你的建议,让你知道......
    • 我发现了另一个潜在问题。当您的 TCPServer 向 TCPClient 发送消息并且它不期望它时会发生什么?有一段时间我一直在摆弄 Indy,但如果我没有记错的话,TCPClient 并不总是像 TCPServer 那样监听入站流量。所以它不能从 TCPServer 接收消息,除非你之前告诉它这样做。我记得在我的一个项目中遇到过这个问题,所以我最终在我的软件的客户端和服务器端都结合了 TCPClient 和 TCPServer。
    • @SilverWarior 如果客户端使用接收线程,使用相同的连接持续侦听套接字上的传入数据,则不需要此解决方法。这就是 Indy Telnet 客户端 (TIdTelnet) 的工作原理。
    • 是的,如果允许服务器向客户端发送未经请求的数据,那么客户端需要始终监听入站数据,无论是通过线程还是通过计时器。您不需要第二个连接,只要通信协议支持,单个连接就可以正常工作。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-24
    • 2020-03-05
    相关资源
    最近更新 更多