IdIOHandlerStack.pas 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. {
  2. $Project$
  3. $Workfile$
  4. $Revision$
  5. $DateUTC$
  6. $Id$
  7. This file is part of the Indy (Internet Direct) project, and is offered
  8. under the dual-licensing agreement described on the Indy website.
  9. (http://www.indyproject.org/)
  10. Copyright:
  11. (c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved.
  12. }
  13. {
  14. $Log$
  15. }
  16. {
  17. Rev 1.53 3/10/05 3:23:16 PM RLebeau
  18. Updated WriteDirect() to access the Intercept property directly.
  19. Rev 1.52 11/12/2004 11:30:16 AM JPMugaas
  20. Expansions for IPv6.
  21. Rev 1.51 11/11/04 12:03:46 PM RLebeau
  22. Updated DoConnectTimeout() to recognize IdTimeoutDefault
  23. Rev 1.50 6/18/04 1:06:58 PM RLebeau
  24. Bug fix for ReadTimeout property
  25. Rev 1.49 5/4/2004 9:57:34 AM JPMugaas
  26. Removed some old uncommented code and reenabled some TransparentProxy code
  27. since it compile in DotNET.
  28. Rev 1.48 2004.04.18 12:52:02 AM czhower
  29. Big bug fix with server disconnect and several other bug fixed that I found
  30. along the way.
  31. Rev 1.47 2004.04.08 3:56:34 PM czhower
  32. Fixed bug with Intercept byte count. Also removed Bytes from Buffer.
  33. Rev 1.46 2004.03.12 8:01:00 PM czhower
  34. Exception update
  35. Rev 1.45 2004.03.07 11:48:42 AM czhower
  36. Flushbuffer fix + other minor ones found
  37. Rev 1.44 2004.03.01 5:12:32 PM czhower
  38. -Bug fix for shutdown of servers when connections still existed (AV)
  39. -Implicit HELP support in CMDserver
  40. -Several command handler bugs
  41. -Additional command handler functionality.
  42. Rev 1.43 2/21/04 9:25:50 PM RLebeau
  43. Fix for BBG #66
  44. Added FLastSocketError member to TIdConnectThread
  45. Rev 1.42 2004.02.03 4:16:48 PM czhower
  46. For unit name changes.
  47. Rev 1.41 12/31/2003 9:51:56 PM BGooijen
  48. Added IPv6 support
  49. Rev 1.40 2003.12.28 1:05:58 PM czhower
  50. .Net changes.
  51. Rev 1.39 11/21/2003 12:05:18 AM BGooijen
  52. Terminated isn't public in TThread any more, made it public here now
  53. Rev 1.38 10/28/2003 9:15:44 PM BGooijen
  54. .net
  55. Rev 1.37 10/18/2003 1:42:46 PM BGooijen
  56. Added include
  57. Rev 1.36 2003.10.14 1:26:56 PM czhower
  58. Uupdates + Intercept support
  59. Rev 1.35 2003.10.11 5:48:36 PM czhower
  60. -VCL fixes for servers
  61. -Chain suport for servers (Super core)
  62. -Scheduler upgrades
  63. -Full yarn support
  64. Rev 1.34 10/9/2003 8:09:10 PM SPerry
  65. bug fixes
  66. Rev 1.33 10/5/2003 11:02:36 PM BGooijen
  67. Write buffering
  68. Rev 1.32 05/10/2003 23:01:02 HHariri
  69. Fix for connect problem when IP address specified as opposed to host
  70. Rev 1.31 2003.10.02 8:23:42 PM czhower
  71. DotNet Excludes
  72. Rev 1.30 2003.10.02 10:16:28 AM czhower
  73. .Net
  74. Rev 1.29 2003.10.01 9:11:18 PM czhower
  75. .Net
  76. Rev 1.28 2003.10.01 5:05:14 PM czhower
  77. .Net
  78. Rev 1.27 2003.10.01 2:46:38 PM czhower
  79. .Net
  80. Rev 1.26 2003.10.01 2:30:38 PM czhower
  81. .Net
  82. Rev 1.22 10/1/2003 12:14:14 AM BGooijen
  83. DotNet: removing CheckForSocketError
  84. Rev 1.21 2003.10.01 1:37:34 AM czhower
  85. .Net
  86. Rev 1.19 2003.09.30 1:22:58 PM czhower
  87. Stack split for DotNet
  88. Rev 1.18 2003.07.14 1:57:22 PM czhower
  89. -First set of IOCP fixes.
  90. -Fixed a threadsafe problem with the stack class.
  91. Rev 1.17 2003.07.14 12:54:32 AM czhower
  92. Fixed graceful close detection if it occurs after connect.
  93. Rev 1.16 2003.07.10 4:34:58 PM czhower
  94. Fixed AV, added some new comments
  95. Rev 1.15 7/4/2003 08:26:46 AM JPMugaas
  96. Optimizations.
  97. Rev 1.14 7/1/2003 03:39:48 PM JPMugaas
  98. Started numeric IP function API calls for more efficiency.
  99. Rev 1.13 6/30/2003 10:25:18 AM BGooijen
  100. removed unnecessary assignment to FRecvBuffer.Size
  101. Rev 1.12 6/29/2003 10:56:28 PM BGooijen
  102. Removed .Memory from the buffer, and added some extra methods
  103. Rev 1.11 2003.06.25 4:28:32 PM czhower
  104. Formatting and fixed a short circuit clause.
  105. Rev 1.10 6/3/2003 11:43:52 PM BGooijen
  106. Elimintated some code
  107. Rev 1.9 4/16/2003 3:31:26 PM BGooijen
  108. Removed InternalCheckForDisconnect, added .Connected
  109. Rev 1.8 4/14/2003 11:44:20 AM BGooijen
  110. CheckForDisconnect calls ReadFromSource now
  111. Rev 1.7 4/2/2003 3:24:56 PM BGooijen
  112. Moved transparantproxy from ..stack to ..socket
  113. Rev 1.6 3/5/2003 11:04:32 PM BGooijen
  114. Fixed Intercept, but the part in WriteBuffer doesn't look really nice yet
  115. Rev 1.5 3/3/2003 11:31:58 PM BGooijen
  116. fixed stack overflow in .CheckForDisconnect
  117. Rev 1.4 2/26/2003 1:15:40 PM BGooijen
  118. FBinding is now freed in IdIOHandlerSocket, instead of in IdIOHandlerStack
  119. Rev 1.3 2003.02.25 1:36:12 AM czhower
  120. Rev 1.2 2002.12.06 11:49:34 PM czhower
  121. Rev 1.1 12-6-2002 20:10:18 BGooijen
  122. Added IPv6-support
  123. Rev 1.0 11/13/2002 08:45:16 AM JPMugaas
  124. }
  125. unit IdIOHandlerStack;
  126. interface
  127. {$i IdCompilerDefines.inc}
  128. uses
  129. Classes,
  130. IdGlobal, IdSocketHandle, IdIOHandlerSocket, IdExceptionCore, IdStack,
  131. SysUtils;
  132. type
  133. TIdIOHandlerStack = class(TIdIOHandlerSocket)
  134. protected
  135. procedure ConnectClient; override;
  136. function ReadDataFromSource(var VBuffer: TIdBytes): Integer; override;
  137. function WriteDataToTarget(const ABuffer: TIdBytes; const AOffset, ALength: Integer): Integer; override;
  138. public
  139. procedure CheckForDisconnect(ARaiseExceptionIfDisconnected: Boolean = True;
  140. AIgnoreBuffer: Boolean = False); override;
  141. function Connected: Boolean; override;
  142. function Readable(AMSec: Integer = IdTimeoutDefault): Boolean; override;
  143. published
  144. property ReadTimeout;
  145. end;
  146. implementation
  147. uses
  148. {$IFDEF USE_VCL_POSIX}
  149. Posix.SysSelect,
  150. Posix.SysTime,
  151. {$ENDIF}
  152. {$IFDEF WINDOWS}
  153. Windows,
  154. {$ENDIF}
  155. IdAntiFreezeBase, IdResourceStringsCore, IdResourceStrings, IdStackConsts, IdException,
  156. IdTCPConnection, IdComponent, IdIOHandler, IdCustomTransparentProxy;
  157. type
  158. TIdConnectThread = class(TThread)
  159. protected
  160. FBinding: TIdSocketHandle;
  161. {$IFDEF HAS_AcquireExceptionObject}
  162. FConnectException: TObject;
  163. {$ELSE}
  164. FLastSocketError: Integer;
  165. FExceptionMessage: string;
  166. {$ENDIF}
  167. FExceptionOccured: Boolean;
  168. procedure Execute; override;
  169. procedure DoTerminate; override;
  170. public
  171. constructor Create(ABinding: TIdSocketHandle); reintroduce;
  172. {$IFDEF HAS_AcquireExceptionObject}
  173. destructor Destroy; override;
  174. {$ENDIF}
  175. procedure CheckForConnectError;
  176. property Terminated;
  177. end;
  178. { TIdIOHandlerStack }
  179. function TIdIOHandlerStack.Connected: Boolean;
  180. begin
  181. ReadFromSource(False, 0, False);
  182. Result := inherited Connected;
  183. end;
  184. procedure TIdIOHandlerStack.ConnectClient;
  185. procedure DoConnectTimeout(ATimeout: Integer);
  186. var
  187. LSleepTime, LWaitTime: Integer;
  188. LThread: TIdConnectThread;
  189. begin
  190. // IndySleep
  191. if TIdAntiFreezeBase.ShouldUse then begin
  192. LSleepTime := IndyMin(GAntiFreeze.IdleTimeOut, 125);
  193. end else begin
  194. LSleepTime := 125;
  195. end;
  196. LThread := TIdConnectThread.Create(Binding);
  197. try
  198. if TIdAntiFreezeBase.ShouldUse then begin
  199. // TODO: we need to take the actual clock into account, not just
  200. // decrement by the sleep interval. If IndySleep() runs longer then
  201. // requested, that would slow down the loop and exceed the original
  202. // timeout that was requested...
  203. {
  204. Start := Ticks64;
  205. repeat
  206. while (GetElapsedTicks(Start) < ATimeout) and (not LThread.Terminated) do begin
  207. LWaitTime := IndyMin(ATimeout - GetElapsedTicks(Start), LSleepTime);
  208. if LWaitTime <= 0 then Break;
  209. ($IFDEF WINDOWS)
  210. if WaitForSingleObject(LThread.Handle, LWaitTime) <> WAIT_TIMEOUT then begin
  211. Break;
  212. end;
  213. ($ELSE)
  214. // TODO: figure out what else can be used here...
  215. IndySleep(LWaitTime);
  216. ($ENDIF)
  217. TIdAntiFreezeBase.DoProcess;
  218. end;
  219. end;
  220. }
  221. while (ATimeout > 0) and (not LThread.Terminated) do begin
  222. LWaitTime := IndyMin(ATimeout, LSleepTime);
  223. {$IFDEF WINDOWS}
  224. if WaitForSingleObject(LThread.Handle, LWaitTime) <> WAIT_TIMEOUT then begin
  225. Break;
  226. end;
  227. {$ELSE}
  228. // TODO: figure out what else can be used here...
  229. IndySleep(LWaitTime);
  230. {$ENDIF}
  231. TIdAntiFreezeBase.DoProcess;
  232. Dec(ATimeout, LWaitTime);
  233. end;
  234. end else begin
  235. {$IFDEF WINDOWS}
  236. WaitForSingleObject(LThread.Handle, ATimeout);
  237. {$ELSE}
  238. // TODO: figure out what else can be used here...
  239. while (ATimeout > 0) and (not LThread.Terminated) do begin
  240. LWaitTime := IndyMin(ATimeout, LSleepTime);
  241. IndySleep(LWaitTime);
  242. Dec(ATimeout, LWaitTime);
  243. end;
  244. {$ENDIF}
  245. end;
  246. if LThread.Terminated then begin
  247. LThread.CheckForConnectError;
  248. end else begin
  249. LThread.Terminate;
  250. // TODO: before closing, maybe enable SO_DONTLINGER, or SO_LINGER with a 0 timeout...
  251. //Binding.SetSockOpt(Id_SOL_SOCKET, Id_SO_DONTLINGER, 1);
  252. {
  253. var l: linger;
  254. l.l_onoff := 1;
  255. l.l_linger := 0;
  256. Binding.SetSockOpt(Id_SOL_SOCKET, Id_SO_LINGER, Integer(@l));
  257. }
  258. Close;
  259. LThread.WaitFor;
  260. raise EIdConnectTimeout.Create(RSConnectTimeout);
  261. end;
  262. finally
  263. LThread.Free;
  264. end;
  265. end;
  266. var
  267. LHost: String;
  268. LPort: Integer;
  269. LIP: string;
  270. LIPVersion : TIdIPVersion;
  271. // under ARC, convert a weak reference to a strong reference before working with it
  272. LProxy: TIdCustomTransparentProxy;
  273. LTimeout: Integer;
  274. begin
  275. inherited ConnectClient;
  276. LProxy := FTransparentProxy;
  277. if Assigned(LProxy) then begin
  278. if LProxy.Enabled then begin
  279. LHost := LProxy.Host;
  280. LPort := LProxy.Port;
  281. LIPVersion := LProxy.IPVersion;
  282. end else begin
  283. LHost := Host;
  284. LPort := Port;
  285. LIPVersion := IPVersion;
  286. end;
  287. end else begin
  288. LHost := Host;
  289. LPort := Port;
  290. LIPVersion := IPVersion;
  291. end;
  292. if LIPVersion = Id_IPv4 then
  293. begin
  294. if not GStack.IsIP(LHost) then begin
  295. if Assigned(OnStatus) then begin
  296. DoStatus(hsResolving, [LHost]);
  297. end;
  298. LIP := GStack.ResolveHost(LHost, LIPVersion);
  299. end else begin
  300. LIP := LHost;
  301. end;
  302. end
  303. else
  304. begin //IPv6
  305. LIP := MakeCanonicalIPv6Address(LHost);
  306. if LIP='' then begin //if MakeCanonicalIPv6Address failed, we have a hostname
  307. if Assigned(OnStatus) then begin
  308. DoStatus(hsResolving, [LHost]);
  309. end;
  310. LIP := GStack.ResolveHost(LHost, LIPVersion);
  311. end else begin
  312. LIP := LHost;
  313. end;
  314. end;
  315. Binding.SetPeer(LIP, LPort, LIPVersion);
  316. // Connect
  317. //note for status events, we check specifically for them here
  318. //so we don't do a string conversion in Binding.PeerIP.
  319. if Assigned(OnStatus) then begin
  320. DoStatus(hsConnecting, [Binding.PeerIP]);
  321. end;
  322. LTimeout := ConnectTimeout;
  323. if (LTimeout = IdTimeoutDefault) or (LTimeout = 0) then begin
  324. LTimeout := IdTimeoutInfinite;
  325. end;
  326. if LTimeout = IdTimeoutInfinite then begin
  327. if TIdAntiFreezeBase.ShouldUse then begin
  328. DoConnectTimeout(120000); // 2 Min
  329. end else begin
  330. Binding.Connect;
  331. end;
  332. end else begin
  333. DoConnectTimeout(LTimeout);
  334. end;
  335. if Assigned(LProxy) then begin
  336. if LProxy.Enabled then begin
  337. LProxy.Connect(Self, Host, Port, IPVersion);
  338. end;
  339. end;
  340. end;
  341. function TIdIOHandlerStack.Readable(AMSec: integer): boolean;
  342. begin
  343. Result := Binding.Readable(AMSec);
  344. end;
  345. function TIdIOHandlerStack.WriteDataToTarget(const ABuffer: TIdBytes; const AOffset, ALength: Integer): Integer;
  346. begin
  347. Assert(Binding<>nil);
  348. Result := Binding.Send(ABuffer, AOffset, ALength);
  349. end;
  350. // Reads any data in tcp/ip buffer and puts it into Indy buffer
  351. // This must be the ONLY raw read from Winsock routine
  352. // This must be the ONLY call to RECV - all data goes thru this method
  353. function TIdIOHandlerStack.ReadDataFromSource(var VBuffer: TIdBytes): Integer;
  354. begin
  355. Assert(Binding<>nil);
  356. Result := Binding.Receive(VBuffer);
  357. end;
  358. procedure TIdIOHandlerStack.CheckForDisconnect(
  359. ARaiseExceptionIfDisconnected: Boolean; AIgnoreBuffer: Boolean);
  360. var
  361. LDisconnected: Boolean;
  362. begin
  363. // ClosedGracefully // Server disconnected
  364. // IOHandler = nil // Client disconnected
  365. if ClosedGracefully then begin
  366. if BindingAllocated then begin
  367. Close;
  368. // Call event handlers to inform the user that we were disconnected
  369. DoStatus(hsDisconnected);
  370. //DoOnDisconnected;
  371. end;
  372. LDisconnected := True;
  373. end else begin
  374. LDisconnected := not BindingAllocated;
  375. end;
  376. // Do not raise unless all data has been read by the user
  377. if LDisconnected then begin
  378. if (InputBufferIsEmpty or AIgnoreBuffer) and ARaiseExceptionIfDisconnected then begin
  379. RaiseConnClosedGracefully;
  380. end;
  381. end;
  382. end;
  383. { TIdConnectThread }
  384. constructor TIdConnectThread.Create(ABinding: TIdSocketHandle);
  385. begin
  386. FBinding := ABinding;
  387. inherited Create(False);
  388. end;
  389. {$IFDEF HAS_AcquireExceptionObject}
  390. destructor TIdConnectThread.Destroy;
  391. begin
  392. FConnectException.Free;
  393. inherited;
  394. end;
  395. {$ENDIF}
  396. procedure TIdConnectThread.Execute;
  397. begin
  398. try
  399. FBinding.Connect;
  400. except
  401. {$IFDEF HAS_AcquireExceptionObject}
  402. // TThread has a FatalException property, but we can't take ownership of it
  403. // so we can re-raise it, so using AcquireExceptionObject() instead to take
  404. // ownership of the exception before it can be assigned to FatalException...
  405. FExceptionOccured := True;
  406. FConnectException := AcquireExceptionObject;
  407. {$ELSE}
  408. on E: Exception do begin
  409. FExceptionOccured := True;
  410. FExceptionMessage := E.Message;
  411. if E is EIdSocketError then begin
  412. if (EIdSocketError(E).LastError <> Id_WSAEBADF) and (EIdSocketError(E).LastError <> Id_WSAENOTSOCK) then begin
  413. FLastSocketError := EIdSocketError(E).LastError;
  414. end;
  415. end;
  416. end;
  417. {$ENDIF}
  418. end;
  419. end;
  420. procedure TIdConnectThread.DoTerminate;
  421. begin
  422. // Necessary as caller checks this
  423. Terminate;
  424. inherited;
  425. end;
  426. procedure TIdConnectThread.CheckForConnectError;
  427. var
  428. LException: TObject;
  429. begin
  430. if FExceptionOccured then begin
  431. {$IFDEF HAS_AcquireExceptionObject}
  432. LException := FConnectException;
  433. FConnectException := nil;
  434. if LException = nil then begin
  435. LException := EIdConnectException.Create(''); // TODO
  436. end;
  437. {$ELSE}
  438. if FLastSocketError <> 0 then begin
  439. LException := EIdSocketError.CreateError(FLastSocketError, FExceptionMessage);
  440. end else begin
  441. LException := EIdConnectException.Create(FExceptionMessage);
  442. end;
  443. {$ENDIF}
  444. raise LException;
  445. end;
  446. end;
  447. initialization
  448. TIdIOHandlerStack.SetDefaultClass;
  449. end.