设置 Active = false 时 TIdTCPServer 挂起

TIdTCPServer hangs when setting Active = false

我正在查看此 example 以了解如何使用 TIdTCPServer/client 组件,我发现如果有任何客户端,那么当您将 active 更改为 false 时,服务器组件将挂起。具体来说,它挂在上下文线程的 Windows "ExitThread" 函数调用上。

要重现该行为:

  1. 运行 服务器,
  2. 单击 "Start Server" 按钮,
  3. 运行一个客户,
  4. 点击连接按钮
  5. 单击 "Stop Server" 按钮

我想要一个简单的 TCP 服务器来监视 LAN 上的进程,但我不知道如何防止这种锁定。我发现了很多绕过这个问题的信息,但对我来说还没有任何意义。我在带有 Indy 10.6.2.5366 的 Win 8.1 上使用 Delphi 10.2。

ExitThread() 无法挂起,除非 DLL 在其 DllMain/DllEntryPoint() 处理程序中行为不当,导致 DLL 加载程序出现死锁。但是,服务器的 Active 属性 setter 肯定会挂起,例如如果任何客户端线程死锁。

您链接到的示例不是一个很好的示例。线程事件处理程序正在执行非线程安全的操作。他们在不与主 UI 线程同步的情况下访问 UI 控件,这可能导致许多问题,包括死锁和死 UI 控件。而且服务端的广播方式实现全错,容易死锁,崩溃,数据损坏。

写那个例子的人(不是我)显然不知道他们在做什么。它需要重写以正确考虑线程安全。试试这样的东西:

unit UServer;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, IdContext, IdComponent, Vcl.StdCtrls,
  IdBaseComponent, IdCustomTCPServer, IdTCPServer, Vcl.ExtCtrls;

type
  TFServer = class(TForm)
    Title         : TLabel;

    btn_start     : TButton;
    btn_stop      : TButton;
    btn_clear     : TButton;

    clients_connected : TLabel;

    IdTCPServer   : TIdTCPServer;
    Label1        : TLabel;
    Panel1        : TPanel;
    messagesLog   : TMemo;

    procedure FormShow(Sender: TObject);

    procedure btn_startClick(Sender: TObject);
    procedure btn_stopClick(Sender: TObject);
    procedure btn_clearClick(Sender: TObject);

    procedure IdTCPServerConnect(AContext: TIdContext);
    procedure IdTCPServerDisconnect(AContext: TIdContext);
    procedure IdTCPServerExecute(AContext: TIdContext);
    procedure IdTCPServerStatus(ASender: TObject; const AStatus: TIdStatus;
                                const AStatusText: string);

  private
    { Private declarations }

    procedure broadcastMessage(p_message : string);
    procedure Log(p_who, p_message: string);
    procedure UpdateClientsConnected(ignoreOne: boolean);

  public
    { Public declarations }

  end;
  // ...

var
  FServer     : TFServer;

implementation

uses
  IdGlobal, IdYarn, IdThreadSafe;

{$R *.dfm}

// ... listening port
const
  GUEST_CLIENT_PORT = 20010;

// *****************************************************************************
//   CLASS : TMyContext
//           HELPER CLASS FOR QUEUING OUTBOUND MESSAGES TO A CLIENT
// *****************************************************************************
type
  TMyContext = class(TIdServerContext)
  private
    FQueue: TIdThreadSafeStringList;
    FAnyInQueue: Boolean;
  public
    constructor Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TIdContextThreadList = nil); override;
    destructor Destroy; override;
    procedure AddToQueue(p_message: string);
    procedure CheckQueue;
  end;

constructor TMyContext.Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TIdContextThreadList = nil);
begin
  inherited Create(AConnection, AYarn, AList);
  FQueue := TIdThreadSafeStringList.Create;
  FAnyQueued := false;
end;

destructor TMyContext.Destroy;
begin
  FQueue.Free;
  inherited;
end;

procedure TMyContext.AddToQueue(p_message: string);
begin
  with FQueue.Lock do
  try
    Add(p_message);
    FAnyInQueue := true;
  finally
    FQueue.Unlock;
  end;
end;

procedure TMyContext.CheckQueue;
var
  queue, tmpList  : TStringList;
  i               : integer;
begin
  if not FAnyInQueue then Exit;
  tmpList := TStringList.Create;
  try
    queue := FQueue.Lock;
    try
      tmpList.Assign(queue);
      queue.Clear;
      FAnyInQueue := false;
    finally
      FQueue.Unlock;
    end;
    for i := 0 to tmpList.Count - 1 do begin
      Connection.IOHandler.WriteLn(tmpList[i]);
    end;
  finally
    tmpList.Free;
  end;
