⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 psitcpconnection.pas

📁 一个delphi的p2p控件的源代码
💻 PAS
📖 第 1 页 / 共 2 页
字号:
unit PsiTCPConnection;

//******************************************************************************
// The original software is under
// Copyright (c) 1993 - 2000, Chad Z. Hower (Kudzu)
//   and the Indy Pit Crew - http://www.nevrona.com/Indy/
//
// Amended : November 2000, by Michael M. Michalak MACS for use with
// MorphTek.com Inc Peer to Peer Open Source Components - http://www.morphtek.com
//
//******************************************************************************

interface

uses
  Classes, PsiException,
  PsiComponent, PsiGlobal, PsiSocketHandle, PsiIntercept;

type
  TPsiBuffer = class(TMemoryStream)
  public
    procedure RemoveXBytes(const AByteCount: integer);
  end;

  TPsiTCPConnection = class(TPsiComponent)
  protected
  	FASCIIFilter: boolean;
    FBinding: TPsiSocketHandle;
    FBuffer: TPsiBuffer;
    // TODO - Change the "move" functions to read write functinos. Get as much as possible down
    // to just TStream so we can replace it easily
  	FClosedGracefully: boolean;
    FCmdResultDetails: TStrings;
    FIntercept: TPsiConnectionIntercept;
    FInterceptEnabled: Boolean;
    FOnDisconnected: TNotifyEvent;
    FReadLnTimedOut: Boolean;
  	FRecvBuffer: TPsiBuffer; // To be used by ReadFromStack only
    FResultNo: SmallInt;
    FWriteBuffer: TPsiBuffer;
    FWriteBufferThreshhold: Integer;
    //
    procedure DoOnDisconnected; virtual;
    function GetCmdResult: string;
    function GetRecvBufferSize: Integer;
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
    procedure ResetConnection; virtual;
    procedure SetIntercept(AValue: TPsiConnectionIntercept);
    procedure SetInterceptEnabled(AValue: Boolean);
    procedure SetRecvBufferSize(const Value: Integer);
  public
    function AllData: string; virtual;
    procedure CancelWriteBuffer;
    procedure Capture(ADest: TObject; const ADelim: string = '.'; const AIsRFCMessage: Boolean = False);
    procedure CheckForDisconnect(const ARaiseExceptionIfDisconnected: boolean = true;
     const AIgnoreBuffer: boolean = false); virtual;
    procedure CheckForGracefulDisconnect(const ARaiseExceptionIfDisconnected: boolean = true); virtual;
    procedure ClearWriteBuffer;
    procedure CloseWriteBuffer;
    function Connected: boolean; virtual;
    constructor Create(AOwner: TComponent); override;
    function CurrentReadBuffer: string;
    function CurrentReadBufferSize: integer;
    destructor Destroy; override;
    procedure Disconnect; virtual;
    procedure DisconnectSocket; virtual;
    function ExtractXBytesFromBuffer(const AByteCount: Integer): string; virtual;
    procedure FlushWriteBuffer(const AByteCount: Integer = -1);
    function GetResponse: SmallInt; virtual;
    function InputLn(const AMask: string = ''): string;
    procedure OpenWriteBuffer(const AThreshhold: Integer = -1);
    // RaiseExceptionForCmdResult - Overload necesary as a exception as a default param doesnt work
    procedure RaiseExceptionForCmdResult; overload; virtual;
    procedure RaiseExceptionForCmdResult(axException: TClassPsiException); overload; virtual;
    procedure ReadBuffer(var ABuffer; const AByteCount: Longint);
    // ReadFromStack must be only call to Recv
    function ReadFromStack(const ARaiseExceptionIfDisconnected: boolean = true;
     const ATimeout: integer = PsiTimeoutInfinite; const AUseBuffer: boolean = true;
     ADestStream: TPsiBuffer = nil): integer;
     virtual;
    function ReadInteger(const AConvert: boolean = true): Integer;
    function ReadLn(const ATerminator: string = '';
     const ATimeout: integer = PsiTimeoutInfinite): string; virtual;
    function ReadLnWait: string;
    function ReadSmallInt: SmallInt;
    procedure ReadStream(AStream: TStream; AByteCount: LongInt = -1;
     const AReadUntilDisconnect: boolean = false);
    function ReadString(const ABytes: integer): string;
    procedure RemoveXBytesFromBuffer(const AByteCount: Integer); virtual;
    function SendCmd(const AOut: string; const AResponse: SmallInt = -1): SmallInt; overload; virtual;
    function SendCmd(const AOut: string; const AResponse: Array of SmallInt): SmallInt; overload; virtual;
    procedure Write(AOut: string); virtual;
    // WriteBuffer must be the ONLY call to SEND - all data goes thru this method
    procedure WriteBuffer(const ABuffer; AByteCount: Longint; const AWriteNow: boolean = false);
    procedure WriteHeader(axHeader: TStrings);
    procedure WriteInteger(AValue: Integer; const AConvert: boolean = true);
    procedure WriteLn(const AOut: string = ''); virtual;
    procedure WriteSmallInt(AValue: SmallInt);
    procedure WriteStream(AStream: TStream; const AAll: boolean = true;
     const AWriteByteCount: Boolean = false); virtual;
    procedure WriteStrings(AValue: TStrings);
    function WriteFile(AFile: String; const AEnableTransferFile: boolean = false): cardinal;
     virtual;
    //
    property Binding: TPsiSocketHandle read FBinding;
  	property ClosedGracefully: boolean read FClosedGracefully;
    property CmdResult: string read GetCmdResult;
    property CmdResultDetails: TStrings read FCmdResultDetails;
    property ReadLnTimedOut: Boolean read FReadLnTimedOut;
    property ResultNo: SmallInt read FResultNo;
  published
  	property ASCIIFilter: boolean read FASCIIFilter write FASCIIFilter;
    property Intercept: TPsiConnectionIntercept read FIntercept write SetIntercept;
    property InterceptEnabled: Boolean read FInterceptEnabled write SetInterceptEnabled;
    property OnDisconnected: TNotifyEvent read FOnDisconnected write FOnDisconnected;
    property OnWork;
    property OnWorkBegin;
    property OnWorkEnd;
    property RecvBufferSize: Integer read GetRecvBufferSize write SetRecvBufferSize;
  end;

