fpsock.pp 14 KB

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