Delphi - 使用Indy检查客户端状态的简单TCP客户端/服务器

时间:2014-11-30 00:10:26

标签: delphi tcp indy

我最近开始使用Indy 10(来自Delphi XE3)和TCP连接。目前,我正在尝试创建一个简单的服务器应用程序来检查客户端状态。但是当我尝试在已连接某个客户端的情况下停用TCPServer时,客户端会断开连接,但TCPServer会停止应答。

我在某处读到TCPServer应该没有问题地处理客户端的断开连接。我必须在OnExecute事件上添加一些代码来解决这个问题吗?

以下是代码:

procedure TfrmMain.btnConnectClick(Sender: TObject);
begin
  If (not TCPServer.Active) Then
  Try
    TCPServer.Bindings.Clear;
    With TCPServer.Bindings.Add Do
    Begin
      IP := '192.168.1.11';
      Port := StrToInt(edtPort.Text);
    end;
    TCPServer.Active := True;
  Except
    On E:Exception Do ShowMessage(E.Message);
  End
end;

procedure TfrmMain.btnDisconnectClick(Sender: TObject);
begin
  If (TCPServer.Active) Then
  Try
    TCPServer.Active := False;
  Except
    On E:Exception Do ShowMessage(E.Message);
  End
end;

procedure TfrmMain.TCPServerConnect(AContext: TIdContext);
var
  IdStackWin: TIdStackWindows;
begin
  IdStackWin := TIdStackWindows.Create;
  With IdStackWin Do
  Try
    memLog.Lines.Add('Connected - ' + HostByAddress(AContext.Binding.PeerIP) + ' (' + AContext.Binding.PeerIP + ')');
  Finally
    IdStackWin.Free;
  end;
end;


procedure TfrmMain.TCPServerDisconnect(AContext: TIdContext);
var
  IdStackWin: TIdStackWindows;
begin
  IdStackWin := TIdStackWindows.Create;
  With IdStackWin Do
  Try
    memLog.Lines.Add('Disconnected - ' + HostByAddress(AContext.Binding.PeerIP) + ' (' + AContext.Binding.PeerIP + ')');
  Finally
    IdStackWin.Free;
  end;
end;

procedure TfrmMain.TCPServerExecute(AContext: TIdContext);
begin
   Application.ProcessMessages;
end;

谢谢!

1 个答案:

答案 0 :(得分:9)

你犯了一些错误。

  1. 不要直接实例化TIdStack类。当任何Indy套接字组件存活时,Indy会为您实例化一个。如果需要访问套接字堆栈,请使用全局GStack对象指针,例如:

    GStack.HostByAddress(AContext.Binding.PeerIP)
    

    在极少数情况下,当没有Indy组件处于活动状态时需要访问GStack时,您可以使用TIdStack.IncUsage()TIdStack.DecUsage()方法调用来包装代码以确保GStack可以。

  2. TIdTCPServer是一个多线程组件。侦听套接字和客户端套接字在工作线程中运行。 OnConnectOnDisconnectOnExecuteOnExceptionOnListenException事件在这些工作线程的上下文中触发,而不是在主UI的上下文中触发线。因此,您必须与主线程同步才能安全地访问VCL / FMX UI控件,否则会发生不良事件,包括死锁等。您可以使用Indy的TIdSync(同步)或TIdNotify类(异步)类,或TThread.Synchronize()(同步)或TThread.Queue()(异步)方法的静态版本需要时与主线程同步。或者您选择的任何其他线程间同步机制。必须仅在主线程的上下文中访问VCL / FMX UI控件。

    警告:在从主线程停用TIdTCPServer时,请勿使用同步同步,即保证死锁。要么使用异步同步,要么从另一个线程(不是服务器线程)停用服务器,这样主线程就可以正常处理同步了。

  3. 永远不应在主UI线程之外调用
  4. Application.ProcessMessages()。没有必要从TIdTCPServer事件中调用它。

  5. 请改为尝试:

    procedure TfrmMain.btnConnectClick(Sender: TObject);
    begin
      if not TCPServer.Active then
      begin
        TCPServer.Bindings.Clear;
        with TCPServer.Bindings.Add do
        Begin
          IP := '192.168.1.11';
          Port := StrToInt(edtPort.Text);
        end;
        TCPServer.Active := True;
      end;
    end;
    
    procedure TfrmMain.btnDisconnectClick(Sender: TObject);
    begin
      TCPServer.Active := False;
    end;
    
    procedure TfrmMain.TCPServerConnect(AContext: TIdContext);
    var
      Msg: string;
    begin
      Msg := 'Connected - ' + GStack.HostByAddress(AContext.Binding.PeerIP) + ' (' + AContext.Binding.PeerIP + ')';
      TThread.Queue(nil,
        procedure
        begin
          memLog.Lines.Add(Msg);
        end
      );
    end;
    
    procedure TfrmMain.TCPServerDisconnect(AContext: TIdContext);
    var
      Msg: string;
    begin
      Msg := 'Disconnected - ' + GStack.HostByAddress(AContext.Binding.PeerIP) + ' (' + AContext.Binding.PeerIP + ')';
      TThread.Queue(nil,
        procedure
        begin
          memLog.Lines.Add(Msg);
        end
      );
    end;
    
    procedure TfrmMain.TCPServerExecute(AContext: TIdContext);
    begin
      // your communications logic here
    end;