{ $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: 10213: IdIOHandlerSocket.pas { { Rev 1.4 4/24/04 12:51:50 PM RLebeau { Added setter method to UseNagle property } { { Rev 1.3 10/15/03 1:44:26 PM RLebeau { Updated TIdConnectThread to store the socket's last error number if an { EIdSocketError is thrown, so that TIdIOHandlerSocket::ConnectClient() can { throw an EIdSocketError instead of an EIdConmectException when appropriate. } { { Rev 1.2 2/16/2003 03:36:00 PM JPMugaas { Added comment about new patch. } { { Rev 1.1 2/15/2003 03:02:10 PM JPMugaas { Now can create a SocksInfo object at design time. Not sure if this will have { any unintended consequences. } { { Rev 1.0 2002.11.12 10:42:34 PM czhower } unit IdIOHandlerSocket; interface uses Classes, IdGlobal, IdSocks, IdSocketHandle, IdIOHandler, IdException; type TIdIOHandlerSocket = class(TIdIOHandler) protected FBinding: TIdSocketHandle; FUseNagle: boolean; FSocksInfo: TIdSocksInfo; procedure SetSocksInfo(ASocks: TIdSocksInfo); function GetSocksInfo: TIdSocksInfo; procedure SetUseNagle(AValue: Boolean); procedure SetNagleOpt(AEnabled: Boolean); procedure Notification(AComponent: TComponent; Operation: TOperation); override; public procedure Close; override; procedure ConnectClient(const AHost: string; const APort: Integer; const ABoundIP: string; const ABoundPort: Integer; const ABoundPortMin: Integer; const ABoundPortMax: Integer; const ATimeout: Integer = IdTimeoutDefault); override; function Connected: Boolean; override; constructor Create(AOwner: TComponent); override; destructor Destroy; override; procedure Open; override; function Readable(AMSec: integer = IdTimeoutDefault): boolean; override; function Recv(var ABuf; ALen: integer): integer; override; function Send(var ABuf; ALen: integer): integer; override; // property Binding: TIdSocketHandle read FBinding; published property SocksInfo: TIdSocksInfo read GetSocksInfo write SetSocksInfo; property UseNagle: boolean read FUseNagle write SetUseNagle default True; end; implementation uses IdAntiFreezeBase, IdStackConsts, IdResourceStrings, IdStack, IdTCPConnection, IdComponent, SysUtils; type TIdConnectThread = class(TThread) protected FBinding: TIdSocketHandle; FExceptionMessage: string; FLastSocketError: Integer; public procedure Execute; override; end; { TIdIOHandlerSocket } procedure TIdIOHandlerSocket.Close; begin inherited Close; if Assigned(FBinding) then begin FBinding.CloseSocket; end; end; procedure TIdIOHandlerSocket.ConnectClient(const AHost: string; const APort: Integer; const ABoundIP: string; const ABoundPort, ABoundPortMin, ABoundPortMax: Integer; const ATimeout: Integer = IdTimeoutDefault); procedure ConnectTimeout(ATimeout: Integer); var LSleepTime: Integer; LInfinite: Boolean; begin 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 Length(FExceptionMessage) > 0 then begin if FLastSocketError <> 0 then begin raise EIdSocketError.CreateError(FLastSocketError, FExceptionMessage); end else begin raise EIdConnectException.Create(FExceptionMessage); end; end; end else begin Terminate; Close; WaitFor; raise EIdConnectTimeout.Create(RSConnectTimeout); end; finally Free; end; end; Var LHost: String; LPort: Integer; begin // Socks support if SocksInfo.Version in [svSocks4, svSocks4A, svSocks5] then begin LHost := SocksInfo.Host; LPort := SocksInfo.Port; end else begin LHost := AHost; LPort := APort; end; inherited ConnectClient(LHost, LPort, ABoundIP, ABoundPort, ABoundPortMin, ABoundPortMax, ATimeout); with Binding do begin AllocateSocket; IP := ABoundIP; Port := ABoundPort; ClientPortMin := ABoundPortMin; ClientPortMax := ABoundPortMax; Bind; end; if not GStack.IsIP(LHost) then begin DoStatus(hsResolving, [LHost]); end; // Tell the binding what its destination is Binding.SetPeer(GStack.ResolveHost(LHost), LPort); SetNagleOpt(UseNagle); // Connect DoStatus(hsConnecting, [Binding.PeerIP]); if (ATimeout = IdTimeoutDefault) or (ATimeout = 0) then begin if TIdAntiFreezeBase.ShouldUse then begin ConnectTimeout(120000); // 2 Min end else begin GStack.CheckForSocketError(Binding.Connect); end; end else begin ConnectTimeout(ATimeout); end; SocksInfo.MakeSocksConnection(AHost, APort); end; function TIdIOHandlerSocket.Connected: Boolean; begin Result := FBinding <> nil; if Result then begin Result := FBinding.HandleAllocated; end; end; constructor TIdIOHandlerSocket.Create(AOwner: TComponent); begin inherited Create(AOwner); FUseNagle := True; end; procedure TIdIOHandlerSocket.Open; begin inherited Open; if not Assigned(FBinding) then begin FBinding := TIdSocketHandle.Create(nil); end else FBinding.Reset(true); end; function TIdIOHandlerSocket.Readable(AMSec: integer): boolean; begin Result := Binding.Readable(AMSec); end; function TIdIOHandlerSocket.Recv(var ABuf; ALen: integer): integer; begin if Connected then begin Result := Binding.Recv(ABuf, ALen, 0); end else begin raise EIdClosedSocket.Create(RSStatusDisconnected); end; end; function TIdIOHandlerSocket.Send(var ABuf; ALen: integer): integer; begin if Connected then begin Result := Binding.Send(ABuf, ALen, 0); end else begin raise EIdClosedSocket.Create(RSStatusDisconnected); end; end; procedure TIdIOHandlerSocket.SetSocksInfo(ASocks: TIdSocksInfo); begin // All this is to preserve the compatibility with old version // In the case when we have SocksInfo as object created in runtime without owner form it is treated as temporary object // In the case when the ASocks points to an object with owner it is treated as component on form. if Assigned(ASocks) then begin if not Assigned(ASocks.Owner) then begin if Assigned(SocksInfo.Owner) then begin FSocksInfo := nil; end; SocksInfo.Assign(ASocks); // This will construct the default SocksInfo end else begin if Assigned(FSocksInfo) then begin if not Assigned(FSocksInfo.Owner) then begin FreeAndNil(FSocksInfo); end; end; FSocksInfo := ASocks; FSocksInfo.FreeNotification(self); end; FSocksInfo.IOHandler := Self; end else begin FSocksInfo := ASocks; end; end; function TIdIOHandlerSocket.GetSocksInfo: TIdSocksInfo; begin { Note that we didn't create the Socks Object at design-time for some reason but I forgot what that reason was. If this introduces unintended consequence, this patch ill be removed and things may have to be reworked. } // if (not (csDesigning in ComponentState)) and (not Assigned(FSocksInfo)) then begin if (not Assigned(FSocksInfo)) then begin FSocksInfo := TIdSocksInfo.Create(nil); end; result := FSocksInfo; end; destructor TIdIOHandlerSocket.Destroy; begin if Assigned(FSocksInfo) then begin if FSocksInfo.Owner = nil then begin FreeAndNil(FSocksInfo); end; end; FreeAndNil(FBinding); inherited Destroy; end; procedure TIdIOHandlerSocket.SetUseNagle(AValue: Boolean); begin if FUseNagle <> AValue then begin FUseNagle := AValue; SetNagleOpt(FUseNagle); end; end; procedure TIdIOHandlerSocket.SetNagleOpt(AEnabled: Boolean); const Options: array[Boolean] of Integer = (1, 0); begin if Connected then begin Binding.SetSockOpt(Id_IPPROTO_TCP, Id_TCP_NODELAY, PChar(@Options[AEnabled]), SizeOf(Options[AEnabled])); end; end; procedure TIdIOHandlerSocket.Notification(AComponent: TComponent; Operation: TOperation); begin inherited Notification(AComponent, OPeration); if (Operation = opRemove) then begin if (AComponent = FSocksInfo) then begin FSocksInfo := nil; end; end; end; { TIdConnectThread } procedure TIdConnectThread.Execute; begin try // Id_WSAEBADF (9) on Linux, Id_WSAENOTSOCK (10038) on Windows GStack.CheckForSocketError(FBinding.Connect, [Id_WSAEBADF, Id_WSAENOTSOCK]); 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; end.