end;
// .............................................................................


// *****************************************************************************
//   EVENT : onShow()
//           ON FORM SHOW
// *****************************************************************************
procedure TFServer.FormShow(Sender: TObject);
begin
  // ... INITIALIZE:

  // ... clear message log
  messagesLog.Lines.Clear;

  // ... zero to clients connected
  clients_connected.Caption := IntToStr(0);

  // ... set buttons
  btn_start.Visible := true;
  btn_start.Enabled := true;
  btn_stop.Visible  := false;

  // ... set context class
  IdTCPServer.ContextClass := TMyContext;
end;
// .............................................................................


// *****************************************************************************
//   EVENT : btn_startClick()
//           CLICK ON START BUTTON
// *****************************************************************************
procedure TFServer.btn_startClick(Sender: TObject);
begin
  btn_start.Enabled := false;

  // ... START SERVER:

  // ... clear the Bindings property ( ... Socket Handles )
  IdTCPServer.Bindings.Clear;
  // ... Bindings is a property of class: TIdSocketHandles;

  // ... add listening ports:

  // ... add a port for connections from guest clients.
  IdTCPServer.Bindings.Add.Port := GUEST_CLIENT_PORT;
  // ... etc..

  // ... ok, Active the Server!
  IdTCPServer.Active  := true;

  // ... hide start button
  btn_start.Visible   := false;

  // ... show stop button
  btn_stop.Visible    := true;
  btn_stop.Enabled    := true;

  // ... message log
  Log('SERVER', 'STARTED!');

end;
// .............................................................................


// *****************************************************************************
//   EVENT : btn_stopClick()
//           CLICK ON STOP BUTTON
// *****************************************************************************
procedure TFServer.btn_stopClick(Sender: TObject);
begin
  btn_stop.Enabled := false;

  // ... before stopping the server ... send 'good bye' to all clients connected
  broadcastMessage( 'Goodbye my Clients :)');

  // ... stop server!
  IdTCPServer.Active := false;

  // ... hide stop button
  btn_stop.Visible   := false;

  // ... show start button
  btn_start.Visible  := true;
  btn_start.Enabled  := true;

  // ... message log
  Log('SERVER', 'STOPPED!');

end;
// .............................................................................


// *****************************************************************************
//   EVENT : btn_clearClick()
//           CLICK ON CLEAR BUTTON
// *****************************************************************************
procedure TFServer.btn_clearClick(Sender: TObject);
begin
  //... clear messages log
  MessagesLog.Lines.Clear;
end;
// .............................................................................


// *****************************************************************************
//   EVENT : onConnect()
//           OCCURS ANY TIME A CLIENT IS CONNECTED
// *****************************************************************************
procedure TFServer.IdTCPServerConnect(AContext: TIdContext);
var
  PeerIP      : string;
  PeerPort    : TIdPort;
begin

  // ... OnConnect is a TIdServerThreadEvent property that represents the event
  //     handler signalled when a new client connection is connected to the server.

  // ... Use OnConnect to perform actions for the client after it is connected
  //     and prior to execution in the OnExecute event handler.

  // ... see indy doc:
  //     http://www.indyproject.org/sockets/docs/index.en.aspx

  // ... getting IP address and Port of Client that connected
  PeerIP    := AContext.Binding.PeerIP;
  PeerPort  := AContext.Binding.PeerPort;

  // ... message log ...........................................................
  Log('SERVER', 'Client Connected! Peer=' + PeerIP + ':' + IntToStr(PeerPort));
  // ...

  // ... update number of clients connected
  UpdateClientsConnected(false);
  // ...

  // ... send the Welcome message to Client connected
  AContext.Connection.IOHandler.WriteLn('Welcome GUEST Client :)');

end;
// .............................................................................


// *****************************************************************************
//   EVENT : onDisconnect()
//           OCCURS ANY TIME A CLIENT IS DISCONNECTED
// *****************************************************************************
procedure TFServer.IdTCPServerDisconnect(AContext: TIdContext);
var
  PeerIP      : string;
  PeerPort    : TIdPort;
begin

  // ... getting IP address and Port of Client that connected
  PeerIP    := AContext.Binding.PeerIP;
  PeerPort  := AContext.Binding.PeerPort;

  // ... message log ...........................................................
  Log('SERVER', 'Client Disconnected! Peer=' + PeerIP + ':' + IntToStr(PeerPort));
  // ...

  // ... update number of clients connected
  UpdateClientsConnected(true);
  // ...

end;
// .............................................................................


// *****************************************************************************
//   EVENT : onExecute()
//           ON EXECUTE THREAD CLIENT
// *****************************************************************************
procedure TFServer.IdTCPServerExecute(AContext: TIdContext);
var
  PeerIP        : string;
  PeerPort      : TIdPort;
  msgFromClient : string;
begin

  // ... OnExecute is a TIdServerThreadEvents event handler used to execute
  //     the task for a client connection to the server.

  // ... check for pending broadcast messages to the client
  TMyContext(AContext).CheckQueue;
  // ...

  // check for inbound messages from client
  if AContext.Connection.IOHandler.InputBufferIsEmpty then
  begin
    AContext.Connection.IOHandler.CheckForDataOnSource(100);
    AContext.Connection.IOHandler.CheckForDisconnect;
    if AContext.Connection.IOHandler.InputBufferIsEmpty then Exit;
  end;

  // ... received a message from the client

  // ... get message from client
  msgFromClient := AContext.Connection.IOHandler.ReadLn;

  // ... getting IP address, Port and PeerPort from Client that connected
  PeerIP    := AContext.Binding.PeerIP;
  PeerPort  := AContext.Binding.PeerPort;

  // ... message log ...........................................................
  Log('CLIENT', '(Peer=' + PeerIP + ':' + IntToStr(PeerPort) + ') ' + msgFromClient);
  // ...

  // ... process message (request) from Client

  // ...

  // ... send response to Client

  AContext.Connection.IOHandler.WriteLn('... response from server :)');

end;
// .............................................................................


// *****************************************************************************
//   EVENT : onStatus()
//           ON STATUS CONNECTION
// *****************************************************************************
procedure TFServer.IdTCPServerStatus(ASender: TObject; const AStatus: TIdStatus;
                                     const AStatusText: string);
begin

  // ... OnStatus is a TIdStatusEvent property that represents the event handler
  //     triggered when the current connection state is changed...

  // ... message log
  Log('SERVER', AStatusText);
end;
// .............................................................................


// *****************************************************************************
//   PROCEDURE : broadcastMessage()
//               BROADCAST A MESSAGE TO ALL CLIENTS CONNECTED
// *****************************************************************************
procedure TFServer.broadcastMessage( p_message : string );
var
  tmpList      : TIdContextList;
  contexClient : TIdContext;
  i            : integer;
begin

  // ... send a message to all clients connected

  // ... get context Locklist
  tmpList := IdTCPServer.Contexts.LockList;
  try
    for i := 0 to tmpList.Count-1 do begin
      // ... get context ( thread of i-client )
      contexClient := tmpList[i];

      // ... queue message to client
      TMyContext(contexClient).AddToQueue(p_message);
    end;
  finally
    // ... unlock list of clients!
    IdTCPServer.Contexts.UnlockList;
  end;

end;
// .............................................................................


// *****************************************************************************
//   PROCEDURE : Log()
//               LOG A MESSAGE TO THE UI
// *****************************************************************************
procedure TFServer.Log(p_who, p_message : string);
begin
  TThread.Queue(nil,
    procedure
    begin
      MessagesLog.Lines.Add('[' + p_who + '] - ' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + ': ' + p_message);
    end
  );
end;
// .............................................................................


// *****************************************************************************
//   PROCEDURE : UpdateClientsConnected()
//               DISPLAY THE NUMBER OF CLIENTS CONNECTED
// *****************************************************************************
procedure TFServer.UpdateClientsConnected(ignoreOne: Boolean);
var
  NumClients: integer;
begin
  with IdTCPServer.Contexts.LockList do
  try
    NumClients := Count;
  finally
    IdTCPServer.Contexts.UnlockList;
  end;

  if ignoreOne then Dec(NumClients);

  TThread.Queue(nil,
    procedure
    begin
      clients_connected.Caption := IntToStr(NumClients);
    end
  );
end;
// .............................................................................

end.

unit UClient;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, IdBaseComponent,
  IdComponent, IdTCPConnection, IdTCPClient, IdThreadComponent;

type
  TFClient = class(TForm)

    Label1        : TLabel;
    Label2        : TLabel;

    messageToSend : TMemo;
    messagesLog   : TMemo;

    btn_connect   : TButton;
    btn_disconnect: TButton;
    btn_send      : TButton;

    // ... TIdTCPClient
    IdTCPClient       : TIdTCPClient;

    // ... TIdThreadComponent
    IdThreadComponent : TIdThreadComponent;

    procedure FormShow(Sender: TObject);

    procedure btn_connectClick(Sender: TObject);
    procedure btn_disconnectClick(Sender: TObject);
    procedure btn_sendClick(Sender: TObject);

    procedure IdTCPClientConnected(Sender: TObject);
    procedure IdTCPClientDisconnected(Sender: TObject);

    procedure IdThreadComponentRun(Sender: TIdThreadComponent);


  private
    { Private declarations }

    procedure Log(p_who, p_message: string);

  public
    { Public declarations }

  end;