implementation

uses
  PsiAntiFreezeBase,
  PsiStack, PsiStackConsts, PsiResourceStrings,
  SysUtils;

function TPsiTCPConnection.AllData: string;
begin
  BeginWork(wmRead); try
    result := '';
    while Connected do begin
      Result := Result + CurrentReadBuffer;
    end;
  finally EndWork(wmRead); end;
end;

procedure TPsiTCPConnection.Capture(ADest: TObject; const ADelim: string = '.';
  const AIsRFCMessage: Boolean = False);
var
  s: string;
begin
  BeginWork(wmRead); try
    repeat
      s := ReadLn;
      if s = ADelim then begin
        exit;
      end;

      // For RFC 822 retrieves
      if AIsRFCMessage and (Copy(s, 1, 2) = '..') then begin
        Delete(s, 1, 1);
      end;

      // Write to output
      if ADest is TStrings then begin
        TStrings(ADest).Add(s);
      end else if ADest is TStream then begin
        TStream(ADest).WriteBuffer(s[1], Length(s));
        s := EOL;
        TStream(ADest).WriteBuffer(s[1], Length(s));
      end else begin
        raise EPsiException.Create(RSObjectTypeNotSupported);
      end;
    until false;
  finally EndWork(wmRead); end;
end;

procedure TPsiTCPConnection.CheckForDisconnect(const ARaiseExceptionIfDisconnected: boolean = true;
 const AIgnoreBuffer: boolean = false);
