IdIOHandlerStack.pas 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520
  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. {$IF (NOT DEFINED(USE_VCL_POSIX) AND DEFINED(WINDOWS)}
  148. {.$DEFINE SUPPORTS_NON_BLOCKING}
  149. {$IFEND}
  150. uses
  151. {$IF DEFINED(USE_VCL_POSIX)}
  152. Posix.SysSelect,
  153. Posix.SysTime,
  154. {$ELSEIF DEFINED(WINDOWS)}
  155. Windows,
  156. {$IFEND}
  157. IdAntiFreezeBase, IdResourceStringsCore, IdStackConsts, IdException,
  158. IdComponent, IdIOHandler, IdCustomTransparentProxy;
  159. type
  160. TIdConnectThread = class(TThread)
  161. protected
  162. FBinding: TIdSocketHandle;
  163. FConnectException: TObject;
  164. FExceptionOccured: Boolean;
  165. procedure Execute; override;
  166. procedure DoTerminate; override;
  167. public
  168. constructor Create(ABinding: TIdSocketHandle); reintroduce;
  169. destructor Destroy; override;
  170. procedure CheckForConnectError;
  171. property Terminated;
  172. end;
  173. { TIdIOHandlerStack }
  174. function TIdIOHandlerStack.Connected: Boolean;
  175. begin
  176. try
  177. ReadFromSource(False, 0, False);
  178. Result := inherited Connected;
  179. except
  180. on E: EIdSocketError do begin
  181. if not ((E.LastError = Id_WSAESHUTDOWN) or (E.LastError = Id_WSAECONNABORTED) or (E.LastError = Id_WSAECONNRESET)) then begin
  182. raise;
  183. end;
  184. Result := False;
  185. end;
  186. end;
  187. end;
  188. procedure TIdIOHandlerStack.ConnectClient;
  189. procedure DoConnectTimeout(ATimeout: Integer);
  190. var
  191. LSleepTime, LWaitTime: Integer;
  192. LThread: TIdConnectThread;
  193. begin
  194. // IndySleep
  195. if TIdAntiFreezeBase.ShouldUse then begin
  196. LSleepTime := IndyMin(GAntiFreeze.IdleTimeOut, 125);
  197. end else begin
  198. LSleepTime := 125;
  199. end;
  200. LThread := TIdConnectThread.Create(Binding);
  201. try
  202. if TIdAntiFreezeBase.ShouldUse then begin
  203. // TODO: we need to take the actual clock into account, not just
  204. // decrement by the sleep interval. If IndySleep() runs longer then
  205. // requested, that would slow down the loop and exceed the original
  206. // timeout that was requested...
  207. {
  208. Start := Ticks64;
  209. repeat
  210. while (GetElapsedTicks(Start) < ATimeout) and (not LThread.Terminated) do begin
  211. LWaitTime := IndyMin(ATimeout - GetElapsedTicks(Start), LSleepTime);
  212. if LWaitTime <= 0 then Break;
  213. ($IFDEF WINDOWS)
  214. if WaitForSingleObject(LThread.Handle, LWaitTime) <> WAIT_TIMEOUT then begin
  215. Break;
  216. end;
  217. ($ELSE)
  218. // TODO: figure out what else can be used here...
  219. IndySleep(LWaitTime);
  220. ($ENDIF)
  221. TIdAntiFreezeBase.DoProcess;
  222. end;
  223. end;
  224. }
  225. while (ATimeout > 0) and (not LThread.Terminated) do begin
  226. LWaitTime := IndyMin(ATimeout, LSleepTime);
  227. {$IFDEF WINDOWS}
  228. if WaitForSingleObject(LThread.Handle, LWaitTime) <> WAIT_TIMEOUT then begin
  229. Break;
  230. end;
  231. {$ELSE}
  232. // TODO: figure out what else can be used here...
  233. IndySleep(LWaitTime);
  234. {$ENDIF}
  235. TIdAntiFreezeBase.DoProcess;
  236. Dec(ATimeout, LWaitTime);
  237. end;
  238. end else begin
  239. {$IFDEF WINDOWS}
  240. WaitForSingleObject(LThread.Handle, ATimeout);
  241. {$ELSE}
  242. // TODO: figure out what else can be used here...
  243. while (ATimeout > 0) and (not LThread.Terminated) do begin
  244. LWaitTime := IndyMin(ATimeout, LSleepTime);
  245. IndySleep(LWaitTime);
  246. Dec(ATimeout, LWaitTime);
  247. end;
  248. {$ENDIF}
  249. end;
  250. if LThread.Terminated then begin
  251. LThread.CheckForConnectError;
  252. end else begin
  253. LThread.Terminate;
  254. // TODO: before closing, maybe enable SO_DONTLINGER, or SO_LINGER with a 0 timeout...
  255. //Binding.SetSockOpt(Id_SOL_SOCKET, Id_SO_DONTLINGER, 1);
  256. {
  257. var l: linger;
  258. l.l_onoff := 1;
  259. l.l_linger := 0;
  260. Binding.SetSockOpt(Id_SOL_SOCKET, Id_SO_LINGER, Integer(@l));
  261. }
  262. Close;
  263. LThread.WaitFor;
  264. raise EIdConnectTimeout.Create(RSConnectTimeout);
  265. end;
  266. finally
  267. LThread.Free;
  268. end;
  269. end;
  270. var
  271. LHost: String;
  272. LPort: Integer;
  273. LIP: string;
  274. LIPVersion : TIdIPVersion;
  275. // under ARC, convert a weak reference to a strong reference before working with it
  276. LProxy: TIdCustomTransparentProxy;
  277. LTimeout: Integer;
  278. begin
  279. inherited ConnectClient;
  280. LProxy := FTransparentProxy;
  281. if Assigned(LProxy) then begin
  282. if LProxy.Enabled then begin
  283. LHost := LProxy.Host;
  284. LPort := LProxy.Port;
  285. LIPVersion := LProxy.IPVersion;
  286. end else begin
  287. LHost := Host;
  288. LPort := Port;
  289. LIPVersion := IPVersion;
  290. end;
  291. end else begin
  292. LHost := Host;
  293. LPort := Port;
  294. LIPVersion := IPVersion;
  295. end;
  296. // TODO: maybe remove these IP checks here and just call GStack.ResolveHost()
  297. // unconditionally? It performs the same IP checks internally. The downside
  298. // would be that OnStatus(hsResolving) would then be fired for all Host values,
  299. // even IP addresses. Maybe that is OK, though...
  300. if LIPVersion = Id_IPv4 then
  301. begin
  302. if not GStack.IsIP(LHost) then begin
  303. if Assigned(OnStatus) then begin
  304. DoStatus(hsResolving, [LHost]);
  305. end;
  306. LIP := GStack.ResolveHost(LHost, LIPVersion);
  307. end else begin
  308. LIP := LHost;
  309. end;
  310. end
  311. else
  312. begin //IPv6
  313. LIP := MakeCanonicalIPv6Address(LHost);
  314. if LIP='' then begin //if MakeCanonicalIPv6Address failed, we have a hostname
  315. if Assigned(OnStatus) then begin
  316. DoStatus(hsResolving, [LHost]);
  317. end;
  318. LIP := GStack.ResolveHost(LHost, LIPVersion);
  319. end else begin
  320. LIP := LHost;
  321. end;
  322. end;
  323. Binding.SetPeer(LIP, LPort, LIPVersion);
  324. // Connect
  325. //note for status events, we check specifically for them here
  326. //so we don't do a string conversion in Binding.PeerIP.
  327. if Assigned(OnStatus) then begin
  328. DoStatus(hsConnecting, [Binding.PeerIP]);
  329. end;
  330. LTimeout := ConnectTimeout;
  331. if (LTimeout = IdTimeoutDefault) or (LTimeout = 0) then begin
  332. LTimeout := IdTimeoutInfinite;
  333. end;
  334. if LTimeout = IdTimeoutInfinite then begin
  335. if TIdAntiFreezeBase.ShouldUse then begin
  336. DoConnectTimeout(120000); // 2 Min
  337. end else begin
  338. Binding.Connect;
  339. end;
  340. end else begin
  341. DoConnectTimeout(LTimeout);
  342. end;
  343. if Assigned(LProxy) then begin
  344. if LProxy.Enabled then begin
  345. LProxy.Connect(Self, Host, Port, IPVersion);
  346. end;
  347. end;
  348. end;
  349. function TIdIOHandlerStack.Readable(AMSec: integer): boolean;
  350. begin
  351. Result := Binding.Readable(AMSec);
  352. end;
  353. function TIdIOHandlerStack.WriteDataToTarget(const ABuffer: TIdBytes; const AOffset, ALength: Integer): Integer;
  354. begin
  355. Assert(Binding<>nil);
  356. Result := Binding.Send(ABuffer, AOffset, ALength);
  357. end;
  358. // Reads any data in tcp/ip buffer and puts it into Indy buffer
  359. // This must be the ONLY raw read from Winsock routine
  360. // This must be the ONLY call to RECV - all data goes thru this method
  361. function TIdIOHandlerStack.ReadDataFromSource(var VBuffer: TIdBytes): Integer;
  362. begin
  363. Assert(Binding<>nil);
  364. Result := Binding.Receive(VBuffer);
  365. end;
  366. procedure TIdIOHandlerStack.CheckForDisconnect(
  367. ARaiseExceptionIfDisconnected: Boolean; AIgnoreBuffer: Boolean);
  368. var
  369. LDisconnected: Boolean;
  370. begin
  371. // ClosedGracefully // Server disconnected
  372. // IOHandler = nil // Client disconnected
  373. if ClosedGracefully then begin
  374. if BindingAllocated then begin
  375. Close;
  376. // Call event handlers to inform the user that we were disconnected
  377. DoStatus(hsDisconnected);
  378. //DoOnDisconnected;
  379. end;
  380. LDisconnected := True;
  381. end else begin
  382. LDisconnected := not BindingAllocated;
  383. end;
  384. // Do not raise unless all data has been read by the user
  385. if LDisconnected then begin
  386. if (InputBufferIsEmpty or AIgnoreBuffer) and ARaiseExceptionIfDisconnected then begin
  387. RaiseConnClosedGracefully;
  388. end;
  389. end;
  390. end;
  391. { TIdConnectThread }
  392. constructor TIdConnectThread.Create(ABinding: TIdSocketHandle);
  393. begin
  394. FBinding := ABinding;
  395. inherited Create(False);
  396. end;
  397. destructor TIdConnectThread.Destroy;
  398. begin
  399. FConnectException.Free;
  400. inherited;
  401. end;
  402. procedure TIdConnectThread.Execute;
  403. begin
  404. try
  405. FBinding.Connect;
  406. except
  407. // TThread has a FatalException property, but we can't take ownership of it
  408. // so we can re-raise it, so using AcquireExceptionObject() instead to take
  409. // ownership of the exception before it can be assigned to FatalException...
  410. FExceptionOccured := True;
  411. FConnectException := AcquireExceptionObject;
  412. end;
  413. end;
  414. procedure TIdConnectThread.DoTerminate;
  415. begin
  416. // Necessary as caller checks this
  417. Terminate;
  418. inherited;
  419. end;
  420. procedure TIdConnectThread.CheckForConnectError;
  421. var
  422. LException: TObject;
  423. begin
  424. if FExceptionOccured then begin
  425. LException := FConnectException;
  426. FConnectException := nil;
  427. if LException = nil then begin
  428. LException := EIdConnectException.Create(''); // TODO
  429. end;
  430. raise LException;
  431. end;
  432. end;
  433. initialization
  434. TIdIOHandlerStack.SetDefaultClass;
  435. end.