var
  FClient     : TFClient;

implementation

{$R *.dfm}

// ... listening port: GUEST CLIENT
const
  GUEST_PORT = 20010;

// *****************************************************************************
//   EVENT : onShow()
//           ON SHOW FORM
// *****************************************************************************
procedure TFClient.FormShow(Sender: TObject);
begin

  // ... INITAILIZE

  // ... message to send
  messageToSend.Clear;
  messageToSend.Enabled     := false;

  // ... log
  messagesLog.Clear;

  // ... buttons
  btn_connect.Enabled       := true;
  btn_disconnect.Enabled    := false;
  btn_send.Enabled          := false;

end;
// .............................................................................


// *****************************************************************************
//   EVENT : btn_connectClick()
//           CLICK ON CONNECT BUTTON
// *****************************************************************************
procedure TFClient.btn_connectClick(Sender: TObject);
begin
  btn_connect.Enabled := false;

  // ... try to connect to Server
  try
    IdTCPClient.Connect;
  except
    on E: Exception do begin
      Log('CLIENT', 'CONNECTION ERROR! ' + E.Message);
      btn_connect.Enabled := true;
    end;
  end;

end;
// .............................................................................


// *****************************************************************************
//   EVENT : btn_disconnectClick()
//           CLICK ON DISCONNECT BUTTON
// *****************************************************************************
procedure TFClient.btn_disconnectClick(Sender: TObject);
begin
  btn_disconnect.Enabled := false;

  // ... disconnect from Server
  IdTCPClient.Disconnect;

  // ... set buttons
  btn_connect.Enabled       := true;
  btn_send.Enabled          := false;

  // ... message to send
  messageToSend.Enabled     := false;
end;
// .............................................................................


// *****************************************************************************
//   EVENT : onConnected()
//           OCCURS WHEN CLIENT IS CONNECTED
// *****************************************************************************
procedure TFClient.IdTCPClientConnected(Sender: TObject);
begin
  // ... messages log
  Log('CLIENT', 'CONNECTED!');

  // ... after connection is ok, run the Thread ... waiting messages 
  //     from server
  IdThreadComponent.Active := true;

  // ... set buttons
  btn_disconnect.Enabled    := true;
  btn_send.Enabled          := true;

  // ... enable message to send
  messageToSend.Enabled     := true;
end;
// .............................................................................


// *****************************************************************************
//   EVENT : onDisconnected()
//           OCCURS WHEN CLIENT IS DISCONNECTED
// *****************************************************************************
procedure TFClient.IdTCPClientDisconnected(Sender: TObject);
begin
  // ... message log
  Log('CLIENT', 'DISCONNECTED!');
end;
// .............................................................................


// *****************************************************************************
//   EVENT : btn_sendClick()
//           CLICK ON SEND BUTTON
// *****************************************************************************
procedure TFClient.btn_sendClick(Sender: TObject);
begin
  // ... send message to Server
  IdTCPClient.IOHandler.WriteLn(messageToSend.Text);
end;
// .............................................................................


// *****************************************************************************
//   EVENT : onRun()
//           OCCURS WHEN THE SERVER SEND A MESSAGE TO CLIENT
// *****************************************************************************
procedure TFClient.IdThreadComponentRun(Sender: TIdThreadComponent);
var
  msgFromServer : string;
begin
  // ... read message from server
  msgFromServer := IdTCPClient.IOHandler.ReadLn();

  // ... messages log
  Log('SERVER', msgFromServer);
end;
// .............................................................................


// *****************************************************************************
//   FUNCTION : Log()
//              LOGS A MESSAGE TO THE UI
// *****************************************************************************
procedure TFClient.Log(p_who, p_message: string);
begin
  TThread.Queue(nil,
    procedure
    begin
      MessagesLog.Lines.Add('[' + p_who + '] - ' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + ': ' + p_message);
    end
  );
end;
// .............................................................................

end.

