fpc/fcl/net/fpsock.pp

246 lines
5.8 KiB
ObjectPascal

{
$Id$
Socket components
Copyright (c) 2003 by
Areca Systems GmbH / Sebastian Guenther, sg@freepascal.org
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.
}
unit fpSock;
interface
uses SysUtils, Sockets, Classes, fpAsync;
type
ESocketError = class(Exception)
end;
TSocketComponent = class(TComponent)
private
FEventLoop: TEventLoop;
protected
DataAvailableNotifyHandle: Pointer;
public
destructor Destroy; override;
property EventLoop: TEventLoop read FEventLoop write FEventLoop;
end;
TSocketStream = class(THandleStream)
private
FOnDisconnect: TNotifyEvent;
function GetLocalAddress: TSockAddr;
function GetPeerAddress: TSockAddr;
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;
// TCP/IP components
TTCPConnection = class(TSocketComponent)
end;
TTCPClient = class(TTCPConnection)
end;
TCustomTCPServer = class;
TQueryConnectEvent = procedure(Sender: TCustomTCPServer; Socket: Integer;
var DoConnect: Boolean) of object;
TConnectEvent = procedure(Sender: TCustomTCPServer;
Stream: TSocketStream) of object;
TCustomTCPServer = class(TTCPConnection)
private
FActive: Boolean;
FPort: Word;
FOnQueryConnect: TQueryConnectEvent;
FOnConnect: TConnectEvent;
procedure SetActive(Value: Boolean);
procedure ListenerDataAvailable(Sender: TObject);
protected
FSocket: Integer;
function DoQueryConnect(ASocket: Integer): Boolean; virtual;
procedure DoConnect(AStream: TSocketStream); virtual;
//!!!: Interface/bindings list?
property Active: Boolean read FActive write SetActive;
property Port: Word read FPort write FPort;
property OnQueryConnect: TQueryConnectEvent read FOnQueryConnect
write FOnQueryConnect;
property OnConnect: TConnectEvent read FOnConnect write FOnConnect;
end;
TTCPServer = class(TCustomTCPServer)
public
property Socket: Integer read FSocket;
published
property Active;
property Port;
property OnQueryConnect;
property OnConnect;
end;
// UDP/IP components
TUDPBase = class(TSocketComponent)
end;
TUDPClient = class(TUDPBase)
end;
TUDPServer = class(TUDPBase)
end;
implementation
uses Resolve;
resourcestring
SSocketCreationError = 'Could not create socket';
SSocketBindingError = 'Could not bind socket to port %d';
SSocketAcceptError = 'Connection accept failed';
destructor TSocketComponent.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;
destructor TSocketStream.Destroy;
begin
FileClose(Handle);
inherited Destroy;
end;
function TSocketStream.Read(var Buffer; Count: LongInt): LongInt;
begin
Result := recv(Handle, Buffer, Count, MSG_NOSIGNAL);
if Result = -1 then
begin
Result := 0;
if Assigned(OnDisconnect) then
OnDisconnect(Self);
end;
end;
function TSocketStream.Write(const Buffer; Count: LongInt): LongInt;
begin
Result := send(Handle, Buffer, Count, MSG_NOSIGNAL);
if Result = -1 then
begin
Result := 0;
if Assigned(OnDisconnect) then
OnDisconnect(Self);
end;
end;
function TSocketStream.GetLocalAddress: TSockAddr;
var
len: LongInt;
begin
len := SizeOf(TSockAddr);
if GetSocketName(Handle, Result, len) <> 0 then
FillChar(Result, SizeOf(Result), 0);
end;
function TSocketStream.GetPeerAddress: TSockAddr;
var
len: LongInt;
begin
len := SizeOf(TSockAddr);
if GetPeerName(Handle, Result, len) <> 0 then
FillChar(Result, SizeOf(Result), 0);
end;
function TCustomTCPServer.DoQueryConnect(ASocket: Integer): Boolean;
begin
Result := True;
if Assigned(OnQueryConnect) then
OnQueryConnect(Self, ASocket, Result);
end;
procedure TCustomTCPServer.DoConnect(AStream: TSocketStream);
begin
if Assigned(OnConnect) then
OnConnect(Self, AStream);
end;
procedure TCustomTCPServer.SetActive(Value: Boolean);
var
Addr: TInetSockAddr;
begin
if Active <> Value then
begin
FActive := False;
if Value then
begin
FSocket := Sockets.Socket(AF_INET, SOCK_STREAM, 0);
if FSocket = -1 then
raise ESocketError.Create(SSocketCreationError);
Addr.Family := AF_INET;
Addr.Port := ShortHostToNet(Port);
Addr.Addr := 0;
if not Bind(FSocket, Addr, SizeOf(Addr)) then
raise ESocketError.CreateFmt(SSocketBindingError, [Port]);
Listen(FSocket, 5);
DataAvailableNotifyHandle := EventLoop.SetDataAvailableNotify(FSocket,
@ListenerDataAvailable, nil);
end else
begin
FileClose(FSocket);
EventLoop.ClearDataAvailableNotify(DataAvailableNotifyHandle);
DataAvailableNotifyHandle := nil;
end;
end;
end;
procedure TCustomTCPServer.ListenerDataAvailable(Sender: TObject);
var
ClientSocket: Integer;
Addr: TInetSockAddr;
AddrSize: Integer;
begin
AddrSize := SizeOf(Addr);
ClientSocket := Accept(FSocket, Addr, AddrSize);
if ClientSocket = -1 then
raise ESocketError.Create(SSocketAcceptError);
if DoQueryConnect(ClientSocket) then
DoConnect(TSocketStream.Create(ClientSocket));
end;
end.
{
$Log$
Revision 1.1 2003-11-22 11:55:28 sg
* First version, a simple starting point for further development
}