IdUDPServer.pas 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509
  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. {$IFDEF USE_OBJECT_ARC}
  78. // When AutoRefCounting is enabled, object references MUST be valid objects.
  79. // It is common for users to store non-object values, though, so we will
  80. // provide separate properties for those purpose
  81. //
  82. // TODO; use TValue instead of separating them
  83. //
  84. FDataObject: TObject;
  85. FDataValue: PtrInt;
  86. {$ELSE}
  87. FData: TObject;
  88. {$ENDIF}
  89. FServer: TIdUDPServer;
  90. //
  91. procedure AfterRun; override;
  92. procedure Run; override;
  93. public
  94. //
  95. //[Error] IdUDPServer.pas(266): E2391 Potentially polymorphic constructor calls must be virtual
  96. constructor Create(AOwner: TIdUDPServer; ABinding: TIdSocketHandle); reintroduce; virtual;
  97. destructor Destroy; override;
  98. //
  99. procedure UDPRead;
  100. procedure UDPException;
  101. //
  102. property AcceptWait: integer read FAcceptWait write FAcceptWait;
  103. property Binding: TIdSocketHandle read FBinding;
  104. property Server: TIdUDPServer read FServer;
  105. {$IFDEF USE_OBJECT_ARC}
  106. property DataObject: TObject read FDataObject write FDataObject;
  107. property DataValue: PtrInt read FDataValue write FDataValue;
  108. {$ELSE}
  109. property Data: TObject read FData write FData;
  110. {$ENDIF}
  111. end;
  112. // TODO: use TIdThreadSafeObjectList instead?
  113. {$IFDEF HAS_GENERICS_TThreadList}
  114. TIdUDPListenerThreadList = TThreadList<TIdUDPListenerThread>;
  115. TIdUDPListenerList = TList<TIdUDPListenerThread>;
  116. {$ELSE}
  117. // TODO: flesh out TThreadList<TIdUDPListenerThread> and TList<TIdUDPListenerThread> for non-Generics compilers...
  118. TIdUDPListenerThreadList = TThreadList;
  119. TIdUDPListenerList = TList;
  120. {$ENDIF}
  121. TIdUDPListenerThreadClass = class of TIdUDPListenerThread;
  122. //Exception is used instead of EIdException because the exception could be from somewhere else
  123. TIdUDPExceptionEvent = procedure(AThread: TIdUDPListenerThread; ABinding: TIdSocketHandle; const AMessage : String; const AExceptionClass : TClass) of object;
  124. TUDPReadEvent = procedure(AThread: TIdUDPListenerThread; const AData: TIdBytes; ABinding: TIdSocketHandle) of object;
  125. TIdUDPServer = class(TIdUDPBase)
  126. protected
  127. FBindings: TIdSocketHandles;
  128. FCurrentBinding: TIdSocketHandle;
  129. FListenerThreads: TIdUDPListenerThreadList;
  130. FThreadClass: TIdUDPListenerThreadClass;
  131. FThreadedEvent: boolean;
  132. //
  133. FOnBeforeBind: TIdSocketHandleEvent;
  134. FOnAfterBind: TNotifyEvent;
  135. FOnUDPRead: TUDPReadEvent;
  136. FOnUDPException : TIdUDPExceptionEvent;
  137. //
  138. procedure BroadcastEnabledChanged; override;
  139. procedure CloseBinding; override;
  140. procedure DoBeforeBind(AHandle: TIdSocketHandle); virtual;
  141. procedure DoAfterBind; virtual;
  142. procedure DoOnUDPException(AThread: TIdUDPListenerThread; ABinding: TIdSocketHandle; const AMessage : String; const AExceptionClass : TClass); virtual;
  143. procedure DoUDPRead(AThread: TIdUDPListenerThread; const AData: TIdBytes; ABinding: TIdSocketHandle); virtual;
  144. function GetActive: Boolean; override;
  145. function GetBinding: TIdSocketHandle; override;
  146. function GetDefaultPort: TIdPort;
  147. procedure InitComponent; override;
  148. procedure SetBindings(const Value: TIdSocketHandles);
  149. procedure SetDefaultPort(const AValue: TIdPort);
  150. public
  151. destructor Destroy; override;
  152. property ThreadClass: TIdUDPListenerThreadClass read FThreadClass write FThreadClass;
  153. published
  154. property Bindings: TIdSocketHandles read FBindings write SetBindings;
  155. property DefaultPort: TIdPort read GetDefaultPort write SetDefaultPort;
  156. property ReuseSocket;
  157. property ThreadedEvent: boolean read FThreadedEvent write FThreadedEvent default False;
  158. //
  159. property OnBeforeBind: TIdSocketHandleEvent read FOnBeforeBind write FOnBeforeBind;
  160. property OnAfterBind: TNotifyEvent read FOnAfterBind write FOnAfterBind;
  161. property OnUDPRead: TUDPReadEvent read FOnUDPRead write FOnUDPRead;
  162. property OnUDPException : TIdUDPExceptionEvent read FOnUDPException write FOnUDPException;
  163. end;
  164. EIdUDPServerException = class(EIdUDPException);
  165. implementation
  166. uses
  167. {$IFDEF VCL_2010_OR_ABOVE}
  168. {$IFDEF WINDOWS}
  169. Windows,
  170. {$ENDIF}
  171. {$ENDIF}
  172. IdGlobalCore, SysUtils;
  173. procedure TIdUDPServer.BroadcastEnabledChanged;
  174. var
  175. i: Integer;
  176. begin
  177. if Assigned(FCurrentBinding) then begin
  178. for i := 0 to Bindings.Count - 1 do begin
  179. Bindings[i].BroadcastEnabled := BroadcastEnabled;
  180. end;
  181. end;
  182. end;
  183. procedure TIdUDPServer.CloseBinding;
  184. var
  185. LListenerThreads: TIdUDPListenerList;
  186. LListener: TIdUDPListenerThread;
  187. begin
  188. // RLebeau 2/17/2006: TIdUDPBase.Destroy() calls CloseBinding()
  189. if Assigned(FListenerThreads) then
  190. begin
  191. LListenerThreads := FListenerThreads.LockList;
  192. try
  193. while LListenerThreads.Count > 0 do
  194. begin
  195. LListener := {$IFDEF HAS_GENERICS_TThreadList}LListenerThreads[0]{$ELSE}TIdUDPListenerThread(LListenerThreads[0]){$ENDIF};
  196. // Stop listening
  197. LListener.Stop;
  198. LListener.Binding.CloseSocket;
  199. // Tear down Listener thread
  200. LListener.WaitFor;
  201. LListener.Free;
  202. LListenerThreads.Delete(0); // RLebeau 2/17/2006
  203. end;
  204. finally
  205. FListenerThreads.UnlockList;
  206. end;
  207. end;
  208. FCurrentBinding := nil;
  209. end;
  210. destructor TIdUDPServer.Destroy;
  211. begin
  212. Active := False;
  213. FreeAndNil(FBindings);
  214. FreeAndNil(FListenerThreads);
  215. inherited Destroy;
  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. {$DEFINE CanCreateTwoBindings}
  257. {$IFDEF LINUX} // should this be UNIX instead?
  258. {$UNDEF CanCreateTwoBindings}
  259. {$ENDIF}
  260. {$IFDEF SOLARIS}
  261. {$UNDEF CanCreateTwoBindings}
  262. {$ENDIF}
  263. {$IFDEF ANDROID}
  264. {$UNDEF CanCreateTwoBindings}
  265. {$ENDIF}
  266. // TODO: Would this be solved by enabling the SO_REUSEPORT option on
  267. // platforms that support it?
  268. function TIdUDPServer.GetBinding: TIdSocketHandle;
  269. var
  270. LListenerThread: TIdUDPListenerThread;
  271. i: Integer;
  272. LBinding: TIdSocketHandle;
  273. LName: string;
  274. begin
  275. if FCurrentBinding = nil then begin
  276. if Bindings.Count = 0 then begin
  277. // TODO: on systems that support dual-stack sockets, create a single
  278. // Binding object that supports both IPv4 and IPv6 on the same socket...
  279. LBinding := Bindings.Add;
  280. LBinding.IPVersion := IPVersion; // IPv4 or IPv6 by default
  281. {$IFDEF CanCreateTwoBindings}
  282. // TODO: maybe add a property so the developer can switch this behavior on/off
  283. case LBinding.IPVersion of
  284. Id_IPv4: begin
  285. if GStack.SupportsIPv6 then begin
  286. Bindings.Add.IPVersion := Id_IPv6;
  287. end;
  288. end;
  289. Id_IPv6: begin
  290. if GStack.SupportsIPv4 then begin
  291. Bindings.Add.IPVersion := Id_IPv4;
  292. end;
  293. end;
  294. end;
  295. {$ENDIF}
  296. end;
  297. // Set up listener threads
  298. i := 0;
  299. try
  300. while i < Bindings.Count do begin
  301. LBinding := Bindings[i];
  302. {$IFDEF LINUX}
  303. LBinding.AllocateSocket(Integer(Id_SOCK_DGRAM));
  304. {$ELSE}
  305. LBinding.AllocateSocket(Id_SOCK_DGRAM);
  306. {$ENDIF}
  307. // do not overwrite if the default. This allows ReuseSocket to be set per binding
  308. if FReuseSocket <> rsOSDependent then begin
  309. LBinding.ReuseSocket := FReuseSocket;
  310. end;
  311. DoBeforeBind(LBinding);
  312. LBinding.Bind;
  313. if FCurrentBinding = nil then begin
  314. FCurrentBinding := Bindings[i];
  315. end;
  316. Inc(i);
  317. end;
  318. except
  319. Dec(i); // the one that failed doesn't need to be closed
  320. while i >= 0 do begin
  321. Bindings[i].CloseSocket;
  322. Dec(i);
  323. end;
  324. raise;
  325. end;
  326. DoAfterBind;
  327. LName := Name;
  328. if LName = '' then begin
  329. LName := 'IdUDPServer'; {do not localize}
  330. end;
  331. for i := 0 to Bindings.Count - 1 do begin
  332. try
  333. LListenerThread := FThreadClass.Create(Self, Bindings[i]);
  334. try
  335. LListenerThread.Name := LName + ' Listener #' + IntToStr(i + 1); {do not localize}
  336. {$IFDEF DELPHI_CROSS}
  337. {$IFNDEF MACOSX}
  338. //Todo: Implement proper priority handling for Linux
  339. //http://www.midnightbeach.com/jon/pubs/2002/BorCon.London/Sidebar.3.html
  340. LListenerThread.Priority := tpListener;
  341. {$ENDIF}
  342. {$ENDIF}
  343. FListenerThreads.Add(LListenerThread);
  344. except
  345. LListenerThread.Free;
  346. raise;
  347. end;
  348. LListenerThread.Start;
  349. except
  350. end;
  351. end;
  352. BroadcastEnabledChanged;
  353. end;
  354. Result := FCurrentBinding;
  355. end;
  356. function TIdUDPServer.GetDefaultPort: TIdPort;
  357. begin
  358. Result := FBindings.DefaultPort;
  359. end;
  360. procedure TIdUDPServer.InitComponent;
  361. begin
  362. inherited InitComponent;
  363. FBindings := TIdSocketHandles.Create(Self);
  364. FListenerThreads := TIdUDPListenerThreadList.Create;
  365. FThreadClass := TIdUDPListenerThread;
  366. end;
  367. procedure TIdUDPServer.SetBindings(const Value: TIdSocketHandles);
  368. begin
  369. FBindings.Assign(Value);
  370. end;
  371. procedure TIdUDPServer.SetDefaultPort(const AValue: TIdPort);
  372. begin
  373. FBindings.DefaultPort := AValue;
  374. end;
  375. { TIdUDPListenerThread }
  376. procedure TIdUDPListenerThread.AfterRun;
  377. begin
  378. inherited AfterRun;
  379. // Close just own binding. The rest will be closed from their
  380. // coresponding threads
  381. FBinding.CloseSocket;
  382. end;
  383. constructor TIdUDPListenerThread.Create(AOwner: TIdUDPServer; ABinding: TIdSocketHandle);
  384. begin
  385. inherited Create(True);
  386. FAcceptWait := 1000;
  387. FBinding := ABinding;
  388. FServer := AOwner;
  389. SetLength(FBuffer, 0);
  390. end;
  391. destructor TIdUDPListenerThread.Destroy;
  392. begin
  393. SetLength(FBuffer, 0);
  394. inherited Destroy;
  395. end;
  396. procedure TIdUDPListenerThread.Run;
  397. var
  398. PeerIP: string;
  399. PeerPort : TIdPort;
  400. PeerIPVersion: TIdIPVersion;
  401. ByteCount: Integer;
  402. begin
  403. if FBinding.Select(AcceptWait) then try
  404. // Doublecheck to see if we've been stopped
  405. // Depending on timing - may not reach here if it is in ancestor run when thread is stopped
  406. if not Stopped then begin
  407. SetLength(FBuffer, FServer.BufferSize);
  408. ByteCount := FBinding.RecvFrom(FBuffer, PeerIP, PeerPort, PeerIPVersion);
  409. // RLebeau: some protocols make use of 0-length messages, so don't discard
  410. // them here. This is not connection-oriented, so recvfrom() only returns
  411. // 0 if a 0-length packet was actually received...
  412. if ByteCount >= 0 then
  413. begin
  414. SetLength(FBuffer, ByteCount);
  415. FBinding.SetPeer(PeerIP, PeerPort, PeerIPVersion);
  416. // TODO: figure out a way to let UDPRead() run in this thread context
  417. // and only synchronize the OnUDPRead event handler so that descendants
  418. // do not need to be synchronized unnecessarily. Probably just have
  419. // TIdUDPServer.DoUDPRead() use TIdSync when ThreadedEvent is false...
  420. if FServer.ThreadedEvent then begin
  421. UDPRead;
  422. end else begin
  423. Synchronize(UDPRead);
  424. end;
  425. end;
  426. end;
  427. except
  428. // exceptions should be ignored so that other clients can be served in case of a DOS attack
  429. on E : Exception do
  430. begin
  431. FCurrentException := E.Message;
  432. FCurrentExceptionClass := E.ClassType;
  433. if FServer.ThreadedEvent then begin
  434. UDPException;
  435. end else begin
  436. Synchronize(UDPException);
  437. end;
  438. end;
  439. end;
  440. end;
  441. procedure TIdUDPListenerThread.UDPRead;
  442. begin
  443. FServer.DoUDPRead(Self, FBuffer, FBinding);
  444. end;
  445. procedure TIdUDPListenerThread.UDPException;
  446. begin
  447. FServer.DoOnUDPException(Self, FBinding, FCurrentException, FCurrentExceptionClass);
  448. end;
  449. end.