•  
Создано: 18.02.2011 1:28:01 · Исправлено: 18.02.2011 1:28:01 · Прочтений: 1553

{ $HDR$}
{**********************************************************************}
{ Unit archived using Team Coherence                                   }
{ Team Coherence is Copyright 2002 by Quality Software Components      }
{                                                                      }
{ For further information / comments, visit our WEB site at            }
{ http://www.TeamCoherence.com                                         }
{**********************************************************************}
{}
{ $Log:  11952: IdIOHandlerStack.pas
{
{   Rev 1.51    11/11/04 12:03:46 PM  RLebeau
{ Updated DoConnectTimeout() to recognize IdTimeoutDefault
}
{
{   Rev 1.50    6/18/04 1:06:58 PM  RLebeau
{ Bug fix for ReadTimeout property
}
{
{   Rev 1.49    5/4/2004 9:57:34 AM  JPMugaas
{ Removed some old uncommented code and reenabled some TransparentProxy code
{ since it compile in DotNET.
}
{
{   Rev 1.48    2004.04.18 12:52:02 AM  czhower
{ Big bug fix with server disconnect and several other bug fixed that I found
{ along the way.
}
{
{   Rev 1.47    2004.04.08 3:56:34 PM  czhower
{ Fixed bug with Intercept byte count. Also removed Bytes from Buffer.
}
{
{   Rev 1.46    2004.03.12 8:01:00 PM  czhower
{ Exception update
}
{
{   Rev 1.45    2004.03.07 11:48:42 AM  czhower
{ Flushbuffer fix + other minor ones found
}
{
{   Rev 1.44    2004.03.01 5:12:32 PM  czhower
{ -Bug fix for shutdown of servers when connections still existed (AV)
{ -Implicit HELP support in CMDserver
{ -Several command handler bugs
{ -Additional command handler functionality.
}
{
{   Rev 1.43    2/21/04 9:25:50 PM  RLebeau
{ Fix for BBG #66
{ 
{ Added FLastSocketError member to TIdConnectThread
}
{
{   Rev 1.42    2004.02.03 4:16:48 PM  czhower
{ For unit name changes.
}
{
{   Rev 1.41    12/31/2003 9:51:56 PM  BGooijen
{ Added IPv6 support
}
{
{   Rev 1.40    2003.12.28 1:05:58 PM  czhower
{ .Net changes.
}
{
{   Rev 1.39    11/21/2003 12:05:18 AM  BGooijen
{ Terminated isn't public in TThread any more, made it public here now
}
{
{   Rev 1.38    10/28/2003 9:15:44 PM  BGooijen
{ .net
}
{
{   Rev 1.37    10/18/2003 1:42:46 PM  BGooijen
{ Added include
}
{
{   Rev 1.36    2003.10.14 1:26:56 PM  czhower
{ Uupdates + Intercept support
}
{
{   Rev 1.35    2003.10.11 5:48:36 PM  czhower
{ -VCL fixes for servers
{ -Chain suport for servers (Super core)
{ -Scheduler upgrades
{ -Full yarn support
}
{
{   Rev 1.34    10/9/2003 8:09:10 PM  SPerry
{ bug fixes
}
{
{   Rev 1.33    10/5/2003 11:02:36 PM  BGooijen
{ Write buffering
}
{
{   Rev 1.32    05/10/2003 23:01:02  HHariri
{ Fix for connect problem when IP address specified as opposed to host
}
{
{   Rev 1.31    2003.10.02 8:23:42 PM  czhower
{ DotNet Excludes
}
{
{   Rev 1.30    2003.10.02 10:16:28 AM  czhower
{ .Net
}
{
{   Rev 1.29    2003.10.01 9:11:18 PM  czhower
{ .Net
}
{
{   Rev 1.28    2003.10.01 5:05:14 PM  czhower
{ .Net
}
{
{   Rev 1.27    2003.10.01 2:46:38 PM  czhower
{ .Net
}
{
{   Rev 1.26    2003.10.01 2:30:38 PM  czhower
{ .Net
}
{
{   Rev 1.22    10/1/2003 12:14:14 AM  BGooijen
{ DotNet: removing CheckForSocketError
}
{
{   Rev 1.21    2003.10.01 1:37:34 AM  czhower
{ .Net
}
{
{   Rev 1.19    2003.09.30 1:22:58 PM  czhower
{ Stack split for DotNet
}
{
{   Rev 1.18    2003.07.14 1:57:22 PM  czhower
{ -First set of IOCP fixes.
{ -Fixed a threadsafe problem with the stack class.
}
{
{   Rev 1.17    2003.07.14 12:54:32 AM  czhower
{ Fixed graceful close detection if it occurs after connect.
}
{
{   Rev 1.16    2003.07.10 4:34:58 PM  czhower
{ Fixed AV, added some new comments
}
{
{   Rev 1.15    7/4/2003 08:26:46 AM  JPMugaas
{ Optimizations.
}
{
{   Rev 1.14    7/1/2003 03:39:48 PM  JPMugaas
{ Started numeric IP function API calls for more efficiency.
}
{
    Rev 1.13    6/30/2003 10:25:18 AM  BGooijen
  removed unnecessary assignment to FRecvBuffer.Size
}
{
    Rev 1.12    6/29/2003 10:56:28 PM  BGooijen
  Removed .Memory from the buffer, and added some extra methods
}
{
{   Rev 1.11    2003.06.25 4:28:32 PM  czhower
{ Formatting and fixed a short circuit clause.
}
{
    Rev 1.10    6/3/2003 11:43:52 PM  BGooijen
  Elimintated some code
}
{
    Rev 1.9    4/16/2003 3:31:26 PM  BGooijen
  Removed InternalCheckForDisconnect, added .Connected
}
{
    Rev 1.8    4/14/2003 11:44:20 AM  BGooijen
  CheckForDisconnect calls ReadFromSource now
}
{
    Rev 1.7    4/2/2003 3:24:56 PM  BGooijen
  Moved transparantproxy from ..stack to ..socket
}
{
    Rev 1.6    3/5/2003 11:04:32 PM  BGooijen
  Fixed Intercept, but the part in WriteBuffer doesn't look really nice yet
}
{
    Rev 1.5    3/3/2003 11:31:58 PM  BGooijen
  fixed stack overflow in .CheckForDisconnect
}
{
    Rev 1.4    2/26/2003 1:15:40 PM  BGooijen
  FBinding is now freed in IdIOHandlerSocket, instead of in IdIOHandlerStack
}
{
{   Rev 1.3    2003.02.25 1:36:12 AM  czhower
}
{
{   Rev 1.2    2002.12.06 11:49:34 PM  czhower
}
{
{   Rev 1.1    12-6-2002 20:10:18  BGooijen
{ Added IPv6-support
}
{
{   Rev 1.0    11/13/2002 08:45:16 AM  JPMugaas
}
unit IdIOHandlerStack;

interface

uses
  Classes,
  IdGlobal, IdSocketHandle, IdIOHandlerSocket, IdExceptionCore, IdStack;

type
  TIdIOHandlerStack = class(TIdIOHandlerSocket)
  protected
    procedure ConnectClient; override;
    function ReadFromSource(ARaiseExceptionIfDisconnected: Boolean = True;
     ATimeout: Integer = IdTimeoutDefault;
     ARaiseExceptionOnTimeout: Boolean = True): Integer; override;
  public
    procedure CheckForDataOnSource(ATimeout: Integer = 0); override;
    procedure CheckForDisconnect(ARaiseExceptionIfDisconnected: Boolean = True;
     AIgnoreBuffer: Boolean = False); override;
    function Connected: Boolean; override;
    function Readable(AMSec: Integer = IdTimeoutDefault): Boolean; override;
    // In TIdIOHandlerStack, WriteBytes must be the ONLY call to
    // WriteToDestination - all data goes thru this method
    procedure WriteDirect(
      ABuffer: TIdBytes
      ); override;
  published
    property ReadTimeout default IdDefTimeout;
  end;

implementation

uses
  IdAntiFreezeBase, IdResourceStringsCore, IdResourceStrings, IdStackConsts, IdException,
  IdTCPConnection, IdComponent, IdIOHandler,
  SysUtils;

type
  TIdConnectThread = class(TThread)
  protected
    FBinding: TIdSocketHandle;
    FLastSocketError: Integer;
    FExceptionMessage: string;
  public
    procedure Execute; override;
    property Terminated;
  end;

{ TIdIOHandlerStack }

function TIdIOHandlerStack.Connected: Boolean;
begin
  ReadFromSource(False, 0, False);
  Result := inherited Connected;
end;

procedure TIdIOHandlerStack.ConnectClient;

  procedure DoConnectTimeout(ATimeout: Integer);
  var
    LSleepTime: Integer;
    LInfinite: Boolean;
  begin
    if ATimeout = IdTimeoutDefault then begin
      ATimeout := IdTimeoutInfinite;
    end;
    LInfinite := ATimeout = IdTimeoutInfinite;
    with TIdConnectThread.Create(True) do try
      FBinding := Binding;
      Resume;
      // Sleep
      if TIdAntiFreezeBase.ShouldUse then begin
        LSleepTime := Min(GAntiFreeze.IdleTimeOut, 125);
      end else begin
        LSleepTime := 125;
      end;

      if LInfinite then begin
        ATimeout := LSleepTime + 1;
      end;

      while ATimeout > LSleepTime do begin
        IdGlobal.Sleep(LSleepTime);
        ATimeout := ATimeout - LSleepTime;

        if LInfinite then begin
          ATimeout := LSleepTime + 1;
        end;

        TIdAntiFreezeBase.DoProcess;
        if Terminated then begin
          ATimeout := 0;
          Break;
        end;
      end;
      IdGlobal.Sleep(ATimeout);
      //
      if Terminated then begin
        if FExceptionMessage <> '' then begin
          if FLastSocketError <> 0 then begin
            raise EIdSocketError.CreateError(FLastSocketError, FExceptionMessage);
          end;
          EIdConnectException.Toss(FExceptionMessage);
        end;
      end else begin
        Terminate;
        Close;
        WaitFor;
        EIdConnectTimeout.Toss(RSConnectTimeout);
      end;
    finally Free; end;
  end;

var
  LHost: String;
  LPort: Integer;
  LIP: string;
begin
  inherited;
  if Assigned(FTransparentProxy) then begin
    if FTransparentProxy.Enabled then begin
      LHost := FTransparentProxy.Host;
      LPort := FTransparentProxy.Port;
    end else begin
      LHost := Host;
      LPort := Port;
    end;
  end else begin
    LHost := Host;
    LPort := Port;
  end;

  if not GStack.IsIP(LHost) then begin
    if Assigned(OnStatus) then begin
      DoStatus(hsResolving, [LHost]);
    end;
    LIP := GStack.ResolveHost(LHost, FIPVersion);
  end else begin
    LIP := LHost;
  end;
  Binding.SetPeer(LIP, LPort);
  // Connect
  //note for status events, we check specifically for them here
  //so we don't do a string conversion in Binding.PeerIP.
  if Assigned(OnStatus) then begin
    DoStatus(hsConnecting, [Binding.PeerIP]);
  end;

  if ConnectTimeout = 0 then begin
    if TIdAntiFreezeBase.ShouldUse then begin
      DoConnectTimeout(120000); // 2 Min
    end else begin
      Binding.Connect;
    end;
  end else begin
    DoConnectTimeout(ConnectTimeout);
  end;
  if Assigned(FTransparentProxy) then begin
    if FTransparentProxy.Enabled then begin
      FTransparentProxy.Connect(Self, Host, Port);
    end;
  end;
end;

function TIdIOHandlerStack.Readable(AMSec: integer): boolean;
begin
  Result := Binding.Readable(AMSec);
end;

procedure TIdIOHandlerStack.WriteDirect(
  ABuffer: TIdBytes
  );
var
  LCount: Integer;
  LPos: Integer;
  LSize: Integer;
begin
  inherited;
  EIdNotConnected.IfFalse(BindingAllocated, RSNotConnected);
  LSize := Length(ABuffer);
  LPos := 0;
  repeat
    LCount := Binding.Send(ABuffer, LPos, LSize - LPos);
    // TODO - Have a AntiFreeze param which allows the send to be split up so that process
    // can be called more. Maybe a prop of the connection, MaxSendSize?
    TIdAntiFreezeBase.DoProcess(False);
    FClosedGracefully := LCount = 0;

    // Check if other side disconnected
    CheckForDisconnect;
    DoWork(wmWrite, LCount);
    LPos := LPos + LCount;
  until LPos >= LSize;
end;

function TIdIOHandlerStack.ReadFromSource(
 ARaiseExceptionIfDisconnected: Boolean; ATimeout: Integer;
 ARaiseExceptionOnTimeout: Boolean): Integer;
// Reads any data in tcp/ip buffer and puts it into Indy buffer
// This must be the ONLY raw read from Winsock routine
// This must be the ONLY call to RECV - all data goes thru this method
var
  LByteCount: Integer;
  LBuffer: TIdBytes;
begin
  if ATimeout = IdTimeoutDefault then begin
    if ReadTimeout = 0 then begin
      ATimeout := IdTimeoutInfinite;
    end else begin
      ATimeout := ReadTimeout;
    end;
  end;
  Result := 0;
  // Check here as this side may have closed the socket
  CheckForDisconnect(ARaiseExceptionIfDisconnected);
  if BindingAllocated then begin
    LByteCount := 0;
    repeat
      if Readable(ATimeout) then begin
        if Assigned(FRecvBuffer) then begin
          // No need to call AntiFreeze, the Readable does that.
          if BindingAllocated then begin
            // TODO: Whey are we reallocating LBuffer every time? This should
            // be a one time operation per connection.
            SetLength(LBuffer, RecvBufferSize); try
              LByteCount := Binding.Receive(LBuffer);
              SetLength(LBuffer, LByteCount);
              if LByteCount > 0 then begin
                if Intercept <> nil then begin
                  Intercept.Receive(LBuffer);
                  LByteCount := Length(LBuffer);
                end;
    //AsciiFilter - needs to go in TIdIOHandler base class
    //            if ASCIIFilter then begin
    //              for i := 1 to IOHandler.RecvBuffer.Size do begin
    //                PChar(IOHandler.RecvBuffer.Memory)[i] := Chr(Ord(PChar(IOHandler.RecvBuffer.Memory)[i]) and $7F);
    //              end;
    //            end;
                // Pass through LBuffer first so it can go through Intercept
                //TODO: If not intercept, we can skip this step
                InputBuffer.Write(LBuffer);
              end;
            finally LBuffer := nil; end;
          end else begin
            EIdClosedSocket.Toss(RSStatusDisconnected);
          end;
        end else begin
          LByteCount := 0;
          EIdException.IfTrue(ARaiseExceptionIfDisconnected, RSNotConnected);
        end;
        FClosedGracefully := LByteCount = 0;
        // Check here as other side may have closed connection
        CheckForDisconnect(ARaiseExceptionIfDisconnected);
        Result := LByteCount;
      end else begin
        // Timeout
        EIdReadTimeout.IfTrue(ARaiseExceptionOnTimeout, RSReadTimeout);
        Result := -1;
        Break;
      end;
    until (LByteCount <> 0) or (BindingAllocated = False);
  end else begin
    if ARaiseExceptionIfDisconnected then begin
      raise EIdException.Create(RSNotConnected);
    end;
  end;
end;

procedure TIdIOHandlerStack.CheckForDisconnect(
 ARaiseExceptionIfDisconnected: Boolean; AIgnoreBuffer: Boolean);
var
  LDisconnected: Boolean;
begin
  // ClosedGracefully // Server disconnected
  // IOHandler = nil // Client disconnected
  if ClosedGracefully then begin
    if BindingAllocated then begin
      Close;
      // Call event handlers to inform the user that we were disconnected
      DoStatus(hsDisconnected);
      //DoOnDisconnected;
    end;
    LDisconnected := True;
  end else begin
    LDisconnected := not BindingAllocated;
  end;
  // Do not raise unless all data has been read by the user
  if LDisconnected then begin
    if Assigned(FInputBuffer) then begin
      if ((FInputBuffer.Size = 0) or AIgnoreBuffer)
       and ARaiseExceptionIfDisconnected then begin
        RaiseConnClosedGracefully;
      end;
    end;
  end;
end;

procedure TIdIOHandlerStack.CheckForDataOnSource(ATimeout: Integer);
begin
  if Connected then begin
    ReadFromSource(False, ATimeout, False);
  end;
end;

{ TIdConnectThread }

procedure TIdConnectThread.Execute;
begin
  try
    try
      FBinding.Connect;
    except
      on E: EIdSocketError do begin
        if (E.LastError <> Id_WSAEBADF) and (E.LastError <> Id_WSAENOTSOCK) then begin
          raise;
        end;
      end;
    end;
  except
    on E: Exception do begin
      FExceptionMessage := E.Message;
      if E is EIdSocketError then begin
        FLastSocketError := EIdSocketError(E).LastError;
      end;
    end;
  end;
  // Necessary as caller checks this
  Terminate;
end;

initialization
  TIdIOHandlerStack.SetDefaultClass;
end.