fpsock.pp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608
  1. {
  2. $Id$
  3. Socket communication components
  4. Copyright (c) 2003 by
  5. Areca Systems GmbH / Sebastian Guenther, [email protected]
  6. See the file COPYING.FPC, included in this distribution,
  7. for details about the copyright.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  11. }
  12. unit fpSock;
  13. interface
  14. uses Errors, SysUtils, Sockets, Classes, fpAsync, Resolve;
  15. type
  16. ESocketError = class(Exception)
  17. end;
  18. TSocketComponent = class(TComponent)
  19. private
  20. FEventLoop: TEventLoop;
  21. public
  22. property EventLoop: TEventLoop read FEventLoop write FEventLoop;
  23. end;
  24. TSocketStream = class(THandleStream)
  25. private
  26. FOnDisconnect: TNotifyEvent;
  27. function GetLocalAddress: TSockAddr;
  28. function GetPeerAddress: TSockAddr;
  29. protected
  30. procedure Disconnected; virtual;
  31. public
  32. destructor Destroy; override;
  33. function Read(var Buffer; Count: LongInt): LongInt; override;
  34. function Write(const Buffer; Count: LongInt): LongInt; override;
  35. property LocalAddress: TSockAddr read GetLocalAddress;
  36. property PeerAddress: TSockAddr read GetPeerAddress;
  37. property OnDisconnect: TNotifyEvent read FOnDisconnect write FOnDisconnect;
  38. end;
  39. // Connection-based sockets
  40. TConnectionBasedSocket = class(TSocketComponent)
  41. protected
  42. FStream: TSocketStream;
  43. FActive: Boolean;
  44. procedure SetActive(Value: Boolean); virtual; abstract;
  45. property Active: Boolean read FActive write SetActive;
  46. property Stream: TSocketStream read FStream;
  47. public
  48. destructor Destroy; override;
  49. end;
  50. TConnectionState = (
  51. connDisconnected,
  52. connResolving,
  53. connConnecting,
  54. connConnected);
  55. TClientConnectionSocket = class;
  56. TConnectionStateChangeEvent = procedure(Sender: TClientConnectionSocket;
  57. OldState, NewState: TConnectionState) of object;
  58. TClientConnectionSocket = class(TConnectionBasedSocket)
  59. private
  60. FOnStateChange: TConnectionStateChangeEvent;
  61. FRetries: Integer;
  62. FRetryDelay: Integer; // Delay between retries in ms
  63. RetryCounter: Integer;
  64. RetryTimerNotifyHandle: Pointer;
  65. CanWriteNotifyHandle: Pointer;
  66. procedure RetryTimerNotify(Sender: TObject);
  67. procedure SocketCanWrite(Sender: TObject);
  68. protected
  69. FConnectionState: TConnectionState;
  70. procedure CreateSocket; virtual; abstract;
  71. procedure DoResolve; virtual;
  72. procedure DoConnect; virtual;
  73. function GetPeerName: String; virtual; abstract;
  74. procedure SetActive(Value: Boolean); override;
  75. procedure SetConnectionState(NewState: TConnectionState);
  76. property ConnectionState: TConnectionState read FConnectionState;
  77. property Retries: Integer read FRetries write FRetries default 0;
  78. property RetryDelay: Integer read FRetryDelay write FRetryDelay default 500;
  79. property OnConnectionStateChange: TConnectionStateChangeEvent
  80. read FOnStateChange write FOnStateChange;
  81. public
  82. constructor Create(AOwner: TComponent); override;
  83. destructor Destroy; override;
  84. end;
  85. TQueryConnectEvent = procedure(Sender: TConnectionBasedSocket; Socket: Integer;
  86. var DoConnect: Boolean) of object;
  87. TConnectEvent = procedure(Sender: TConnectionBasedSocket;
  88. Stream: TSocketStream) of object;
  89. TSocketConnectionServer = class(TConnectionBasedSocket)
  90. private
  91. FOnQueryConnect: TQueryConnectEvent;
  92. FOnConnect: TConnectEvent;
  93. protected
  94. DataAvailableNotifyHandle: Pointer;
  95. procedure ListenerDataAvailable(Sender: TObject);
  96. function DoQueryConnect(ASocket: Integer): Boolean;
  97. procedure DoConnect(AStream: TSocketStream); virtual;
  98. property OnQueryConnect: TQueryConnectEvent read FOnQueryConnect
  99. write FOnQueryConnect;
  100. property OnConnect: TConnectEvent read FOnConnect write FOnConnect;
  101. end;
  102. // TCP/IP components
  103. TCustomTCPClient = class(TClientConnectionSocket)
  104. private
  105. FHost: String;
  106. FPort: Word;
  107. HostAddr: THostAddr;
  108. procedure SetHost(const Value: String);
  109. procedure SetPort(Value: Word);
  110. protected
  111. procedure CreateSocket; override;
  112. procedure DoResolve; override;
  113. procedure DoConnect; override;
  114. function GetPeerName: String; override;
  115. property Host: String read FHost write SetHost;
  116. property Port: Word read FPort write SetPort;
  117. public
  118. destructor Destroy; override;
  119. end;
  120. TTCPClient = class(TCustomTCPClient)
  121. public
  122. property ConnectionState;
  123. property Stream;
  124. published
  125. property Active;
  126. property Host;
  127. property Port;
  128. property Retries;
  129. property RetryDelay;
  130. property OnConnectionStateChange;
  131. end;
  132. TCustomTCPServer = class;
  133. TCustomTCPServer = class(TSocketConnectionServer)
  134. private
  135. FPort: Word;
  136. procedure SetActive(Value: Boolean); override;
  137. protected
  138. //!!!: Interface/bindings list?
  139. property Port: Word read FPort write FPort;
  140. public
  141. destructor Destroy; override;
  142. end;
  143. TTCPServer = class(TCustomTCPServer)
  144. public
  145. property Stream;
  146. published
  147. property Active;
  148. property Port;
  149. property OnQueryConnect;
  150. property OnConnect;
  151. end;
  152. implementation
  153. uses
  154. {$IFDEF VER1_0}
  155. Linux;
  156. {$ELSE}
  157. baseunix,Unix;
  158. {$ENDIF}
  159. resourcestring
  160. SSocketNoEventLoopAssigned = 'No event loop assigned';
  161. SSocketCreationError = 'Could not create socket: %s';
  162. SHostNotFound = 'Host "%s" not found';
  163. SSocketConnectFailed = 'Could not connect to %s: %s';
  164. SSocketBindingError = 'Could not bind socket to port %d: %s';
  165. SSocketAcceptError = 'Connection accept failed: %s';
  166. SSocketIsActive = 'Cannot change parameters while active';
  167. {$ifndef VER1_0}
  168. Const
  169. Sys_EAGAIN = ESYSEAGAIN;
  170. Sys_EINPROGRESS = ESYSEINPROGRESS;
  171. {$endif}
  172. // TSocketStream
  173. destructor TSocketStream.Destroy;
  174. begin
  175. FileClose(Handle);
  176. inherited Destroy;
  177. end;
  178. function TSocketStream.Read(var Buffer; Count: LongInt): LongInt;
  179. begin
  180. Result := recv(Handle, Buffer, Count, MSG_NOSIGNAL);
  181. if Result = -1 then
  182. begin
  183. Result := 0;
  184. if SocketError <> Sys_EAGAIN then
  185. Disconnected;
  186. end;
  187. end;
  188. function TSocketStream.Write(const Buffer; Count: LongInt): LongInt;
  189. begin
  190. Result := send(Handle, Buffer, Count, MSG_NOSIGNAL);
  191. if Result = -1 then
  192. begin
  193. Result := 0;
  194. if SocketError <> Sys_EAGAIN then
  195. Disconnected;
  196. end;
  197. end;
  198. procedure TSocketStream.Disconnected;
  199. begin
  200. if Assigned(OnDisconnect) then
  201. OnDisconnect(Self);
  202. end;
  203. function TSocketStream.GetLocalAddress: TSockAddr;
  204. var
  205. len: LongInt;
  206. begin
  207. len := SizeOf(TSockAddr);
  208. if GetSocketName(Handle, Result, len) <> 0 then
  209. FillChar(Result, SizeOf(Result), 0);
  210. end;
  211. function TSocketStream.GetPeerAddress: TSockAddr;
  212. var
  213. len: LongInt;
  214. begin
  215. len := SizeOf(TSockAddr);
  216. if GetPeerName(Handle, Result, len) <> 0 then
  217. FillChar(Result, SizeOf(Result), 0);
  218. end;
  219. // TConnectionBasedSocket
  220. destructor TConnectionBasedSocket.Destroy;
  221. begin
  222. FreeAndNil(FStream);
  223. inherited Destroy;
  224. end;
  225. // TClientConnectionSocket
  226. constructor TClientConnectionSocket.Create(AOwner: TComponent);
  227. begin
  228. inherited Create(AOwner);
  229. FRetryDelay := 500;
  230. end;
  231. destructor TClientConnectionSocket.Destroy;
  232. begin
  233. if Assigned(RetryTimerNotifyHandle) then
  234. EventLoop.RemoveTimerNotify(RetryTimerNotifyHandle);
  235. inherited Destroy;
  236. end;
  237. procedure TClientConnectionSocket.DoResolve;
  238. begin
  239. // By default, no resolving is done, so continue directly with connecting
  240. DoConnect;
  241. end;
  242. procedure TClientConnectionSocket.DoConnect;
  243. begin
  244. SetConnectionState(connConnecting);
  245. try
  246. if not Assigned(EventLoop) then
  247. raise ESocketError.Create(SSocketNoEventLoopAssigned);
  248. CanWriteNotifyHandle := EventLoop.SetCanWriteNotify(Stream.Handle,
  249. @SocketCanWrite, nil);
  250. except
  251. SetConnectionState(connDisconnected);
  252. raise;
  253. end;
  254. end;
  255. procedure TClientConnectionSocket.SetActive(Value: Boolean);
  256. begin
  257. if Value <> Active then
  258. begin
  259. if Value then
  260. begin
  261. // Activate the connection
  262. FActive := True;
  263. RetryCounter := 0;
  264. CreateSocket;
  265. DoResolve;
  266. end else
  267. begin
  268. // Close the connection
  269. FActive := False;
  270. try
  271. FreeAndNil(FStream);
  272. if Assigned(CanWriteNotifyHandle) then
  273. begin
  274. EventLoop.ClearCanWriteNotify(CanWriteNotifyHandle);
  275. CanWriteNotifyHandle := nil;
  276. end;
  277. if Assigned(RetryTimerNotifyHandle) then
  278. begin
  279. EventLoop.RemoveTimerNotify(RetryTimerNotifyHandle);
  280. RetryTimerNotifyHandle := nil;
  281. end;
  282. finally
  283. SetConnectionState(connDisconnected);
  284. end;
  285. end;
  286. end;
  287. end;
  288. procedure TClientConnectionSocket.SetConnectionState(NewState:
  289. TConnectionState);
  290. var
  291. OldState: TConnectionState;
  292. begin
  293. if NewState <> ConnectionState then
  294. begin
  295. OldState := ConnectionState;
  296. FConnectionState := NewState;
  297. if Assigned(OnConnectionStateChange) then
  298. OnConnectionStateChange(Self, OldState, NewState);
  299. end;
  300. end;
  301. procedure TClientConnectionSocket.RetryTimerNotify(Sender: TObject);
  302. begin
  303. RetryTimerNotifyHandle := nil;
  304. Active := True;
  305. end;
  306. procedure TClientConnectionSocket.SocketCanWrite(Sender: TObject);
  307. var
  308. Error: Integer;
  309. ErrorLen, GetResult: LongInt;
  310. begin
  311. if ConnectionState = connConnecting then
  312. begin
  313. EventLoop.ClearCanWriteNotify(CanWriteNotifyHandle);
  314. CanWriteNotifyHandle := nil;
  315. ErrorLen := SizeOf(Error);
  316. GetResult := Sockets.GetSocketOptions(Stream.Handle, SOL_SOCKET, SO_ERROR,
  317. Error, ErrorLen);
  318. if GetResult <> 0 then
  319. raise ESocketError.CreateFmt(SSocketConnectFailed,
  320. [GetPeerName, StrError(GetResult)]);
  321. if Error <> 0 then
  322. if (RetryCounter >= Retries) and (Retries >= 0) then
  323. raise ESocketError.CreateFmt(SSocketConnectFailed,
  324. [GetPeerName, StrError(Error)])
  325. else begin
  326. Active := False;
  327. RetryTimerNotifyHandle := EventLoop.AddTimerNotify(RetryDelay, False,
  328. @RetryTimerNotify, Self);
  329. Inc(RetryCounter);
  330. end
  331. else
  332. begin
  333. RetryCounter := 0;
  334. SetConnectionState(connConnected);
  335. end;
  336. end;
  337. end;
  338. // TSocketConnectionServer
  339. procedure TSocketConnectionServer.ListenerDataAvailable(Sender: TObject);
  340. var
  341. ClientSocket: Integer;
  342. Addr: TInetSockAddr;
  343. AddrSize: Integer;
  344. begin
  345. AddrSize := SizeOf(Addr);
  346. ClientSocket := Accept(Stream.Handle, Addr, AddrSize);
  347. if ClientSocket = -1 then
  348. raise ESocketError.CreateFmt(SSocketAcceptError, [StrError(SocketError)]);
  349. if DoQueryConnect(ClientSocket) then
  350. DoConnect(TSocketStream.Create(ClientSocket));
  351. end;
  352. function TSocketConnectionServer.DoQueryConnect(ASocket: Integer): Boolean;
  353. begin
  354. Result := True;
  355. if Assigned(OnQueryConnect) then
  356. OnQueryConnect(Self, ASocket, Result);
  357. end;
  358. procedure TSocketConnectionServer.DoConnect(AStream: TSocketStream);
  359. begin
  360. if Assigned(OnConnect) then
  361. OnConnect(Self, AStream);
  362. end;
  363. // TCustomTCPClient
  364. type
  365. TClientSocketStream = class(TSocketStream)
  366. protected
  367. Client: TCustomTCPClient;
  368. procedure Disconnected; override;
  369. end;
  370. procedure TClientSocketStream.Disconnected;
  371. begin
  372. inherited Disconnected;
  373. Client.Active := False;
  374. end;
  375. destructor TCustomTCPClient.Destroy;
  376. begin
  377. if Assigned(CanWriteNotifyHandle) then
  378. begin
  379. EventLoop.ClearCanWriteNotify(CanWriteNotifyHandle);
  380. // Set to nil to be sure that descendant classes don't do something stupid
  381. CanWriteNotifyHandle := nil;
  382. end;
  383. inherited Destroy;
  384. end;
  385. procedure TCustomTCPClient.SetHost(const Value: String);
  386. begin
  387. if Value <> Host then
  388. begin
  389. if Active then
  390. raise ESocketError.Create(SSocketIsActive);
  391. FHost := Value;
  392. end;
  393. end;
  394. procedure TCustomTCPClient.SetPort(Value: Word);
  395. begin
  396. if Value <> Port then
  397. begin
  398. if Active then
  399. raise ESocketError.Create(SSocketIsActive);
  400. FPort := Value;
  401. end;
  402. end;
  403. procedure TCustomTCPClient.DoResolve;
  404. var
  405. HostResolver: THostResolver;
  406. begin
  407. HostAddr := StrToHostAddr(Host);
  408. if HostAddr[1] = 0 then
  409. begin
  410. HostResolver := THostResolver.Create(nil);
  411. try
  412. SetConnectionState(connResolving);
  413. if not HostResolver.NameLookup(FHost) then
  414. raise ESocketError.CreateFmt(SHostNotFound, [Host]);
  415. HostAddr := HostResolver.HostAddress;
  416. finally
  417. HostResolver.Free;
  418. end;
  419. end;
  420. DoConnect;
  421. end;
  422. procedure TCustomTCPClient.CreateSocket;
  423. var
  424. Socket: Integer;
  425. begin
  426. Socket := Sockets.Socket(AF_INET, SOCK_STREAM, 0);
  427. if Socket = -1 then
  428. raise ESocketError.CreateFmt(SSocketCreationError,
  429. [StrError(SocketError)]);
  430. FStream := TClientSocketStream.Create(Socket);
  431. TClientSocketStream(FStream).Client := Self;
  432. end;
  433. procedure TCustomTCPClient.DoConnect;
  434. var
  435. SockAddr: TInetSockAddr;
  436. begin
  437. inherited DoConnect;
  438. SockAddr.Family := AF_INET;
  439. SockAddr.Port := ShortHostToNet(Port);
  440. SockAddr.Addr := Cardinal(HostAddr);
  441. Sockets.Connect(Stream.Handle, SockAddr, SizeOf(SockAddr));
  442. if (SocketError <> sys_EINPROGRESS) and (SocketError <> 0) then
  443. raise ESocketError.CreateFmt(SSocketConnectFailed,
  444. [GetPeerName, StrError(SocketError)]);
  445. end;
  446. function TCustomTCPClient.GetPeerName: String;
  447. begin
  448. Result := Format('%s:%d', [Host, Port]);
  449. end;
  450. // TCustomTCPServer
  451. destructor TCustomTCPServer.Destroy;
  452. begin
  453. if Assigned(DataAvailableNotifyHandle) then
  454. begin
  455. EventLoop.ClearDataAvailableNotify(DataAvailableNotifyHandle);
  456. // Set to nil to be sure that descendant classes don't do something stupid
  457. DataAvailableNotifyHandle := nil;
  458. end;
  459. inherited Destroy;
  460. end;
  461. procedure TCustomTCPServer.SetActive(Value: Boolean);
  462. var
  463. Socket, TrueValue: Integer;
  464. Addr: TInetSockAddr;
  465. begin
  466. if Active <> Value then
  467. begin
  468. FActive := False;
  469. if Value then
  470. begin
  471. Socket := Sockets.Socket(AF_INET, SOCK_STREAM, 0);
  472. if Socket = -1 then
  473. raise ESocketError.CreateFmt(SSocketCreationError,
  474. [StrError(SocketError)]);
  475. TrueValue := 1;
  476. Sockets.SetSocketOptions(Socket, SOL_SOCKET, SO_REUSEADDR,
  477. TrueValue, SizeOf(TrueValue));
  478. FStream := TSocketStream.Create(Socket);
  479. Addr.Family := AF_INET;
  480. Addr.Port := ShortHostToNet(Port);
  481. Addr.Addr := 0;
  482. if not Bind(Socket, Addr, SizeOf(Addr)) then
  483. raise ESocketError.CreateFmt(SSocketBindingError,
  484. [Port, StrError(SocketError)]);
  485. Listen(Socket, 5);
  486. if not Assigned(EventLoop) then
  487. raise ESocketError.Create(SSocketNoEventLoopAssigned);
  488. DataAvailableNotifyHandle := EventLoop.SetDataAvailableNotify(Socket,
  489. @ListenerDataAvailable, nil);
  490. FActive := True;
  491. end else
  492. begin
  493. FreeAndNil(FStream);
  494. EventLoop.ClearDataAvailableNotify(DataAvailableNotifyHandle);
  495. DataAvailableNotifyHandle := nil;
  496. end;
  497. end;
  498. end;
  499. end.
  500. {
  501. $Log$
  502. Revision 1.5 2004-08-14 20:31:54 sg
  503. * Sockets now use REUSE_ADDR by default, to enable fast restarts of server
  504. applications
  505. Revision 1.4 2004/02/20 20:46:21 michael
  506. + Fixes for 1.1 - revised from Marcos stuff
  507. Revision 1.3 2004/02/02 14:39:48 marco
  508. * 1.0.x problems fixed
  509. Revision 1.2 2004/01/31 19:13:14 sg
  510. * Splittet old HTTP unit into httpbase and httpclient
  511. * Many improvements in fpSock (e.g. better disconnection detection)
  512. Revision 1.1 2003/11/22 11:55:28 sg
  513. * First version, a simple starting point for further development
  514. }