begin
  if ClosedGracefully then begin
    if Binding.HandleAllocated then begin
    	DisconnectSocket;
    end;
    // Do not raise unless all data has been read by the user
	  if ((CurrentReadBufferSize = 0) or AIgnoreBuffer) and ARaiseExceptionIfDisconnected then begin
      // ************************************************************* //
      // An exception may occur here. This only happens in the IDE and this is normal.
      // Winshoes will handle this. This does not happen in the EXE.
      // ************************************************************* //
      raise EPsiConnClosedGraceful.Create(RSConnectionClosedGracefully);
    end;
  end;
end;

function TPsiTCPConnection.Connected: boolean;
begin
  CheckForDisconnect(False);
  result := Binding.HandleAllocated;
end;

constructor TPsiTCPConnection.Create(AOwner: TComponent);
begin
  inherited;
  FBinding := TPsiSocketHandle.Create(nil);
  FCmdResultDetails := TStringList.Create;
 	FRecvBuffer := TPsiBuffer.Create;
  RecvBufferSize := 8192;
  FBuffer := TPsiBuffer.Create;
end;

function TPsiTCPConnection.CurrentReadBuffer: string;
begin
  result := '';
  if Connected then begin
    ReadFromStack(False);
    result := ExtractXBytesFromBuffer(FBuffer.Size);
  end;
end;

function TPsiTCPConnection.CurrentReadBufferSize: integer;
begin
  result := FBuffer.Size;
end;

destructor TPsiTCPConnection.Destroy;
begin
  FreeAndNil(FBuffer);
 	FreeAndNil(FRecvBuffer);
  FreeAndNil(FCmdResultDetails);
  FreeAndNil(FBinding);
  inherited;
end;

procedure TPsiTCPConnection.Disconnect;
begin
  DisconnectSocket;
end;

procedure TPsiTCPConnection.DoOnDisconnected;
begin
  if assigned(OnDisconnected) then begin
    OnDisconnected(Self);
  end;
end;

function TPsiTCPConnection.ExtractXBytesFromBuffer(const AByteCount: Integer): string;
begin
  if AByteCount > FBuffer.Size then begin
    raise EPsiException.Create(RSNotEnoughDataInBuffer);
  end;
  SetString(result, PChar(FBuffer.Memory), AByteCount);
  RemoveXBytesFromBuffer(AByteCount);
  DoWork(wmRead, AByteCount);
end;

function TPsiTCPConnection.GetCmdResult: string;
begin
	result := '';
	if CmdResultDetails.Count > 0 then begin
		result := CmdResultDetails[CmdResultDetails.Count - 1];
  end;
end;

function TPsiTCPConnection.GetRecvBufferSize: Integer;
begin
	result := FRecvBuffer.Size;
end;

function TPsiTCPConnection.GetResponse: SmallInt;
var
  sLine, sTerm: string;
begin
  CmdResultDetails.Clear;
  sLine := ReadLnWait;
  CmdResultDetails.Add(sLine);
  if length(sLine) > 3 then begin
    if sLine[4] = '-' then begin // Multi line response coming
      sTerm := Copy(sLine, 1, 3) + ' ';
      {We keep reading lines until we encounter either a line such as "250"
       or "250 Read"}
      repeat
        sLine := ReadLnWait;
        CmdResultDetails.Add(sLine);
      until (Length(sLine) < 4) or (AnsiSameText(Copy(sLine, 1, 4), sTerm));
    end;
  end;

  if AnsiSameText(Copy(CmdResult, 1, 3), '+OK') then begin
    FResultNo := wsOK;
  end else if AnsiSameText(Copy(CmdResult, 1, 4), '-ERR') then begin
    FResultNo := wsErr;
  end else begin
    FResultNo := StrToIntDef(Copy(CmdResult, 1, 3), 0);
  end;

  Result := ResultNo;
end;

procedure TPsiTCPConnection.RaiseExceptionForCmdResult(axException: TClassPsiException);
begin
  raise axException.Create(CmdResult);
end;

procedure TPsiTCPConnection.RaiseExceptionForCmdResult;
begin
  EPsiProtocolReplyError.CreateError(ResultNo, CmdResult);
end;