我遇到了同样的问题,程序在清除连接的客户端的活动标志时冻结。这似乎是 IdScheduler 中的错误。 我的代码 `

//---------------------------------------------------------------------------
#include <vcl.h>
#include <IdSync.hpp>
#pragma hdrstop
//---------------------------------------------------------------------------
/*
    This is a general framework for TIdTCSServer and TIdTCPClient
    It uses a thread to read from the client.
    All threads are named.


    Bugs:
    4/11/19     Resetting the 'Active' property while there are still active
                connections (either local or from another program) locks up on
                that line. Both client and server threads remain active.
                Closing the program however works, so its processes must
                operate in a different manner.
                Closing a different process that is running a connected client
                works.
                Resetting the 'Active' property with a differnt process and a
                connected client locks on that line, and does not release
                when the other process is closed ();

    Maybe not an actual bug
        Server::OnStatus doesnt fire. Why ?

    Notes -
        It appears that setting 'Bindings' on the server has no effect.
        Default Ip (0's) will accept on any network (I run several at once,
        even if just ethernet & VirtualBox).
        I had thought that setting the bindings would allow certain network
        cards to be excluded from server access. In a production environment,
        I often find seperated networks are required by my customers.
        (I am aware I can easily refuse non-authorized connections)

        Two string altering functions 'IsMainThread' & 'IsNotMainThread' are
        provided to ensure that the proper mechanisms are used to write
        to the respective TListBox objects (VCL not being thread-safe).
*/
//---------------------------------------------------------------------------
#include "TIdTCPClientServerWin.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
//---------------------------------------------------------------------------
// A TIdSync is required for reading from the Server
//---------------------------------------------------------------------------
class TMyNotify : public TIdSync {
private:
    TListBox * lb;

public:
    String str;

    __fastcall TMyNotify ( TListBox * l ) {
        lb = l;
    }
    void __fastcall DoSynchronize (void) {
        Form2->IsNotMainThread ( str );
        lb->Items->Add ( str );
    }
};
//---------------------------------------------------------------------------
TForm2 *Form2;
//---------------------------------------------------------------------------
//                                  Form
//---------------------------------------------------------------------------
__fastcall TForm2::TForm2 ( TComponent * Owner )
                 : TForm  (              Owner ) {
    String str;

    mn = new TMyNotify ( lbServer );
    str = "Main Thread";
    uiMainThread = GetCurrentThreadId ();
    TThread::NameThreadForDebugging ( str );
}
//---------------------------------------------------------------------------
// Thread checks to ensure msgs that require syncing get it, and vice versa.
//---------------------------------------------------------------------------
void __fastcall TForm2::IsNotMainThread ( String& str ) {
    unsigned int uiCurrentThread;

    uiCurrentThread = GetCurrentThreadId ();
    if ( uiCurrentThread != uiMainThread ) {
        str += " Not Main";
    } /* endif */
}
//---------------------------------------------------------------------------
void __fastcall TForm2::IsMainThread ( String& str ) {
    unsigned int uiCurrentThread;

    uiCurrentThread = GetCurrentThreadId ();
    if ( uiCurrentThread != uiMainThread ) return;
    str += " IsMain";
}
//---------------------------------------------------------------------------
//                                  Server
//---------------------------------------------------------------------------
// Locks up when disabling - in vcl.forms
void __fastcall TForm2::cbServerActiveClick ( TObject * Sender ) {
    bool bFlag;

    bFlag = cbServerActive->Checked;
    IdTCPServer1->Active = bFlag;
}
//---------------------------------------------------------------------------
void __fastcall TForm2::IdTCPServer1Execute ( TIdContext * AContext ) {
    try {
        mn->str = String ( "read " )
                + AContext->Connection->IOHandler->ReadLn ();
        IsMainThread ( mn->str );
        mn->Synchronize ();
        AContext->Connection->IOHandler->WriteLn ( mn->str );
        IsMainThread ( mn->str );
        mn->str = String ( "write" );
        mn->Synchronize ();

    } catch (...) {
        AContext->Connection->Disconnect ();
        IsMainThread ( mn->str );
        mn->str = String ( "Exception caused by disconnection caught" );
        mn->Synchronize ();
    } /* end try/catch */
}
//---------------------------------------------------------------------------
// Thread Naming
//---------------------------------------------------------------------------
// names listener threads
void __fastcall TForm2::IdTCPServer1BeforeListenerRun ( TIdThread * AThread ) {
    String str;
    TIdIPVersion ver;
    TIdListenerThread * listen;

    listen = (TIdListenerThread *) AThread;
    str = IdTCPServer1->Name
        + String ( ":Listening for " );
    ver = listen->Binding->IPVersion;
    switch ( ver ) {
    case Id_IPv4:
        str += String ( "IPv4" );
        break;

    case Id_IPv6:
        str += String ( "IPv6" );
        break;

    default:
        str += String ( "Undefined" ) + String ( (int) ver );
        break;
    }
    str += String ( " connections on " );
    str += listen->Binding->IP;
    AThread->NameThreadForDebugging ( str );
}
//---------------------------------------------------------------------------
// Messaging ( some require syncing )
//---------------------------------------------------------------------------
// Overrides thread's 'OnBeforeRun' event
void __fastcall TForm2::IdTCPServer1Connect ( TIdContext * AContext ) {
    String str;
    String strPrologue;

    strPrologue = IdTCPServer1->Name
                + String ( ":" );
    str = String ( "Connection from " )
        + AContext->Binding->PeerIP
        + String ( ":" )
        + AContext->Binding->PeerPort
        + String ( " accepted" );
    TThread::NameThreadForDebugging ( strPrologue + str );
    mn->str = str;
    IsMainThread ( mn->str );
    mn->Synchronize ();
}
//---------------------------------------------------------------------------
// Overrides thread's 'OnAfterRun' event
void __fastcall TForm2::IdTCPServer1Disconnect ( TIdContext * AContext ) {
    mn->str = String ( "Disconnected from " )
            + AContext->Connection->Socket->Binding->PeerIP
            + String ( ":" )
            + AContext->Connection->Socket->Binding->PeerPort;
    IsMainThread ( mn->str );
    mn->Synchronize ();
}
//---------------------------------------------------------------------------
void __fastcall TForm2::IdTCPServer1Status ( TObject       * ASender,
                                             const TIdStatus AStatus,
                                             const UnicodeString AStatusText ) {
    String str;

    str = String ( "Status:" )
        + AStatusText;
    IsNotMainThread ( str );
    lbServer->Items->Add ( AStatusText );
}
//---------------------------------------------------------------------------
void __fastcall TForm2::IdTCPServer1Exception ( TIdContext * AContext,
                                                Exception  * AException ) {
    IsMainThread ( mn->str );
    mn->str = String ( "Exception:" )
            + AException->Message;
    mn->Synchronize ();
}
//---------------------------------------------------------------------------
//                                  Client
//---------------------------------------------------------------------------
// A thread is required for reading from the Client
class TMyThread : public TIdThread {
private:
    String         str;
    TIdTCPClient * cli;
    TListBox     * lb;

public:
    String __fastcall ThreadName ( TIdTCPClient * c ) {
        str = c->Name
            + String ( ":Host " )
            + c->Socket->Host
            + String ( " connected using local port " )
            + c->Socket->Binding->Port;
        return str;
    }
    __fastcall TMyThread ( TIdTCPClient * c, TListBox * l )
               : TIdThread ( true,
                             true,
                             ThreadName ( c ) ) {
        cli = c;
        lb  = l;
        FreeOnTerminate = false;
    }
    void __fastcall MyRead ( void ) {
        String strMsg;

        strMsg = String ( "recvd " ) + str;
        Form2->IsNotMainThread ( str );
        lb->Items->Add ( strMsg );
    }
    void __fastcall MyTerm ( void ) {
        String strMsg;

        strMsg = String ( "Terminated" );
        Form2->IsNotMainThread ( str );
        lb->Items->Add ( str );
    }
    void __fastcall Run ( void ) {
        try {
            str = cli->IOHandler->ReadLn ();
            cli->IOHandler->CheckForDisconnect ( true, true );
            Synchronize ( MyRead );

        } catch (...) {
            Synchronize ( MyTerm );
            Terminate ();
        } /* end try/catch */
    }
};
//---------------------------------------------------------------------------
void __fastcall TForm2::btnSendClick ( TObject * Sender ) {
    String      str;
    TDateTime   dt;

    dt = Now ();
    str = dt.FormatString ( "HH:NN:SS" );
    try {
        IdTCPClient1->IOHandler->WriteLn ( str );
        IsNotMainThread ( str );
        lbClient->Items->Add ( str );

    } catch (...) {
        str = "Exception in Write";
        IsNotMainThread ( str );
        lbClient->Items->Add ( str );
        IdTCPClient1->Disconnect ();
    } /* end try/catch */
}
//---------------------------------------------------------------------------
void __fastcall TForm2::cbClientEnabledClick ( TObject * Sender ) {
    if ( cbClientEnabled->Checked ) {
        IdTCPClient1->Connect ();
        return;
    } /* endif */
    IdTCPClient1->Disconnect ();
}
//---------------------------------------------------------------------------
// Messaging
//---------------------------------------------------------------------------
void __fastcall TForm2::IdTCPClient1Connected ( TObject * Sender ) {
    mt = new TMyThread ( IdTCPClient1, lbClient );
    mt->Start ();
}
//---------------------------------------------------------------------------
// Connection not yet established at this point
void __fastcall TForm2::IdTCPClient1SocketAllocated ( TObject * Sender ) {
    String str;

    str = "New Socket";
    IsNotMainThread ( str );
    lbClient->Items->Add ( str );
}
//---------------------------------------------------------------------------
void __fastcall TForm2::IdTCPClient1Status ( TObject * ASender,
                                             const TIdStatus AStatus,
                                             const UnicodeString AStatusText ) {
    String str;
    int    iLen;

    str = String ( "Status:" )
        + AStatusText;
    str.Delete ( str.Length (), 1 );

    switch ( AStatus ) {
    case hsConnected:
        str += String ( " using local port " )
            + String ( IdTCPClient1->Socket->Binding->Port );
        break;
    };

    IsNotMainThread ( str );
    lbClient->Items->Add ( str );
}
//---------------------------------------------------------------------------
void __fastcall TForm2::lbClearDblClick ( TObject * Sender ) {
    TListBox * lb;

    lb = (TListBox *) Sender;
    lb->Items->Clear ();
}
//---------------------------------------------------------------------------

// End of File

头文件:

//---------------------------------------------------------------------------
#ifndef TIdTCPClientServerWinH
#define TIdTCPClientServerWinH
//---------------------------------------------------------------------------
#include <System.Classes.hpp>
#include <Vcl.Controls.hpp>
#include <Vcl.StdCtrls.hpp>
#include <Vcl.Forms.hpp>
#include <IdBaseComponent.hpp>
#include <IdComponent.hpp>
#include <IdContext.hpp>
#include <IdCustomTCPServer.hpp>
#include <IdTCPClient.hpp>
#include <IdTCPConnection.hpp>
#include <IdTCPServer.hpp>
#include <Vcl.ComCtrls.hpp>
#include <IdThread.hpp>
#include <System.SysUtils.hpp>
#include <IdAntiFreezeBase.hpp>
#include <Vcl.IdAntiFreeze.hpp>
//---------------------------------------------------------------------------
class TMyNotify;
class TMyThread;
//---------------------------------------------------------------------------
class TForm2 : public TForm
{
__published:    // IDE-managed Components
    TIdTCPServer *IdTCPServer1;
    TIdTCPClient *IdTCPClient1;
    TListBox *lbServer;
    TButton *btnSend;
    TGroupBox *GroupBox1;
    TCheckBox *cbServerActive;
    TGroupBox *GroupBox2;
    TListBox *lbClient;
    TCheckBox *cbClientEnabled;
    TStatusBar *StatusBar1;
    TIdAntiFreeze *IdAntiFreeze1;
    void __fastcall btnSendClick(TObject *Sender);
    void __fastcall IdTCPServer1Connect(TIdContext *AContext);
    void __fastcall IdTCPServer1Disconnect(TIdContext *AContext);
    void __fastcall IdTCPServer1Status(TObject *ASender, const TIdStatus AStatus, 
const UnicodeString AStatusText);
    void __fastcall IdTCPServer1Execute(TIdContext *AContext);
    void __fastcall cbClientEnabledClick(TObject *Sender);
    void __fastcall cbServerActiveClick(TObject *Sender);
    void __fastcall IdTCPClient1Connected(TObject *Sender);
    void __fastcall IdTCPClient1SocketAllocated(TObject *Sender);
    void __fastcall IdTCPClient1Status(TObject *ASender, const TIdStatus AStatus, 
const UnicodeString AStatusText);
    void __fastcall IdTCPServer1BeforeListenerRun(TIdThread *AThread);
    void __fastcall IdTCPServer1Exception(TIdContext *AContext, Exception 
                                          *AException);
    void __fastcall lbClearDblClick(TObject *Sender);



private:    // User declarations
    TMyNotify * mn;
    TMyThread * mt;
    unsigned int uiMainThread;

    void __fastcall RdSync ( void );
    void __fastcall WrSync ( void );
    void __fastcall ExSync ( void );

    void __fastcall BeforeContextRun ( TIdContext * AContext );
    void __fastcall AfterContextRun  ( TIdContext * AContext );

public:     // User declarations
    __fastcall TForm2(TComponent* Owner);
    void __fastcall IsMainThread    ( String& str );
    void __fastcall IsNotMainThread ( String& str );
};
//---------------------------------------------------------------------------
extern PACKAGE TForm2 *Form2;
//---------------------------------------------------------------------------
#endif

DFM 文件:

object Form2: TForm2
Left = 0
Top = 0
Caption = 'TIdTCP Client Sever Test'
ClientHeight = 314
ClientWidth = 554
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
DesignSize = (
  554
  314)
PixelsPerInch = 96
TextHeight = 13
object GroupBox1: TGroupBox
  Left = 8
  Top = 8
  Width = 265
  Height = 273
  Anchors = [akLeft, akTop, akBottom]
  Caption = 'Server'
  TabOrder = 0
  DesignSize = (
    265
    273)
  object lbServer: TListBox
    Left = 16
    Top = 40
    Width = 233
    Height = 217
    Anchors = [akLeft, akTop, akRight, akBottom]
    ItemHeight = 13
    TabOrder = 0
    OnDblClick = lbClearDblClick
  end
  object cbServerActive: TCheckBox
    Left = 16
    Top = 16
    Width = 97
    Height = 17
    Caption = 'cbServerActive'
    TabOrder = 1
    OnClick = cbServerActiveClick
  end
end
object GroupBox2: TGroupBox
  Left = 288
  Top = 8
  Width = 258
  Height = 273
  Anchors = [akTop, akRight, akBottom]
  Caption = 'Client'
  TabOrder = 1
  DesignSize = (
    258
    273)
  object lbClient: TListBox
    Left = 16
    Top = 51
    Width = 226
    Height = 206
    Anchors = [akLeft, akTop, akRight, akBottom]
    ItemHeight = 13
    TabOrder = 0
    OnDblClick = lbClearDblClick
    ExplicitWidth = 193
  end
  object btnSend: TButton
    Left = 134
    Top = 20
    Width = 75
    Height = 25
    Caption = 'Send'
    TabOrder = 1
    OnClick = btnSendClick
  end
  object cbClientEnabled: TCheckBox
    Left = 16
    Top = 20
    Width = 97
    Height = 25
    Caption = 'cbClientEnabled'
    TabOrder = 2
    OnClick = cbClientEnabledClick
  end
end
object StatusBar1: TStatusBar
  Left = 0
  Top = 295
  Width = 554
  Height = 19
  Panels = <>
  SimplePanel = True
end
object IdTCPServer1: TIdTCPServer
  OnStatus = IdTCPServer1Status
  Bindings = <>
  DefaultPort = 474
  OnBeforeListenerRun = IdTCPServer1BeforeListenerRun
  OnConnect = IdTCPServer1Connect
  OnDisconnect = IdTCPServer1Disconnect
  OnException = IdTCPServer1Exception
  UseNagle = False
  OnExecute = IdTCPServer1Execute
  Left = 128
  Top = 24
end
object IdTCPClient1: TIdTCPClient
  OnStatus = IdTCPClient1Status
  OnConnected = IdTCPClient1Connected
  ConnectTimeout = 0
  Host = '127.0.0.1'
  IPVersion = Id_IPv4
  Port = 474
  ReadTimeout = -1
  UseNagle = False
  OnSocketAllocated = IdTCPClient1SocketAllocated
  Left = 320
  Top = 24
end
object IdAntiFreeze1: TIdAntiFreeze
  Left = 272
  Top = 56
end
end

`

