123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582 |
- {
- Socket communication components
- Copyright (c) 2003 by
- Areca Systems GmbH / Sebastian Guenther, [email protected]
- See the file COPYING.FPC, included in this distribution,
- for details about the copyright.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- }
- {$mode objfpc}
- {$H+}
- unit fpSock;
- interface
- uses Errors, SysUtils, Sockets, Classes, fpAsync, Resolve;
- type
- ESocketError = class(Exception)
- end;
- TSocketComponent = class(TComponent)
- private
- FEventLoop: TEventLoop;
- public
- property EventLoop: TEventLoop read FEventLoop write FEventLoop;
- end;
- TSocketStream = class(THandleStream)
- private
- FOnDisconnect: TNotifyEvent;
- function GetLocalAddress: TSockAddr;
- function GetPeerAddress: TSockAddr;
- protected
- procedure Disconnected; virtual;
- public
- destructor Destroy; override;
- function Read(var Buffer; Count: LongInt): LongInt; override;
- function Write(const Buffer; Count: LongInt): LongInt; override;
- property LocalAddress: TSockAddr read GetLocalAddress;
- property PeerAddress: TSockAddr read GetPeerAddress;
- property OnDisconnect: TNotifyEvent read FOnDisconnect write FOnDisconnect;
- end;
- // Connection-based sockets
- TConnectionBasedSocket = class(TSocketComponent)
- protected
- FStream: TSocketStream;
- FActive: Boolean;
- procedure SetActive(Value: Boolean); virtual; abstract;
- property Active: Boolean read FActive write SetActive;
- property Stream: TSocketStream read FStream;
- public
- destructor Destroy; override;
- end;
- TConnectionState = (
- connDisconnected,
- connResolving,
- connConnecting,
- connConnected);
- TClientConnectionSocket = class;
- TConnectionStateChangeEvent = procedure(Sender: TClientConnectionSocket;
- OldState, NewState: TConnectionState) of object;
- TClientConnectionSocket = class(TConnectionBasedSocket)
- private
- FOnStateChange: TConnectionStateChangeEvent;
- FRetries: Integer;
- FRetryDelay: Integer; // Delay between retries in ms
- RetryCounter: Integer;
- RetryTimerNotifyHandle: Pointer;
- CanWriteNotifyHandle: Pointer;
- procedure RetryTimerNotify(Sender: TObject);
- procedure SocketCanWrite(Sender: TObject);
- protected
- FConnectionState: TConnectionState;
- procedure CreateSocket; virtual; abstract;
- procedure DoResolve; virtual;
- procedure DoConnect; virtual;
- function GetPeerName: String; virtual; abstract;
- procedure SetActive(Value: Boolean); override;
- procedure SetConnectionState(NewState: TConnectionState);
- property ConnectionState: TConnectionState read FConnectionState;
- property Retries: Integer read FRetries write FRetries default 0;
- property RetryDelay: Integer read FRetryDelay write FRetryDelay default 500;
- property OnConnectionStateChange: TConnectionStateChangeEvent
- read FOnStateChange write FOnStateChange;
- public
- constructor Create(AOwner: TComponent); override;
- destructor Destroy; override;
- end;
- TQueryConnectEvent = procedure(Sender: TConnectionBasedSocket; Socket: Integer;
- var DoConnect: Boolean) of object;
- TConnectEvent = procedure(Sender: TConnectionBasedSocket;
- Stream: TSocketStream) of object;
- TSocketConnectionServer = class(TConnectionBasedSocket)
- private
- FOnQueryConnect: TQueryConnectEvent;
- FOnConnect: TConnectEvent;
- protected
- DataAvailableNotifyHandle: Pointer;
- procedure ListenerDataAvailable(Sender: TObject);
- function DoQueryConnect(ASocket: Integer): Boolean;
- procedure DoConnect(AStream: TSocketStream); virtual;
- property OnQueryConnect: TQueryConnectEvent read FOnQueryConnect
- write FOnQueryConnect;
- property OnConnect: TConnectEvent read FOnConnect write FOnConnect;
- end;
- // TCP/IP components
- TCustomTCPClient = class(TClientConnectionSocket)
- private
- FHost: String;
- FPort: Word;
- HostAddr: THostAddr;
- procedure SetHost(const Value: String);
- procedure SetPort(Value: Word);
- protected
- procedure CreateSocket; override;
- procedure DoResolve; override;
- procedure DoConnect; override;
- function GetPeerName: String; override;
- property Host: String read FHost write SetHost;
- property Port: Word read FPort write SetPort;
- public
- destructor Destroy; override;
- end;
- TTCPClient = class(TCustomTCPClient)
- public
- property ConnectionState;
- property Stream;
- published
- property Active;
- property Host;
- property Port;
- property Retries;
- property RetryDelay;
- property OnConnectionStateChange;
- end;
- TCustomTCPServer = class;
- TCustomTCPServer = class(TSocketConnectionServer)
- private
- FPort: Word;
- procedure SetActive(Value: Boolean); override;
- protected
- //!!!: Interface/bindings list?
- property Port: Word read FPort write FPort;
- public
- destructor Destroy; override;
- end;
- TTCPServer = class(TCustomTCPServer)
- public
- property Stream;
- published
- property Active;
- property Port;
- property OnQueryConnect;
- property OnConnect;
- end;
- implementation
- uses
- baseunix,Unix;
- resourcestring
- SSocketNoEventLoopAssigned = 'No event loop assigned';
- SSocketCreationError = 'Could not create socket: %s';
- SHostNotFound = 'Host "%s" not found';
- SSocketConnectFailed = 'Could not connect to %s: %s';
- SSocketBindingError = 'Could not bind socket to port %d: %s';
- SSocketAcceptError = 'Connection accept failed: %s';
- SSocketIsActive = 'Cannot change parameters while active';
- Const
- Sys_EAGAIN = ESYSEAGAIN;
- Sys_EINPROGRESS = ESYSEINPROGRESS;
- // TSocketStream
- destructor TSocketStream.Destroy;
- begin
- FileClose(Handle);
- inherited Destroy;
- end;
- function TSocketStream.Read(var Buffer; Count: LongInt): LongInt;
- begin
- Result := fprecv(Handle, @Buffer, Count, MSG_NOSIGNAL);
- if Result = -1 then
- begin
- Result := 0;
- if SocketError <> Sys_EAGAIN then
- Disconnected;
- end;
- end;
- function TSocketStream.Write(const Buffer; Count: LongInt): LongInt;
- begin
- Result := FPsend(Handle, @Buffer, Count, MSG_NOSIGNAL);
- if Result = -1 then
- begin
- Result := 0;
- if SocketError <> Sys_EAGAIN then
- Disconnected;
- end;
- end;
- procedure TSocketStream.Disconnected;
- begin
- if Assigned(OnDisconnect) then
- OnDisconnect(Self);
- end;
- function TSocketStream.GetLocalAddress: TSockAddr;
- var
- len: LongInt;
- begin
- len := SizeOf(TSockAddr);
- if fpGetSockName(Handle, @Result, @len) <> 0 then
- FillChar(Result, SizeOf(Result), 0);
- end;
- function TSocketStream.GetPeerAddress: TSockAddr;
- var
- len: LongInt;
- begin
- len := SizeOf(TSockAddr);
- if FpGetPeerName(Handle, @Result, @len) <> 0 then
- FillChar(Result, SizeOf(Result), 0);
- end;
- // TConnectionBasedSocket
- destructor TConnectionBasedSocket.Destroy;
- begin
- FreeAndNil(FStream);
- inherited Destroy;
- end;
- // TClientConnectionSocket
- constructor TClientConnectionSocket.Create(AOwner: TComponent);
- begin
- inherited Create(AOwner);
- FRetryDelay := 500;
- end;
- destructor TClientConnectionSocket.Destroy;
- begin
- if Assigned(RetryTimerNotifyHandle) then
- EventLoop.RemoveTimerNotify(RetryTimerNotifyHandle);
- inherited Destroy;
- end;
- procedure TClientConnectionSocket.DoResolve;
- begin
- // By default, no resolving is done, so continue directly with connecting
- DoConnect;
- end;
- procedure TClientConnectionSocket.DoConnect;
- begin
- SetConnectionState(connConnecting);
- try
- if not Assigned(EventLoop) then
- raise ESocketError.Create(SSocketNoEventLoopAssigned);
- CanWriteNotifyHandle := EventLoop.SetCanWriteNotify(Stream.Handle,
- @SocketCanWrite, nil);
- except
- SetConnectionState(connDisconnected);
- raise;
- end;
- end;
- procedure TClientConnectionSocket.SetActive(Value: Boolean);
- begin
- if Value <> Active then
- begin
- if Value then
- begin
- // Activate the connection
- FActive := True;
- RetryCounter := 0;
- CreateSocket;
- DoResolve;
- end else
- begin
- // Close the connection
- FActive := False;
- try
- FreeAndNil(FStream);
- if Assigned(CanWriteNotifyHandle) then
- begin
- EventLoop.ClearCanWriteNotify(CanWriteNotifyHandle);
- CanWriteNotifyHandle := nil;
- end;
- if Assigned(RetryTimerNotifyHandle) then
- begin
- EventLoop.RemoveTimerNotify(RetryTimerNotifyHandle);
- RetryTimerNotifyHandle := nil;
- end;
- finally
- SetConnectionState(connDisconnected);
- end;
- end;
- end;
- end;
- procedure TClientConnectionSocket.SetConnectionState(NewState:
- TConnectionState);
- var
- OldState: TConnectionState;
- begin
- if NewState <> ConnectionState then
- begin
- OldState := ConnectionState;
- FConnectionState := NewState;
- if Assigned(OnConnectionStateChange) then
- OnConnectionStateChange(Self, OldState, NewState);
- end;
- end;
- procedure TClientConnectionSocket.RetryTimerNotify(Sender: TObject);
- begin
- RetryTimerNotifyHandle := nil;
- Active := True;
- end;
- procedure TClientConnectionSocket.SocketCanWrite(Sender: TObject);
- var
- Error: Integer;
- ErrorLen, GetResult: LongInt;
- begin
- if ConnectionState = connConnecting then
- begin
- EventLoop.ClearCanWriteNotify(CanWriteNotifyHandle);
- CanWriteNotifyHandle := nil;
- ErrorLen := SizeOf(Error);
- GetResult := Sockets.fpGetSockOpt(Stream.Handle, SOL_SOCKET, SO_ERROR,
- @Error, @ErrorLen);
- if GetResult <> 0 then
- raise ESocketError.CreateFmt(SSocketConnectFailed,
- [GetPeerName, StrError(GetResult)]);
- if Error <> 0 then
- if (RetryCounter >= Retries) and (Retries >= 0) then
- raise ESocketError.CreateFmt(SSocketConnectFailed,
- [GetPeerName, StrError(Error)])
- else begin
- Active := False;
- RetryTimerNotifyHandle := EventLoop.AddTimerNotify(RetryDelay, False,
- @RetryTimerNotify, Self);
- Inc(RetryCounter);
- end
- else
- begin
- RetryCounter := 0;
- SetConnectionState(connConnected);
- end;
- end;
- end;
- // TSocketConnectionServer
- procedure TSocketConnectionServer.ListenerDataAvailable(Sender: TObject);
- var
- ClientSocket: Integer;
- Addr: TInetSockAddr;
- AddrSize: Integer;
- begin
- AddrSize := SizeOf(Addr);
- ClientSocket := FpAccept(Stream.Handle, @Addr, @AddrSize);
- if ClientSocket = -1 then
- raise ESocketError.CreateFmt(SSocketAcceptError, [StrError(SocketError)]);
- if DoQueryConnect(ClientSocket) then
- DoConnect(TSocketStream.Create(ClientSocket));
- end;
- function TSocketConnectionServer.DoQueryConnect(ASocket: Integer): Boolean;
- begin
- Result := True;
- if Assigned(OnQueryConnect) then
- OnQueryConnect(Self, ASocket, Result);
- end;
- procedure TSocketConnectionServer.DoConnect(AStream: TSocketStream);
- begin
- if Assigned(OnConnect) then
- OnConnect(Self, AStream);
- end;
- // TCustomTCPClient
- type
- TClientSocketStream = class(TSocketStream)
- protected
- Client: TCustomTCPClient;
- procedure Disconnected; override;
- end;
- procedure TClientSocketStream.Disconnected;
- begin
- inherited Disconnected;
- Client.Active := False;
- end;
- destructor TCustomTCPClient.Destroy;
- begin
- if Assigned(CanWriteNotifyHandle) then
- begin
- EventLoop.ClearCanWriteNotify(CanWriteNotifyHandle);
- // Set to nil to be sure that descendant classes don't do something stupid
- CanWriteNotifyHandle := nil;
- end;
- inherited Destroy;
- end;
- procedure TCustomTCPClient.SetHost(const Value: String);
- begin
- if Value <> Host then
- begin
- if Active then
- raise ESocketError.Create(SSocketIsActive);
- FHost := Value;
- end;
- end;
- procedure TCustomTCPClient.SetPort(Value: Word);
- begin
- if Value <> Port then
- begin
- if Active then
- raise ESocketError.Create(SSocketIsActive);
- FPort := Value;
- end;
- end;
- procedure TCustomTCPClient.DoResolve;
- var
- HostResolver: THostResolver;
- begin
- HostAddr := StrToNetAddr(Host);
- if HostAddr.s_bytes[4] = 0 then
- begin
- HostResolver := THostResolver.Create(nil);
- try
- SetConnectionState(connResolving);
- if not HostResolver.NameLookup(FHost) then
- raise ESocketError.CreateFmt(SHostNotFound, [Host]);
- HostAddr := HostResolver.HostAddress;
- finally
- HostResolver.Free;
- end;
- end;
- DoConnect;
- end;
- procedure TCustomTCPClient.CreateSocket;
- var
- Socket: Integer;
- begin
- Socket := Sockets.FPSocket(AF_INET, SOCK_STREAM, 0);
- if Socket = -1 then
- raise ESocketError.CreateFmt(SSocketCreationError,
- [StrError(SocketError)]);
- FStream := TClientSocketStream.Create(Socket);
- TClientSocketStream(FStream).Client := Self;
- end;
- procedure TCustomTCPClient.DoConnect;
- var
- SockAddr: TInetSockAddr;
- begin
- inherited DoConnect;
- SockAddr.Family := AF_INET;
- SockAddr.Port := ShortHostToNet(Port);
- SockAddr.Addr := Cardinal(HostAddr);
- if Sockets.FpConnect(Stream.Handle, @SockAddr, SizeOf(SockAddr))<>0 Then
- if (SocketError <> sys_EINPROGRESS) and (SocketError <> 0) then
- raise ESocketError.CreateFmt(SSocketConnectFailed,
- [GetPeerName, StrError(SocketError)]);
- end;
- function TCustomTCPClient.GetPeerName: String;
- begin
- Result := Format('%s:%d', [Host, Port]);
- end;
- // TCustomTCPServer
- destructor TCustomTCPServer.Destroy;
- begin
- if Assigned(DataAvailableNotifyHandle) then
- begin
- EventLoop.ClearDataAvailableNotify(DataAvailableNotifyHandle);
- // Set to nil to be sure that descendant classes don't do something stupid
- DataAvailableNotifyHandle := nil;
- end;
- inherited Destroy;
- end;
- procedure TCustomTCPServer.SetActive(Value: Boolean);
- var
- Socket, TrueValue: Integer;
- Addr: TInetSockAddr;
- begin
- if Active <> Value then
- begin
- FActive := False;
- if Value then
- begin
- Socket := Sockets.fpSocket(AF_INET, SOCK_STREAM, 0);
- if Socket = -1 then
- raise ESocketError.CreateFmt(SSocketCreationError,
- [StrError(SocketError)]);
- TrueValue := 1;
- Sockets.fpSetSockOpt(Socket, SOL_SOCKET, SO_REUSEADDR,
- @TrueValue, SizeOf(TrueValue));
- FStream := TSocketStream.Create(Socket);
- Addr.Family := AF_INET;
- Addr.Port := ShortHostToNet(Port);
- Addr.Addr := 0;
- if fpBind(Socket, @Addr, SizeOf(Addr))<>0 then
- raise ESocketError.CreateFmt(SSocketBindingError,
- [Port, StrError(SocketError)]);
- fpListen(Socket, 5);
- if not Assigned(EventLoop) then
- raise ESocketError.Create(SSocketNoEventLoopAssigned);
- DataAvailableNotifyHandle := EventLoop.SetDataAvailableNotify(Socket,
- @ListenerDataAvailable, nil);
- FActive := True;
- end else
- begin
- FreeAndNil(FStream);
- EventLoop.ClearDataAvailableNotify(DataAvailableNotifyHandle);
- DataAvailableNotifyHandle := nil;
- end;
- end;
- end;
- end.
|