IdIOHandlerStack.pas 15 KB

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