我使用调试器跟踪执行路径,发现它在过程 TIdScheduler.TerminateAllYarns 中陷入循环。 概括 在 IdSceduler:168 [程序 TIdScheduler.TerminateAllYarns] 中, 我们尝试终止所有线程。线程被报告为已停止 [由过程 TIdThread.GetStopped],但这从未反映在 FActiveYarns 中,如通过 LList.Count (IdScheduler:182) 指定的那样。 我使用的是 Indy 10.1.5,CBuilder 10.0(西雅图)版本 23.0.20618.2753

此致

`

我遇到了同样的问题。

之前的回答对我没有帮助。

终于自己找到了

虽然我读这篇文章很晚,但希望对您和其他人有所帮助

你之前有事要做

tcpServer.Active := 假;

首先,您需要使 onDisconnect 事件处理程序不工作。

tcpServer.OnDisconnect:= nil;

而且你必须断开所有客户端

aContexClient.Connection.Disconnect();  //aContect -> all Context

见下方编码

procedure disconnectAllclient();
var
    tmpList      : TList;
    contexClient : TidContext;
begin
    tmpList  := tcpServer.Contexts.LockList;
    try
       while (tmpList.Count > 0) do begin
           contexClient := tmpList[0];
           contexClient.Connection.Disconnect();
           tmpList.Delete(0);
       end;
    finally
        tcpServer.Contexts.UnlockList;
    end;
end;

use :
    tcpServer.OnDisconnect  := nil;
    disconnectAllclient();
    tcpServer.Active := False;