IdUDPServer.pas 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  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.14 11/12/2004 3:44:00 PM JPMugaas
  18. Compiler error fix. OOPPS!!!
  19. Rev 1.13 11/12/2004 11:30:20 AM JPMugaas
  20. Expansions for IPv6.
  21. Rev 1.12 6/11/2004 11:48:34 PM JPMugaas
  22. Fix for mistake I made. UDPReceive should have been UDPException
  23. Rev 1.11 6/11/2004 4:05:34 PM JPMugaas
  24. RecvFrom should now work in the UDP server with IPv6.
  25. An OnException event was added for logging purposes.
  26. Rev 1.10 09/06/2004 00:25:32 CCostelloe
  27. Kylix 3 patch
  28. Rev 1.9 2004.02.03 4:17:02 PM czhower
  29. For unit name changes.
  30. Rev 1.8 2004.01.20 10:03:40 PM czhower
  31. InitComponent
  32. Rev 1.7 2003.12.31 8:03:36 PM czhower
  33. Matched visibility
  34. Rev 1.6 10/26/2003 6:01:44 PM BGooijen
  35. Fixed binding problem
  36. Rev 1.5 10/24/2003 5:18:38 PM BGooijen
  37. Removed boolean shortcutting from .GetActive
  38. Rev 1.4 10/22/2003 04:41:02 PM JPMugaas
  39. Should compile with some restored functionality. Still not finished.
  40. Rev 1.3 2003.10.11 9:58:50 PM czhower
  41. Started on some todos
  42. Rev 1.2 2003.10.11 5:52:18 PM czhower
  43. -VCL fixes for servers
  44. -Chain suport for servers (Super core)
  45. -Scheduler upgrades
  46. -Full yarn support
  47. Rev 1.1 2003.09.30 1:23:10 PM czhower
  48. Stack split for DotNet
  49. Rev 1.0 11/13/2002 09:02:30 AM JPMugaas
  50. }
  51. unit IdUDPServer;
  52. interface
  53. {$I IdCompilerDefines.inc}
  54. //Put FPC into Delphi mode
  55. uses
  56. Classes,
  57. {$IFDEF HAS_UNIT_Generics_Collections}
  58. System.Generics.Collections,
  59. {$ENDIF}
  60. IdComponent,
  61. IdException,
  62. IdGlobal,
  63. IdSocketHandle,
  64. IdStackConsts,
  65. IdThread,
  66. IdUDPBase,
  67. IdStack;
  68. type
  69. TIdUDPServer = class;
  70. TIdUDPListenerThread = class(TIdThread)
  71. protected
  72. FBinding: TIdSocketHandle;
  73. FAcceptWait: Integer;
  74. FBuffer: TIdBytes;
  75. FCurrentException: String;
  76. FCurrentExceptionClass: TClass;
  77. // When ARC is enabled, object references MUST be valid objects.
  78. // It is common for users to store non-object values, though, so
  79. // we will provide separate properties for those purpose
  80. FDataObject: TObject;
  81. FDataValue: PtrInt;
  82. //
  83. {$IF DEFINED(HAS_UNSAFE_OBJECT_REF)}[Unsafe]
  84. {$ELSEIF DEFINED(HAS_WEAK_OBJECT_REF)}[Weak]
  85. {$IFEND} FServer: TIdUDPServer;
  86. //
  87. procedure AfterRun; override;
  88. procedure Run; override;
  89. public
  90. //
  91. //[Error] IdUDPServer.pas(266): E2391 Potentially polymorphic constructor calls must be virtual
  92. constructor Create(AOwner: TIdUDPServer; ABinding: TIdSocketHandle); reintroduce; virtual;
  93. destructor Destroy; override;
  94. //
  95. procedure UDPRead;
  96. procedure UDPException;
  97. //
  98. property AcceptWait: integer read FAcceptWait write FAcceptWait;
  99. property Binding: TIdSocketHandle read FBinding;
  100. property Server: TIdUDPServer read FServer;
  101. property DataObject: TObject read FDataObject write FDataObject;
  102. property DataValue: PtrInt read FDataValue write FDataValue;
  103. {$IFNDEF USE_OBJECT_ARC}
  104. property Data: TObject read FDataObject write FDataObject; // deprecated 'Use DataObject or DataValue property.';
  105. {$ENDIF}
  106. end;
  107. // TODO: use TIdThreadSafeObjectList instead?
  108. {$IFDEF HAS_GENERICS_TThreadList}
  109. TIdUDPListenerThreadList = TThreadList<TIdUDPListenerThread>;
  110. TIdUDPListenerList = TList<TIdUDPListenerThread>;
  111. {$ELSE}
  112. // TODO: flesh out TThreadList<TIdUDPListenerThread> and TList<TIdUDPListenerThread> for non-Generics compilers...
  113. TIdUDPListenerThreadList = TThreadList;
  114. TIdUDPListenerList = TList;
  115. {$ENDIF}
  116. TIdUDPListenerThreadClass = class of TIdUDPListenerThread;
  117. //Exception is used instead of EIdException because the exception could be from somewhere else
  118. TIdUDPExceptionEvent = procedure(AThread: TIdUDPListenerThread; ABinding: TIdSocketHandle; const AMessage : String; const AExceptionClass : TClass) of object;
  119. TUDPReadEvent = procedure(AThread: TIdUDPListenerThread; const AData: TIdBytes; ABinding: TIdSocketHandle) of object;
  120. TIdUDPServer = class(TIdUDPBase)
  121. protected
  122. FBindings: TIdSocketHandles;
  123. FCurrentBinding: TIdSocketHandle;
  124. FListenerThreads: TIdUDPListenerThreadList;
  125. FThreadClass: TIdUDPListenerThreadClass;
  126. FThreadedEvent: boolean;
  127. //
  128. FOnBeforeBind: TIdSocketHandleEvent;
  129. FOnAfterBind: TNotifyEvent;
  130. FOnUDPRead: TUDPReadEvent;
  131. FOnUDPException : TIdUDPExceptionEvent;
  132. //
  133. procedure BroadcastEnabledChanged; override;
  134. procedure CloseBinding; override;
  135. procedure DoBeforeBind(AHandle: TIdSocketHandle); virtual;
  136. procedure DoAfterBind; virtual;
  137. procedure DoOnUDPException(AThread: TIdUDPListenerThread; ABinding: TIdSocketHandle; const AMessage : String; const AExceptionClass : TClass); virtual;
  138. procedure DoUDPRead(AThread: TIdUDPListenerThread; const AData: TIdBytes; ABinding: TIdSocketHandle); virtual;
  139. function GetActive: Boolean; override;
  140. function GetBinding: TIdSocketHandle; override;
  141. function GetDefaultPort: TIdPort;
  142. procedure SetBindings(const Value: TIdSocketHandles);
  143. procedure SetDefaultPort(const AValue: TIdPort);
  144. public
  145. constructor Create(AOwner: TComponent); override;
  146. destructor Destroy; override;
  147. property ThreadClass: TIdUDPListenerThreadClass read FThreadClass write FThreadClass;
  148. published
  149. property Bindings: TIdSocketHandles read FBindings write SetBindings;
  150. property DefaultPort: TIdPort read GetDefaultPort write SetDefaultPort;
  151. property ReuseSocket;
  152. property ThreadedEvent: boolean read FThreadedEvent write FThreadedEvent default False;
  153. //
  154. property OnBeforeBind: TIdSocketHandleEvent read FOnBeforeBind write FOnBeforeBind;
  155. property OnAfterBind: TNotifyEvent read FOnAfterBind write FOnAfterBind;
  156. property OnUDPRead: TUDPReadEvent read FOnUDPRead write FOnUDPRead;
  157. property OnUDPException : TIdUDPExceptionEvent read FOnUDPException write FOnUDPException;
  158. end;
  159. EIdUDPServerException = class(EIdUDPException);
  160. implementation
  161. uses
  162. {$IF DEFINED(WINDOWS) AND DEFINED(DCC_2010_OR_ABOVE)}
  163. Windows,
  164. {$IFEND}
  165. IdGlobalCore, SysUtils;
  166. constructor TIdUDPServer.Create(AOwner: TComponent);
  167. begin
  168. inherited Create(Owner);
  169. FBindings := TIdSocketHandles.Create(Self);
  170. FListenerThreads := TIdUDPListenerThreadList.Create;
  171. FThreadClass := TIdUDPListenerThread;
  172. end;
  173. destructor TIdUDPServer.Destroy;
  174. begin
  175. Active := False;
  176. FBindings.Free;
  177. FListenerThreads.Free;
  178. inherited Destroy;
  179. end;
  180. procedure TIdUDPServer.BroadcastEnabledChanged;
  181. var
  182. i: Integer;
  183. begin
  184. if Assigned(FCurrentBinding) then begin
  185. for i := 0 to Bindings.Count - 1 do begin
  186. Bindings[i].BroadcastEnabled := BroadcastEnabled;
  187. end;
  188. end;
  189. end;
  190. procedure TIdUDPServer.CloseBinding;
  191. var
  192. LListenerThreads: TIdUDPListenerList;
  193. LListener: TIdUDPListenerThread;
  194. begin
  195. // RLebeau 2/17/2006: TIdUDPBase.Destroy() calls CloseBinding()
  196. if Assigned(FListenerThreads) then
  197. begin
  198. LListenerThreads := FListenerThreads.LockList;
  199. try
  200. while LListenerThreads.Count > 0 do
  201. begin
  202. LListener := {$IFDEF HAS_GENERICS_TThreadList}LListenerThreads[0]{$ELSE}TIdUDPListenerThread(LListenerThreads[0]){$ENDIF};
  203. // Stop listening
  204. LListener.Stop;
  205. LListener.Binding.CloseSocket;
  206. // Tear down Listener thread
  207. LListener.WaitFor;
  208. LListener.Free;
  209. LListenerThreads.Delete(0); // RLebeau 2/17/2006
  210. end;
  211. finally
  212. FListenerThreads.UnlockList;
  213. end;
  214. end;
  215. FCurrentBinding := nil;
  216. end;
  217. procedure TIdUDPServer.DoBeforeBind(AHandle: TIdSocketHandle);
  218. begin
  219. if Assigned(FOnBeforeBind) then begin
  220. FOnBeforeBind(AHandle);
  221. end;
  222. end;
  223. procedure TIdUDPServer.DoAfterBind;
  224. begin
  225. if Assigned(FOnAfterBind) then begin
  226. FOnAfterBind(Self);
  227. end;
  228. end;
  229. procedure TIdUDPServer.DoOnUDPException(AThread: TIdUDPListenerThread; ABinding: TIdSocketHandle; const AMessage : String; const AExceptionClass : TClass);
  230. begin
  231. if Assigned(FOnUDPException) then begin
  232. OnUDPException(AThread, ABinding, AMessage, AExceptionClass);
  233. end;
  234. end;
  235. procedure TIdUDPServer.DoUDPRead(AThread: TIdUDPListenerThread; const AData: TIdBytes; ABinding: TIdSocketHandle);
  236. begin
  237. if Assigned(OnUDPRead) then begin
  238. OnUDPRead(AThread, AData, ABinding);
  239. end;
  240. end;
  241. function TIdUDPServer.GetActive: Boolean;
  242. begin
  243. if IsDesignTime then begin
  244. // inherited GetActive keeps track of design-time Active property
  245. Result := inherited GetActive;
  246. end else begin
  247. Result := Assigned(FCurrentBinding);
  248. if Result then begin
  249. Result := FCurrentBinding.HandleAllocated;
  250. end;
  251. end;
  252. end;
  253. // Linux/Unix does not allow an IPv4 socket and an IPv6 socket
  254. // to listen on the same port at the same time! Windows does not
  255. // have that problem...
  256. {$IF DEFINED(LINUX) OR DEFINED(SOLARIS) OR DEFINED(ANDROID)} // should this be UNIX instead?
  257. {$UNDEF CanCreateTwoBindings}
  258. {$ELSE}
  259. {$DEFINE CanCreateTwoBindings}
  260. {$IFEND}
  261. // TODO: Would this be solved by enabling the SO_REUSEPORT option on
  262. // platforms that support it?
  263. function TIdUDPServer.GetBinding: TIdSocketHandle;
  264. var
  265. LListenerThread: TIdUDPListenerThread;
  266. i: Integer;
  267. LBinding: TIdSocketHandle;
  268. LName: string;
  269. begin
  270. if FCurrentBinding = nil then begin
  271. if Bindings.Count = 0 then begin
  272. // TODO: on systems that support dual-stack sockets, create a single
  273. // Binding object that supports both IPv4 and IPv6 on the same socket...
  274. LBinding := Bindings.Add;
  275. LBinding.IPVersion := IPVersion; // IPv4 or IPv6 by default
  276. {$IFDEF CanCreateTwoBindings}
  277. // TODO: maybe add a property so the developer can switch this behavior on/off
  278. case LBinding.IPVersion of
  279. Id_IPv4: begin
  280. if GStack.SupportsIPv6 then begin
  281. Bindings.Add.IPVersion := Id_IPv6;
  282. end;
  283. end;
  284. Id_IPv6: begin
  285. if GStack.SupportsIPv4 then begin
  286. Bindings.Add.IPVersion := Id_IPv4;
  287. end;
  288. end;
  289. end;
  290. {$ENDIF}
  291. end;
  292. // Set up listener threads
  293. i := 0;
  294. try
  295. while i < Bindings.Count do begin
  296. LBinding := Bindings[i];
  297. {$IFDEF LINUX}
  298. LBinding.AllocateSocket(Integer(Id_SOCK_DGRAM));
  299. {$ELSE}
  300. LBinding.AllocateSocket(Id_SOCK_DGRAM);
  301. {$ENDIF}
  302. // do not overwrite if the default. This allows ReuseSocket to be set per binding
  303. if FReuseSocket <> rsOSDependent then begin
  304. LBinding.ReuseSocket := FReuseSocket;
  305. end;
  306. DoBeforeBind(LBinding);
  307. LBinding.Bind;
  308. if FCurrentBinding = nil then begin
  309. FCurrentBinding := Bindings[i];
  310. end;
  311. Inc(i);
  312. end;
  313. except
  314. Dec(i); // the one that failed doesn't need to be closed
  315. while i >= 0 do begin
  316. Bindings[i].CloseSocket;
  317. Dec(i);
  318. end;
  319. raise;
  320. end;
  321. DoAfterBind;
  322. LName := Name;
  323. if LName = '' then begin
  324. LName := 'IdUDPServer'; {do not localize}
  325. end;
  326. for i := 0 to Bindings.Count - 1 do begin
  327. try
  328. LListenerThread := FThreadClass.Create(Self, Bindings[i]);
  329. try
  330. LListenerThread.Name := LName + ' Listener #' + IntToStr(i + 1); {do not localize}
  331. {$IF DEFINED(DELPHI_CROSS) AND (NOT DEFINED(MACOSX))}
  332. //Todo: Implement proper priority handling for Linux
  333. //http://www.midnightbeach.com/jon/pubs/2002/BorCon.London/Sidebar.3.html
  334. LListenerThread.Priority := tpListener;
  335. {$IFEND}
  336. FListenerThreads.Add(LListenerThread);
  337. except
  338. LListenerThread.Free;
  339. raise;
  340. end;
  341. LListenerThread.Start;
  342. except
  343. end;
  344. end;
  345. BroadcastEnabledChanged;
  346. end;
  347. Result := FCurrentBinding;
  348. end;
  349. function TIdUDPServer.GetDefaultPort: TIdPort;
  350. begin
  351. Result := FBindings.DefaultPort;
  352. end;
  353. procedure TIdUDPServer.SetBindings(const Value: TIdSocketHandles);
  354. begin
  355. FBindings.Assign(Value);
  356. end;
  357. procedure TIdUDPServer.SetDefaultPort(const AValue: TIdPort);
  358. begin
  359. FBindings.DefaultPort := AValue;
  360. end;
  361. { TIdUDPListenerThread }
  362. procedure TIdUDPListenerThread.AfterRun;
  363. begin
  364. inherited AfterRun;
  365. // Close just own binding. The rest will be closed from their
  366. // coresponding threads
  367. FBinding.CloseSocket;
  368. end;
  369. constructor TIdUDPListenerThread.Create(AOwner: TIdUDPServer; ABinding: TIdSocketHandle);
  370. begin
  371. inherited Create(True);
  372. FAcceptWait := 1000;
  373. FBinding := ABinding;
  374. FServer := AOwner;
  375. SetLength(FBuffer, 0);
  376. end;
  377. destructor TIdUDPListenerThread.Destroy;
  378. begin
  379. SetLength(FBuffer, 0);
  380. inherited Destroy;
  381. end;
  382. procedure TIdUDPListenerThread.Run;
  383. var
  384. PeerIP: string;
  385. PeerPort : TIdPort;
  386. PeerIPVersion: TIdIPVersion;
  387. ByteCount: Integer;
  388. begin
  389. if FBinding.Select(AcceptWait) then try
  390. // Doublecheck to see if we've been stopped
  391. // Depending on timing - may not reach here if it is in ancestor run when thread is stopped
  392. if not Stopped then begin
  393. SetLength(FBuffer, FServer.BufferSize);
  394. ByteCount := FBinding.RecvFrom(FBuffer, PeerIP, PeerPort, PeerIPVersion);
  395. // RLebeau: some protocols make use of 0-length messages, so don't discard
  396. // them here. This is not connection-oriented, so recvfrom() only returns
  397. // 0 if a 0-length packet was actually received...
  398. if ByteCount >= 0 then
  399. begin
  400. SetLength(FBuffer, ByteCount);
  401. FBinding.SetPeer(PeerIP, PeerPort, PeerIPVersion);
  402. // TODO: figure out a way to let UDPRead() run in this thread context
  403. // and only synchronize the OnUDPRead event handler so that descendants
  404. // do not need to be synchronized unnecessarily. Probably just have
  405. // TIdUDPServer.DoUDPRead() use TIdSync when ThreadedEvent is false...
  406. if FServer.ThreadedEvent then begin
  407. UDPRead;
  408. end else begin
  409. Synchronize(UDPRead);
  410. end;
  411. end;
  412. end;
  413. except
  414. // exceptions should be ignored so that other clients can be served in case of a DOS attack
  415. on E : Exception do
  416. begin
  417. FCurrentException := E.Message;
  418. FCurrentExceptionClass := E.ClassType;
  419. if FServer.ThreadedEvent then begin
  420. UDPException;
  421. end else begin
  422. Synchronize(UDPException);
  423. end;
  424. end;
  425. end;
  426. end;
  427. procedure TIdUDPListenerThread.UDPRead;
  428. begin
  429. FServer.DoUDPRead(Self, FBuffer, FBinding);
  430. end;
  431. procedure TIdUDPListenerThread.UDPException;
  432. begin
  433. FServer.DoOnUDPException(Self, FBinding, FCurrentException, FCurrentExceptionClass);
  434. end;
  435. end.