procedure TPsiTCPConnection.ReadBuffer(var ABuffer; const AByteCount: Integer);
begin
	if (AByteCount > 0) and (@ABuffer <> nil) then begin
    // Read from stack until we have enough data
    while CurrentReadBufferSize < AByteCount do begin
      ReadFromStack;
    end;
    // Copy it to the callers buffer
    Move(PChar(FBuffer.Memory)[0], ABuffer, AByteCount);
    // Remove used data from buffer
    RemoveXBytesFromBuffer(AByteCount);
  end;
end;

function TPsiTCPConnection.ReadFromStack(const ARaiseExceptionIfDisconnected: boolean = true;
 const ATimeout: integer = PsiTimeoutInfinite; const AUseBuffer: boolean = true;
 ADestStream: TPsiBuffer = nil): integer;
// Reads any data in tcp/ip buffer and puts it into Winshoe 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
  nByteCount, j: Integer;

  procedure DefaultRecv;
  begin
    // No need to call AntiFreeze, the Readable does that.
    nByteCount := Binding.Recv(ADestStream.Memory^, ADestStream.Size, 0);
  end;

begin
  result := 0;
  // Check here as this side may have closed the socket
  CheckForDisconnect(ARaiseExceptionIfDisconnected);
  if Connected then begin
    if ADestStream = nil then begin
      ADestStream := FRecvBuffer;
    end;
    if Binding.Readable(ATimeout) then begin
      if InterceptEnabled then begin
        if Intercept.RecvHandling then begin
          nByteCount := Intercept.Recv(ADestStream.Memory^, ADestStream.Size);
        end else begin
          DefaultRecv;
        end;
      end else begin
        DefaultRecv;
      end;

      FClosedGracefully := nByteCount = 0;
      if not ClosedGracefully then begin
        if GStack.CheckForSocketError(nByteCount, [Psi_WSAESHUTDOWN]) then begin
          nByteCount := 0;
          if Binding.HandleAllocated then begin
            DisconnectSocket;
          end;
          // Do not raise unless all data has been read by the user
          if CurrentReadBufferSize = 0 then begin
            GStack.RaiseSocketError(Psi_WSAESHUTDOWN);
          end;
        end;
        if ASCIIFilter then begin
          for j := 1 to nByteCount do begin
            PChar(ADestStream.Memory)[j] := Chr(Ord(PChar(ADestStream.Memory)[j]) and $7F);
          end;
        end;
      end;
      if AUseBuffer then begin
        FBuffer.Position := FBuffer.Size;
        FBuffer.WriteBuffer(ADestStream.Memory^, nByteCount);
      end else begin
        // If buffered, DoWork is called when bytes are removed from the buffer
        DoWork(wmRead, nByteCount);
      end;
      if InterceptEnabled then begin
        Intercept.DataReceived(ADestStream.Memory^, nByteCount);
      end;
      // Check here as other side may have closed connection
      CheckForDisconnect(ARaiseExceptionIfDisconnected);
      result := nByteCount;
    end;
  end;
end;

function TPsiTCPConnection.ReadInteger(const AConvert: boolean = true): Integer;
begin
	ReadBuffer(Result, SizeOf(Result));
  if AConvert then begin
    Result := Integer(GStack.WSNToHL(LongWord(Result)));
  end;
end;

function TPsiTCPConnection.ReadLn(const ATerminator: string = '';
 const ATimeout: integer = PsiTimeoutInfinite): string;
var
  i: Integer;
  s: string;
  LTerminator: string;
begin
  if Length(ATerminator) = 0 then begin
    LTerminator := LF;
  end else begin
    LTerminator := ATerminator;
  end;
  FReadLnTimedOut := False;
 	i := 0;
  repeat
    if CurrentReadBufferSize > 0 then begin
	    {TODO make searches more efficient, do not copy to a string, search mem directly}
      SetString(s, PChar(FBuffer.Memory), FBuffer.Size);
      i := AnsiPos(LTerminator, s);
    end;
    // ReadFromStack blocks - do not call unless we need to

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -