fpsock.pp 15 KB

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