我使用Indy 10.6.2.5298。

TIdTCPConnection.Disconnect和TIdIOHandler.Close有什么区别?他们两个都断开线路,但有时前者会导致访问冲突错误。

很抱歉,我无法通过帮助文档及其源代码来理解它。

type
  TForm1 = class(TForm)
    IdTCPServer1: TIdTCPServer;
    procedure FormClick(Sender: TObject);
    procedure IdTCPServer1Execute(AContext: TIdContext);
  private
    TestContext: TIdContext;
  end;

procedure TForm1.FormClick(Sender: TObject);
begin
  TestContext.Connection.Disconnect; // access violation

  TestContext.Connection.IOHandler.Close; // always works well
end;

procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);
begin
  TestContext := AContext;

  AContext.Connection.Disconnect; // works well
end;

最佳答案

TIdTCPConnection.Disconnect()在内部调用IOHandler.Close(),如果已分配IOHandler并已将其打开(它还会调用TIdTCPConnection.DisconnectNotifyPeer()并触发OnDisconnectedOnStatus事件):

procedure TIdTCPConnection.Disconnect(ANotifyPeer: Boolean);
var
  // under ARC, convert a weak reference to a strong reference before working with it
  LIOHandler: TIdIOHandler;
begin
  try
    // Separately to avoid calling .Connected unless needed
    if ANotifyPeer then begin
      // TODO: do not call Connected() here if DisconnectNotifyPeer() is not
      // overriden. Ideally, Connected() should be called by overridden
      // DisconnectNotifyPeer() implementations if they really need it. But
      // to avoid any breakages in third-party overrides, we could check here
      // if DisconnectNotifyPeer() has been overridden and then call Connected()
      // to maintain existing behavior...
      //
      try
        if Connected then begin
          DisconnectNotifyPeer;
        end;
      except
        // TODO: maybe allow only EIdConnClosedGracefully and EIdSocketError?
      end;
    end;
  finally
    {
     there are a few possible situations here:
     1) we are still connected, then everything works as before,
        status disconnecting, then disconnect, status disconnected
     2) we are not connected, and this is just some "rogue" call to
        disconnect(), then nothing happens
     3) we are not connected, because ClosedGracefully, then
        LConnected will be false, but the implicit call to
        CheckForDisconnect (inside Connected) will call the events
    }
    // We dont check connected here - we realy dont care about actual socket state
    // Here we just want to close the actual IOHandler. It is very possible for a
    // socket to be disconnected but the IOHandler still open. In this case we only
    // care of the IOHandler is still open.
    //
    // This is especially important if the socket has been disconnected with error, at this
    // point we just want to ignore it and checking .Connected would trigger this. We
    // just want to close. For some reason NS 7.1 (And only 7.1, not 7.0 or Mozilla) cause
    // CONNABORTED. So its extra important we just disconnect without checking socket state.
    LIOHandler := IOHandler;
    if Assigned(LIOHandler) then begin
      if LIOHandler.Opened then begin
        DoStatus(hsDisconnecting);
        LIOHandler.Close;
        DoOnDisconnected;
        DoStatus(hsDisconnected);
        //LIOHandler.InputBuffer.Clear;
      end;
    end;
  end;
end;


TIdIOHandler.Close()只需关闭套接字(如果已分配):

procedure TIdIOHandlerSocket.Close;
begin
  if FBinding <> nil then begin
    FBinding.CloseSocket;
  end;
  inherited Close;
end;




procedure TIdIOHandler.Close;
//do not do FInputBuffer.Clear; here.
//it breaks reading when remote connection does a disconnect
var
  // under ARC, convert a weak reference to a strong reference before working with it
  LIntercept: TIdConnectionIntercept;
begin
  try
    LIntercept := Intercept;
    if LIntercept <> nil then begin
      LIntercept.Disconnect;
    end;
  finally
    FOpened := False;
    WriteBufferClear;
  end;
end;


发生访问冲突错误的原因很可能是因为测试代码一开始并不是线程安全的。 TIdTCPServer是多线程组件。它的OnConnectOnDisconnectOnExecuteOnException事件是在管理TIdContext对象的辅助线程的上下文中触发的。您的OnClick处理程序正在访问该线程之外的TIdContext对象。关闭套接字后,TIdTCPServer将立即检测到该异常并停止线程,破坏TIdContext及其TIdTCPConnectionTIdIOHandler对象。由于线程计时和上下文切换,您的OnClick处理程序很可能会在销毁这些对象后继续访问这些对象。您不会在OnExecute处理程序内部遇到问题,因为在线程运行时对象仍然有效。

为了使您的OnClick代码与TIdTCPServer配合使用,您需要锁定TIdTCPServer.Contexts列表,以便在TIdContext仍在尝试使用它时,不会破坏OnClick对象,例如:

procedure TForm1.FormClick(Sender: TObject);
var
  List: TIdContextList;
begin
  List := IdTCPServer1.Contexts.LockList;
  try
    //has the context already been removed?
    if List.IndexOf(TestContext) <> -1 then
      TestContext.Connection.Disconnect;
  finally
    IdTCPServer1.Contexts.UnlockList;
  end;
end;

09-25 20:07