blob: f0cab79db281c315ea5f503a5751adbd168091c4 [file] [log] [blame]
(*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*)
unit Thrift.Socket;
{$I Thrift.Defines.inc}
{$I-} // prevent annoying errors with default log delegate and no console
interface
{$IFNDEF OLD_SOCKETS} // not for OLD_SOCKETS
uses
Winapi.Windows, Winapi.Winsock2;
const
AI_PASSIVE = $00000001; // Socket address will be used in bind() call
AI_CANONNAME = $00000002; // Return canonical name in first ai_canonname
AI_NUMERICHOST = $00000004; // Nodename must be a numeric address string
AI_NUMERICSERV = $00000008; // Servicename must be a numeric port number
AI_ALL = $00000100; // Query both IP6 and IP4 with AI_V4MAPPED
AI_ADDRCONFIG = $00000400; // Resolution only if global address configured
AI_V4MAPPED = $00000800; // On v6 failure, query v4 and convert to V4MAPPED format
AI_NON_AUTHORITATIVE = $00004000; // LUP_NON_AUTHORITATIVE
AI_SECURE = $00008000; // LUP_SECURE
AI_RETURN_PREFERRED_NAMES = $00010000; // LUP_RETURN_PREFERRED_NAMES
AI_FQDN = $00020000; // Return the FQDN in ai_canonname
AI_FILESERVER = $00040000; // Resolving fileserver name resolution
type
PAddrInfoA = ^TAddrInfoA;
TAddrInfoA = record
ai_flags: Integer;
ai_family: Integer;
ai_socktype: Integer;
ai_protocol: Integer;
ai_addrlen: NativeUInt;
ai_canonname: PAnsiChar;
ai_addr: PSockAddr;
ai_next: PAddrInfoA;
end;
PAddrInfoW = ^TAddrInfoW;
TAddrInfoW = record
ai_flags: Integer;
ai_family: Integer;
ai_socktype: Integer;
ai_protocol: Integer;
ai_addrlen: NativeUInt;
ai_canonname: PChar;
ai_addr: PSockAddr;
ai_next: PAddrInfoW;
end;
TAddressFamily = USHORT;
TIn6Addr = record
case Integer of
0: (_Byte: array[0..15] of UCHAR);
1: (_Word: array[0..7] of USHORT);
end;
TScopeId = record
public
Value: ULONG;
private
function GetBitField(Loc: Integer): Integer; inline;
procedure SetBitField(Loc: Integer; const aValue: Integer); inline;
public
property Zone: Integer index $0028 read GetBitField write SetBitField;
property Level: Integer index $2804 read GetBitField write SetBitField;
end;
TSockAddrIn6 = record
sin6_family: TAddressFamily;
sin6_port: USHORT;
sin6_flowinfo: ULONG;
sin6_addr: TIn6Addr;
case Integer of
0: (sin6_scope_id: ULONG);
1: (sin6_scope_struct: TScopeId);
end;
PSockAddrIn6 = ^TSockAddrIn6;
const
NI_NOFQDN = $01; // Only return nodename portion for local hosts
NI_NUMERICHOST = $02; // Return numeric form of the host's address
NI_NAMEREQD = $04; // Error if the host's name not in DNS
NI_NUMERICSERV = $08; // Return numeric form of the service (port #)
NI_DGRAM = $10; // Service is a datagram service
NI_MAXHOST = 1025; // Max size of a fully-qualified domain name
NI_MAXSERV = 32; // Max size of a service name
function getaddrinfo(pNodeName, pServiceName: PAnsiChar; const pHints: TAddrInfoA; var ppResult: PAddrInfoA): Integer; stdcall;
function GetAddrInfoW(pNodeName, pServiceName: PWideChar; const pHints: TAddrInfoW; var ppResult: PAddrInfoW): Integer; stdcall;
procedure freeaddrinfo(pAddrInfo: PAddrInfoA); stdcall;
procedure FreeAddrInfoW(pAddrInfo: PAddrInfoW); stdcall;
function getnameinfo(const pSockaddr: TSockAddr; SockaddrLength: Integer; pNodeBuffer: PAnsiChar; NodeBufferSize: DWORD; pServiceBuffer: PAnsiChar;
ServiceBufferSize: DWORD; Flags: Integer): Integer; stdcall;
function GetNameInfoW(const pSockaddr: TSockAddr; SockaddrLength: Integer; pNodeBuffer: PWideChar; NodeBufferSize: DWORD; pServiceBuffer: PWideChar;
ServiceBufferSize: DWORD; Flags: Integer): Integer; stdcall;
type
TSmartPointerDestroyer<T> = reference to procedure(Value: T);
ISmartPointer<T> = reference to function: T;
TSmartPointer<T> = class(TInterfacedObject, ISmartPointer<T>)
private
FValue: T;
FDestroyer: TSmartPointerDestroyer<T>;
public
constructor Create(AValue: T; ADestroyer: TSmartPointerDestroyer<T>);
destructor Destroy; override;
function Invoke: T;
end;
TBaseSocket = class abstract
public type
TLogDelegate = reference to procedure( const str: string);
strict private
FPort: Integer;
FSocket: Winapi.Winsock2.TSocket;
FSendTimeout,
FRecvTimeout: Longword;
FKeepAlive: Boolean;
FLogDelegate: TLogDelegate;
class constructor Create;
class destructor Destroy;
class procedure DefaultLogDelegate(const Str: string);
protected type
IGetAddrInfoWrapper = interface
function Init: Integer;
function GetRes: PAddrInfoW;
property Res: PAddrInfoW read GetRes;
end;
TGetAddrInfoWrapper = class(TInterfacedObject, IGetAddrInfoWrapper)
strict private
FNode: string;
FService: string;
FHints,
FRes: PAddrInfoW;
public
constructor Create(ANode, AService: string; AHints: PAddrInfoW);
destructor Destroy; override;
function Init: Integer;
function GetRes: PAddrInfoW;
property Res: PAddrInfoW read GetRes;
end;
strict protected
procedure CommonInit; virtual;
function CreateSocket(AAddress: string; APort: Integer): IGetAddrInfoWrapper;
procedure SetRecvTimeout(ARecvTimeout: Longword); virtual;
procedure SetSendTimeout(ASendTimeout: Longword); virtual;
procedure SetKeepAlive(AKeepAlive: Boolean); virtual;
procedure SetSocket(ASocket: Winapi.Winsock2.TSocket);
property LogDelegate: TLogDelegate read FLogDelegate;
public
//
// Constructs a new socket. Note that this does NOT actually connect the
// socket.
//
constructor Create(ALogDelegate: TLogDelegate = nil); overload;
constructor Create(APort: Integer; ALogDelegate: TLogDelegate = nil); overload;
//
// Destroys the socket object, closing it if necessary.
//
destructor Destroy; override;
//
// Shuts down communications on the socket
//
procedure Close; virtual;
// The port that the socket is connected to
property Port: Integer read FPort write FPort;
// The receive timeout
property RecvTimeout: Longword read FRecvTimeout write SetRecvTimeout;
// The send timeout
property SendTimeout: Longword read FSendTimeout write SetSendTimeout;
// Set SO_KEEPALIVE
property KeepAlive: Boolean read FKeepAlive write SetKeepAlive;
// The underlying socket descriptor
property Socket: Winapi.Winsock2.TSocket read FSocket write SetSocket;
end;
TSocket = class(TBaseSocket)
strict private type
TCachedPeerAddr = record
case Integer of
0: (ipv4: TSockAddrIn);
1: (ipv6: TSockAddrIn6);
end;
strict private
FHost: string;
FPeerHost: string;
FPeerAddress: string;
FPeerPort: Integer;
FInterruptListener: ISmartPointer<Winapi.Winsock2.TSocket>;
FConnTimeout: Longword;
FLingerOn: Boolean;
FLingerVal: Integer;
FNoDelay: Boolean;
FMaxRecvRetries: Longword;
FCachedPeerAddr: TCachedPeerAddr;
procedure InitPeerInfo;
procedure OpenConnection(Res: TBaseSocket.IGetAddrInfoWrapper);
procedure LocalOpen;
procedure SetGenericTimeout(S: Winapi.Winsock2.TSocket; Timeout: Longword; OptName: Integer);
function GetIsOpen: Boolean;
procedure SetNoDelay(ANoDelay: Boolean);
function GetSocketInfo: string;
function GetPeerHost: string;
function GetPeerAddress: string;
function GetPeerPort: Integer;
function GetOrigin: string;
strict protected
procedure CommonInit; override;
procedure SetRecvTimeout(ARecvTimeout: Longword); override;
procedure SetSendTimeout(ASendTimeout: Longword); override;
procedure SetKeepAlive(AKeepAlive: Boolean); override;
public
//
// Constructs a new socket. Note that this does NOT actually connect the
// socket.
//
constructor Create(ALogDelegate: TBaseSocket.TLogDelegate = nil); overload;
//
// Constructs a new socket. Note that this does NOT actually connect the
// socket.
//
// @param host An IP address or hostname to connect to
// @param port The port to connect on
//
constructor Create(AHost: string; APort: Integer; ALogDelegate: TBaseSocket.TLogDelegate = nil); overload;
//
// Constructor to create socket from socket descriptor.
//
constructor Create(ASocket: Winapi.Winsock2.TSocket; ALogDelegate: TBaseSocket.TLogDelegate = nil); overload;
//
// Constructor to create socket from socket descriptor that
// can be interrupted safely.
//
constructor Create(ASocket: Winapi.Winsock2.TSocket; AInterruptListener: ISmartPointer<Winapi.Winsock2.TSocket>;
ALogDelegate: TBaseSocket.TLogDelegate = nil); overload;
//
// Creates and opens the socket
//
// @throws ETransportationException If the socket could not connect
//
procedure Open;
//
// Shuts down communications on the socket
//
procedure Close; override;
//
// Reads from the underlying socket.
// \returns the number of bytes read or 0 indicates EOF
// \throws TTransportException of types:
// Interrupted means the socket was interrupted
// out of a blocking call
// NotOpen means the socket has been closed
// TimedOut means the receive timeout expired
// Unknown means something unexpected happened
//
function Read(var Buf; Len: Integer): Integer;
//
// Writes to the underlying socket. Loops until done or fail.
//
procedure Write(const Buf; Len: Integer);
//
// Writes to the underlying socket. Does single send() and returns result.
//
function WritePartial(const Buf; Len: Integer): Integer;
//
// Returns a cached copy of the peer address.
//
function GetCachedAddress(out Len: Integer): PSockAddr;
//
// Set a cache of the peer address (used when trivially available: e.g.
// accept() or connect()). Only caches IPV4 and IPV6; unset for others.
//
procedure SetCachedAddress(const Addr: TSockAddr; Len: Integer);
//
// Controls whether the linger option is set on the socket.
//
// @param on Whether SO_LINGER is on
// @param linger If linger is active, the number of seconds to linger for
//
procedure SetLinger(LingerOn: Boolean; LingerVal: Integer);
//
// Calls select() on the socket to see if there is more data available.
//
function Peek: Boolean;
// Whether the socket is alive
property IsOpen: Boolean read GetIsOpen;
// The host that the socket is connected to
property Host: string read FHost write FHost;
// Whether to enable or disable Nagle's algorithm
property NoDelay: Boolean read FNoDelay write SetNoDelay;
// Connect timeout
property ConnTimeout: Longword read FConnTimeout write FConnTimeout;
// The max number of recv retries in the case of a WSAEWOULDBLOCK
property MaxRecvRetries: Longword read FMaxRecvRetries write FMaxRecvRetries;
// Socket information formatted as a string <Host: x Port: x>
property SocketInfo: string read GetSocketInfo;
// The DNS name of the host to which the socket is connected
property PeerHost: string read GetPeerHost;
// The address of the host to which the socket is connected
property PeerAddress: string read GetPeerAddress;
// The port of the host to which the socket is connected
property PeerPort: Integer read GetPeerPort;
// The origin the socket is connected to
property Origin: string read GetOrigin;
end;
TServerSocketFunc = reference to procedure(sock: Winapi.Winsock2.TSocket);
TServerSocket = class(TBaseSocket)
strict private
FAddress: string;
FAcceptBacklog,
FRetryLimit,
FRetryDelay,
FTcpSendBuffer,
FTcpRecvBuffer: Integer;
FAcceptTimeout: Longword;
FListening,
FInterruptableChildren: Boolean;
FInterruptSockWriter, // is notified on Interrupt()
FInterruptSockReader, // is used in select with FSocket for interruptability
FChildInterruptSockWriter: Winapi.Winsock2.TSocket; // is notified on InterruptChildren()
FChildInterruptSockReader: ISmartPointer<Winapi.Winsock2.TSocket>; // if FnterruptableChildren this is shared with child TSockets
FListenCallback,
FAcceptCallback: TServerSocketFunc;
function CreateSocketObj(Client: Winapi.Winsock2.TSocket): TSocket;
procedure Notify(NotifySocket: Winapi.Winsock2.TSocket);
procedure SetInterruptableChildren(AValue: Boolean);
strict protected
procedure CommonInit; override;
public const
DEFAULT_BACKLOG = 1024;
public
//
// Constructor.
//
// @param port Port number to bind to
//
constructor Create(APort: Integer; ALogDelegate: TBaseSocket.TLogDelegate = nil); overload;
//
// Constructor.
//
// @param port Port number to bind to
// @param sendTimeout Socket send timeout
// @param recvTimeout Socket receive timeout
//
constructor Create(APort: Integer; ASendTimeout, ARecvTimeout: Longword; ALogDelegate: TBaseSocket.TLogDelegate = nil); overload;
//
// Constructor.
//
// @param address Address to bind to
// @param port Port number to bind to
//
constructor Create(AAddress: string; APort: Integer; ALogDelegate: TBaseSocket.TLogDelegate = nil); overload;
procedure Listen;
function Accept: TSocket;
procedure Interrupt;
procedure InterruptChildren;
procedure Close; override;
property AcceptBacklog: Integer read FAcceptBacklog write FAcceptBacklog;
property AcceptTimeout: Longword read FAcceptTimeout write FAcceptTimeout;
property RetryLimit: Integer read FRetryLimit write FRetryLimit;
property RetryDelay: Integer read FRetryDelay write FRetryDelay;
property TcpSendBuffer: Integer read FTcpSendBuffer write FTcpSendBuffer;
property TcpRecvBuffer: Integer read FTcpRecvBuffer write FTcpRecvBuffer;
// When enabled (the default), new children TSockets will be constructed so
// they can be interrupted by TServerTransport.InterruptChildren().
// This is more expensive in terms of system calls (poll + recv) however
// ensures a connected client cannot interfere with TServer.Stop().
//
// When disabled, TSocket children do not incur an additional poll() call.
// Server-side reads are more efficient, however a client can interfere with
// the server's ability to shutdown properly by staying connected.
//
// Must be called before listen(); mode cannot be switched after that.
// \throws EPropertyError if listen() has been called
property InterruptableChildren: Boolean read FInterruptableChildren write SetInterruptableChildren;
// listenCallback gets called just before listen, and after all Thrift
// setsockopt calls have been made. If you have custom setsockopt
// things that need to happen on the listening socket, this is the place to do it.
property ListenCallback: TServerSocketFunc read FListenCallback write FListenCallback;
// acceptCallback gets called after each accept call, on the newly created socket.
// It is called after all Thrift setsockopt calls have been made. If you have
// custom setsockopt things that need to happen on the accepted
// socket, this is the place to do it.
property AcceptCallback: TServerSocketFunc read FAcceptCallback write FAcceptCallback;
end;
{$ENDIF} // not for OLD_SOCKETS
implementation
{$IFNDEF OLD_SOCKETS} // not for OLD_SOCKETS
uses
System.SysUtils, System.Math, System.DateUtils, Thrift.Transport;
constructor TBaseSocket.TGetAddrInfoWrapper.Create(ANode, AService: string; AHints: PAddrInfoW);
begin
inherited Create;
FNode := ANode;
FService := AService;
FHints := AHints;
FRes := nil;
end;
destructor TBaseSocket.TGetAddrInfoWrapper.Destroy;
begin
if Assigned(FRes) then
FreeAddrInfoW(FRes);
inherited Destroy;
end;
function TBaseSocket.TGetAddrInfoWrapper.Init: Integer;
begin
if FRes = nil then
Exit(GetAddrInfoW(@FNode[1], @FService[1], FHints^, FRes));
Result := 0;
end;
function TBaseSocket.TGetAddrInfoWrapper.GetRes: PAddrInfoW;
begin
Result := FRes;
end;
procedure DestroyerOfFineSockets(ssock: Winapi.Winsock2.TSocket);
begin
closesocket(ssock);
end;
function TScopeId.GetBitField(Loc: Integer): Integer;
begin
Result := (Value shr (Loc shr 8)) and ((1 shl (Loc and $FF)) - 1);
end;
procedure TScopeId.SetBitField(Loc: Integer; const aValue: Integer);
begin
Value := (Value and ULONG((not ((1 shl (Loc and $FF)) - 1)))) or ULONG(aValue shl (Loc shr 8));
end;
function getaddrinfo; external 'ws2_32.dll' name 'getaddrinfo';
function GetAddrInfoW; external 'ws2_32.dll' name 'GetAddrInfoW';
procedure freeaddrinfo; external 'ws2_32.dll' name 'freeaddrinfo';
procedure FreeAddrInfoW; external 'ws2_32.dll' name 'FreeAddrInfoW';
function getnameinfo; external 'ws2_32.dll' name 'getnameinfo';
function GetNameInfoW; external 'ws2_32.dll' name 'GetNameInfoW';
constructor TSmartPointer<T>.Create(AValue: T; ADestroyer: TSmartPointerDestroyer<T>);
begin
inherited Create;
FValue := AValue;
FDestroyer := ADestroyer;
end;
destructor TSmartPointer<T>.Destroy;
begin
if Assigned(FDestroyer) then FDestroyer(FValue);
inherited Destroy;
end;
function TSmartPointer<T>.Invoke: T;
begin
Result := FValue;
end;
class constructor TBaseSocket.Create;
var
Version: WORD;
Data: WSAData;
Error: Integer;
begin
Version := $0202;
FillChar(Data, SizeOf(Data), 0);
Error := WSAStartup(Version, Data);
if Error <> 0 then
raise Exception.Create('Failed to initialize Winsock.');
end;
class destructor TBaseSocket.Destroy;
begin
WSACleanup;
end;
class procedure TBaseSocket.DefaultLogDelegate(const Str: string);
var
OutStr: string;
begin
OutStr := Format('Thrift: %s %s', [DateTimeToStr(Now, TFormatSettings.Create), Str]);
try
Writeln(OutStr);
if IoResult <> 0 then OutputDebugString(PChar(OutStr));
except
OutputDebugString(PChar(OutStr));
end;
end;
procedure TBaseSocket.CommonInit;
begin
FSocket := INVALID_SOCKET;
FPort := 0;
FSendTimeout := 0;
FRecvTimeout := 0;
FKeepAlive := False;
FLogDelegate := DefaultLogDelegate;
end;
function TBaseSocket.CreateSocket(AAddress: string; APort: Integer): IGetAddrInfoWrapper;
var
Hints: TAddrInfoW;
Res: PAddrInfoW;
ThePort: array[0..5] of Char;
Error: Integer;
begin
FillChar(Hints, SizeOf(Hints), 0);
Hints.ai_family := PF_UNSPEC;
Hints.ai_socktype := SOCK_STREAM;
Hints.ai_flags := AI_PASSIVE or AI_ADDRCONFIG;
StrFmt(ThePort, '%d', [FPort]);
Result := TGetAddrInfoWrapper.Create(AAddress, ThePort, @Hints);
Error := Result.Init;
if Error <> 0 then begin
LogDelegate(Format('GetAddrInfoW %d: %s', [Error, SysErrorMessage(Error)]));
Close;
raise TTransportExceptionNotOpen.Create('Could not resolve host for server socket.');
end;
// Pick the ipv6 address first since ipv4 addresses can be mapped
// into ipv6 space.
Res := Result.Res;
while Assigned(Res) do begin
if (Res^.ai_family = AF_INET6) or (not Assigned(Res^.ai_next)) then
Break;
Res := Res^.ai_next;
end;
FSocket := Winapi.Winsock2.socket(Res^.ai_family, Res^.ai_socktype, Res^.ai_protocol);
if FSocket = INVALID_SOCKET then begin
Error := WSAGetLastError;
LogDelegate(Format('TBaseSocket.CreateSocket() socket() %s', [SysErrorMessage(Error)]));
Close;
raise TTransportExceptionNotOpen.Create(Format('socket(): %s', [SysErrorMessage(Error)]));
end;
end;
procedure TBaseSocket.SetRecvTimeout(ARecvTimeout: Longword);
begin
FRecvTimeout := ARecvTimeout;
end;
procedure TBaseSocket.SetSendTimeout(ASendTimeout: Longword);
begin
FSendTimeout := ASendTimeout;
end;
procedure TBaseSocket.SetKeepAlive(AKeepAlive: Boolean);
begin
FKeepAlive := AKeepAlive;
end;
procedure TBaseSocket.SetSocket(ASocket: Winapi.Winsock2.TSocket);
begin
if FSocket <> INVALID_SOCKET then
Close;
FSocket := ASocket;
end;
constructor TBaseSocket.Create(ALogDelegate: TLogDelegate);
begin
inherited Create;
CommonInit;
if Assigned(ALogDelegate) then FLogDelegate := ALogDelegate;
end;
constructor TBaseSocket.Create(APort: Integer; ALogDelegate: TLogDelegate);
begin
inherited Create;
CommonInit;
FPort := APort;
if Assigned(ALogDelegate) then FLogDelegate := ALogDelegate;
end;
destructor TBaseSocket.Destroy;
begin
Close;
inherited Destroy;
end;
procedure TBaseSocket.Close;
begin
if FSocket <> INVALID_SOCKET then begin
shutdown(FSocket, SD_BOTH);
closesocket(FSocket);
end;
FSocket := INVALID_SOCKET;
end;
procedure TSocket.InitPeerInfo;
begin
FCachedPeerAddr.ipv4.sin_family := AF_UNSPEC;
FPeerHost := '';
FPeerAddress := '';
FPeerPort := 0;
end;
procedure TSocket.CommonInit;
begin
inherited CommonInit;
FHost := '';
FInterruptListener := nil;
FConnTimeout := 0;
FLingerOn := True;
FLingerVal := 0;
FNoDelay := True;
FMaxRecvRetries := 5;
InitPeerInfo;
end;
procedure TSocket.OpenConnection(Res: TBaseSocket.IGetAddrInfoWrapper);
label
Done;
var
ErrnoCopy: Integer;
Ret,
Ret2: Integer;
Fds: TFdSet;
TVal: TTimeVal;
PTVal: PTimeVal;
Val,
Lon: Integer;
One,
Zero: Cardinal;
begin
if SendTimeout > 0 then SetSendTimeout(SendTimeout);
if RecvTimeout > 0 then SetRecvTimeout(RecvTimeout);
if KeepAlive then SetKeepAlive(KeepAlive);
SetLinger(FLingerOn, FLingerVal);
SetNoDelay(FNoDelay);
// Set the socket to be non blocking for connect if a timeout exists
Zero := 0;
if FConnTimeout > 0 then begin
One := 1;
if ioctlsocket(Socket, Integer(FIONBIO), One) = SOCKET_ERROR then begin
ErrnoCopy := WSAGetLastError;
LogDelegate(Format('TSocket.OpenConnection() ioctlsocket() %s %s', [SocketInfo, SysErrorMessage(ErrnoCopy)]));
raise TTransportExceptionNotOpen.Create(Format('ioctlsocket() failed: %s', [SysErrorMessage(ErrnoCopy)]));
end;
end
else begin
if ioctlsocket(Socket, Integer(FIONBIO), Zero) = SOCKET_ERROR then begin
ErrnoCopy := WSAGetLastError;
LogDelegate(Format('TSocket.OpenConnection() ioctlsocket() %s %s', [SocketInfo, SysErrorMessage(ErrnoCopy)]));
raise TTransportExceptionNotOpen.Create(Format('ioctlsocket() failed: %s', [SysErrorMessage(ErrnoCopy)]));
end;
end;
Ret := connect(Socket, Res.Res^.ai_addr^, Res.Res^.ai_addrlen);
if Ret = 0 then goto Done;
ErrnoCopy := WSAGetLastError;
if (ErrnoCopy <> WSAEINPROGRESS) and (ErrnoCopy <> WSAEWOULDBLOCK) then begin
LogDelegate(Format('TSocket.OpenConnection() connect() ', [SocketInfo, SysErrorMessage(ErrnoCopy)]));
raise TTransportExceptionNotOpen.Create(Format('connect() failed: %s', [SysErrorMessage(ErrnoCopy)]));
end;
FD_ZERO(Fds);
_FD_SET(Socket, Fds);
if FConnTimeout > 0 then begin
TVal.tv_sec := FConnTimeout div 1000;
TVal.tv_usec := (FConnTimeout mod 1000) * 1000;
PTVal := @TVal;
end
else
PTVal := nil;
Ret := select(1, nil, @Fds, nil, PTVal);
if Ret > 0 then begin
// Ensure the socket is connected and that there are no errors set
Lon := SizeOf(Val);
Ret2 := getsockopt(Socket, SOL_SOCKET, SO_ERROR, @Val, Lon);
if Ret2 = SOCKET_ERROR then begin
ErrnoCopy := WSAGetLastError;
LogDelegate(Format('TSocket.OpenConnection() getsockopt() ', [SocketInfo, SysErrorMessage(ErrnoCopy)]));
raise TTransportExceptionNotOpen.Create(Format('getsockopt(): %s', [SysErrorMessage(ErrnoCopy)]));
end;
// no errors on socket, go to town
if Val = 0 then goto Done;
LogDelegate(Format('TSocket.OpenConnection() error on socket (after select()) ', [SocketInfo, SysErrorMessage(ErrnoCopy)]));
raise TTransportExceptionNotOpen.Create(Format('socket OpenConnection() error: %s', [SysErrorMessage(Val)]));
end
else if Ret = 0 then begin
// socket timed out
LogDelegate(Format('TSocket.OpenConnection() timed out ', [SocketInfo, SysErrorMessage(ErrnoCopy)]));
raise TTransportExceptionNotOpen.Create('OpenConnection() timed out');
end
else begin
// error on select()
ErrnoCopy := WSAGetLastError;
LogDelegate(Format('TSocket.OpenConnection() select() ', [SocketInfo, SysErrorMessage(ErrnoCopy)]));
raise TTransportExceptionNotOpen.Create(Format('select() failed: %s', [SysErrorMessage(ErrnoCopy)]));
end;
Done:
// Set socket back to normal mode (blocking)
ioctlsocket(Socket, Integer(FIONBIO), Zero);
SetCachedAddress(Res.Res^.ai_addr^, Res.Res^.ai_addrlen);
end;
procedure TSocket.LocalOpen;
var
Res: TBaseSocket.IGetAddrInfoWrapper;
begin
if IsOpen then Exit;
// Validate port number
if (Port < 0) or (Port > $FFFF) then
raise TTransportExceptionBadArgs.Create('Specified port is invalid');
Res := CreateSocket(Host, Port);
OpenConnection(Res);
end;
procedure TSocket.SetGenericTimeout(S: Winapi.Winsock2.TSocket; Timeout: Longword; OptName: Integer);
var
Time: DWORD;
begin
if S = INVALID_SOCKET then
Exit;
Time := Timeout;
if setsockopt(S, SOL_SOCKET, OptName, @Time, SizeOf(Time)) = SOCKET_ERROR then
LogDelegate(Format('SetGenericTimeout() setsockopt() %s', [SysErrorMessage(WSAGetLastError)]));
end;
function TSocket.GetIsOpen: Boolean;
begin
Result := Socket <> INVALID_SOCKET;
end;
procedure TSocket.SetNoDelay(ANoDelay: Boolean);
var
V: Integer;
begin
FNoDelay := ANoDelay;
if Socket = INVALID_SOCKET then
Exit;
V := IfThen(FNoDelay, 1, 0);
if setsockopt(Socket, IPPROTO_TCP, TCP_NODELAY, @V, SizeOf(V)) = SOCKET_ERROR then
LogDelegate(Format('TSocket.SetNoDelay() setsockopt() %s %s', [SocketInfo, SysErrorMessage(WSAGetLastError)]));
end;
function TSocket.GetSocketInfo: string;
begin
if (FHost = '') or (Port = 0) then
Result := '<Host: ' + GetPeerAddress + ' Port: ' + GetPeerPort.ToString + '>'
else
Result := '<Host: ' + FHost + ' Port: ' + Port.ToString + '>';
end;
function TSocket.GetPeerHost: string;
var
Addr: TSockAddrStorage;
AddrPtr: PSockAddr;
AddrLen: Integer;
ClientHost: array[0..NI_MAXHOST-1] of Char;
ClientService: array[0..NI_MAXSERV-1] of Char;
begin
if FPeerHost = '' then begin
if Socket = INVALID_SOCKET then
Exit(FPeerHost);
AddrPtr := GetCachedAddress(AddrLen);
if AddrPtr = nil then begin
AddrLen := SizeOf(Addr);
if getpeername(Socket, PSockAddr(@Addr)^, AddrLen) <> 0 then
Exit(FPeerHost);
AddrPtr := PSockAddr(@Addr);
SetCachedAddress(AddrPtr^, AddrLen);
end;
GetNameInfoW(AddrPtr^, AddrLen, ClientHost, NI_MAXHOST, ClientService, NI_MAXSERV, 0);
FPeerHost := ClientHost;
end;
Result := FPeerHost;
end;
function TSocket.GetPeerAddress: string;
var
Addr: TSockAddrStorage;
AddrPtr: PSockAddr;
AddrLen: Integer;
ClientHost: array[0..NI_MAXHOST-1] of Char;
ClientService: array[0..NI_MAXSERV-1] of Char;
begin
if FPeerAddress = '' then begin
if Socket = INVALID_SOCKET then
Exit(FPeerAddress);
AddrPtr := GetCachedAddress(AddrLen);
if AddrPtr = nil then begin
AddrLen := SizeOf(Addr);
if getpeername(Socket, PSockAddr(@Addr)^, AddrLen) <> 0 then
Exit(FPeerHost);
AddrPtr := PSockAddr(@Addr);
SetCachedAddress(AddrPtr^, AddrLen);
end;
GetNameInfoW(AddrPtr^, AddrLen, ClientHost, NI_MAXHOST, ClientService, NI_MAXSERV, NI_NUMERICHOST or NI_NUMERICSERV);
FPeerAddress := ClientHost;
TryStrToInt(ClientService, FPeerPort);
end;
Result := FPeerAddress
end;
function TSocket.GetPeerPort: Integer;
begin
GetPeerAddress;
Result := FPeerPort;
end;
function TSocket.GetOrigin: string;
begin
Result := GetPeerHost + ':' + GetPeerPort.ToString;
end;
procedure TSocket.SetRecvTimeout(ARecvTimeout: Longword);
begin
inherited SetRecvTimeout(ARecvTimeout);
SetGenericTimeout(Socket, ARecvTimeout, SO_RCVTIMEO);
end;
procedure TSocket.SetSendTimeout(ASendTimeout: Longword);
begin
inherited SetSendTimeout(ASendTimeout);
SetGenericTimeout(Socket, ASendTimeout, SO_SNDTIMEO);
end;
procedure TSocket.SetKeepAlive(AKeepAlive: Boolean);
var
Value: Integer;
begin
inherited SetKeepAlive(AKeepAlive);
Value := IfThen(KeepAlive, 1, 0);
if setsockopt(Socket, SOL_SOCKET, SO_KEEPALIVE, @Value, SizeOf(Value)) = SOCKET_ERROR then
LogDelegate(Format('TSocket.SetKeepAlive() setsockopt() %s %s', [SocketInfo, SysErrorMessage(WSAGetLastError)]));
end;
constructor TSocket.Create(ALogDelegate: TBaseSocket.TLogDelegate = nil);
begin
// Not needed, but just a placeholder
inherited Create(ALogDelegate);
end;
constructor TSocket.Create(AHost: string; APort: Integer; ALogDelegate: TBaseSocket.TLogDelegate);
begin
inherited Create(APort, ALogDelegate);
FHost := AHost;
end;
constructor TSocket.Create(ASocket: Winapi.Winsock2.TSocket; ALogDelegate: TBaseSocket.TLogDelegate);
begin
inherited Create(ALogDelegate);
Socket := ASocket;
end;
constructor TSocket.Create(ASocket: Winapi.Winsock2.TSocket; AInterruptListener: ISmartPointer<Winapi.Winsock2.TSocket>;
ALogDelegate: TBaseSocket.TLogDelegate);
begin
inherited Create(ALogDelegate);
Socket := ASocket;
FInterruptListener := AInterruptListener;
end;
procedure TSocket.Open;
begin
if IsOpen then Exit;
LocalOpen;
end;
procedure TSocket.Close;
begin
inherited Close;
InitPeerInfo;
end;
function TSocket.Read(var Buf; Len: Integer): Integer;
label
TryAgain;
var
Retries: Longword;
EAgainThreshold,
ReadElapsed: UInt64;
Start: TDateTime;
Got: Integer;
Fds: TFdSet;
ErrnoCopy: Integer;
TVal: TTimeVal;
PTVal: PTimeVal;
Ret: Integer;
begin
if Socket = INVALID_SOCKET then
raise TTransportExceptionNotOpen.Create('Called read on non-open socket');
Retries := 0;
// THRIFT_EAGAIN can be signalled both when a timeout has occurred and when
// the system is out of resources (an awesome undocumented feature).
// The following is an approximation of the time interval under which
// THRIFT_EAGAIN is taken to indicate an out of resources error.
EAgainThreshold := 0;
if RecvTimeout <> 0 then
// if a readTimeout is specified along with a max number of recv retries, then
// the threshold will ensure that the read timeout is not exceeded even in the
// case of resource errors
EAgainThreshold := RecvTimeout div IfThen(FMaxRecvRetries > 0, FMaxRecvRetries, 2);
TryAgain:
// Read from the socket
if RecvTimeout > 0 then
Start := Now
else
// if there is no read timeout we don't need the TOD to determine whether
// an THRIFT_EAGAIN is due to a timeout or an out-of-resource condition.
Start := 0;
if Assigned(FInterruptListener) then begin
FD_ZERO(Fds);
_FD_SET(Socket, Fds);
_FD_SET(FInterruptListener, Fds);
if RecvTimeout > 0 then begin
TVal.tv_sec := RecvTimeout div 1000;
TVal.tv_usec := (RecvTimeout mod 1000) * 1000;
PTVal := @TVal;
end
else
PTVal := nil;
Ret := select(2, @Fds, nil, nil, PTVal);
ErrnoCopy := WSAGetLastError;
if Ret < 0 then begin
// error cases
if (ErrnoCopy = WSAEINTR) and (Retries < FMaxRecvRetries) then begin
Inc(Retries);
goto TryAgain;
end;
LogDelegate(Format('TSocket.Read() select() %s', [SysErrorMessage(ErrnoCopy)]));
raise TTransportExceptionUnknown.Create(Format('Unknown: %s', [SysErrorMessage(ErrnoCopy)]));
end
else if Ret > 0 then begin
// Check the interruptListener
if FD_ISSET(FInterruptListener, Fds) then
raise TTransportExceptionInterrupted.Create('Interrupted');
end
else // Ret = 0
raise TTransportExceptionTimedOut.Create('WSAEWOULDBLOCK (timed out)');
// falling through means there is something to recv and it cannot block
end;
Got := recv(Socket, Buf, Len, 0);
ErrnoCopy := WSAGetLastError;
// Check for error on read
if Got < 0 then begin
if ErrnoCopy = WSAEWOULDBLOCK then begin
// if no timeout we can assume that resource exhaustion has occurred.
if RecvTimeout = 0 then
raise TTransportExceptionTimedOut.Create('WSAEWOULDBLOCK (unavailable resources)');
// check if this is the lack of resources or timeout case
ReadElapsed := MilliSecondsBetween(Now, Start);
if (EAgainThreshold = 0) or (ReadElapsed < EAgainThreshold) then begin
if Retries < FMaxRecvRetries then begin
Inc(Retries);
Sleep(1);
goto TryAgain;
end
else
raise TTransportExceptionTimedOut.Create('WSAEWOULDBLOCK (unavailable resources)');
end
else
// infer that timeout has been hit
raise TTransportExceptionTimedOut.Create('WSAEWOULDBLOCK (timed out)');
end;
// If interrupted, try again
if (ErrnoCopy = WSAEINTR) and (Retries < FMaxRecvRetries) then begin
Inc(Retries);
goto TryAgain;
end;
if ErrnoCopy = WSAECONNRESET then
Exit(0);
// This ish isn't open
if ErrnoCopy = WSAENOTCONN then
raise TTransportExceptionNotOpen.Create('WSAENOTCONN');
// Timed out!
if ErrnoCopy = WSAETIMEDOUT then
raise TTransportExceptionNotOpen.Create('WSAETIMEDOUT');
// Now it's not a try again case, but a real probblez
LogDelegate(Format('TSocket.Read() recv() %s %s', [SocketInfo, SysErrorMessage(ErrnoCopy)]));
// Some other error, whatevz
raise TTransportExceptionUnknown.Create(Format('Unknown: %s', [SysErrorMessage(ErrnoCopy)]));
end;
Result := Got;
end;
procedure TSocket.Write(const Buf; Len: Integer);
var
Sent, B: Integer;
begin
Sent := 0;
while Sent < Len do begin
B := WritePartial((PByte(@Buf) + Sent)^, Len - Sent);
if B = 0 then
// This should only happen if the timeout set with SO_SNDTIMEO expired.
// Raise an exception.
raise TTransportExceptionTimedOut.Create('send timeout expired');
Inc(Sent, B);
end;
end;
function TSocket.WritePartial(const Buf; Len: Integer): Integer;
var
B: Integer;
ErrnoCopy: Integer;
begin
if Socket = INVALID_SOCKET then
raise TTransportExceptionNotOpen.Create('Called write on non-open socket');
B := send(Socket, Buf, Len, 0);
if B < 0 then begin
// Fail on a send error
ErrnoCopy := WSAGetLastError;
if ErrnoCopy = WSAEWOULDBLOCK then
Exit(0);
LogDelegate(Format('TSocket.WritePartial() send() %s %s', [SocketInfo, SysErrorMessage(ErrnoCopy)]));
if (ErrnoCopy = WSAECONNRESET) or (ErrnoCopy = WSAENOTCONN) then begin
Close;
raise TTransportExceptionNotOpen.Create(Format('write() send(): %s', [SysErrorMessage(ErrnoCopy)]));
end;
raise TTransportExceptionUnknown.Create(Format('write() send(): %s', [SysErrorMessage(ErrnoCopy)]));
end;
// Fail on blocked send
if B = 0 then
raise TTransportExceptionNotOpen.Create('Socket send returned 0.');
Result := B;
end;
function TSocket.GetCachedAddress(out Len: Integer): PSockAddr;
begin
case FCachedPeerAddr.ipv4.sin_family of
AF_INET: begin
Len := SizeOf(TSockAddrIn);
Result := PSockAddr(@FCachedPeerAddr.ipv4);
end;
AF_INET6: begin
Len := SizeOf(TSockAddrIn6);
Result := PSockAddr(@FCachedPeerAddr.ipv6);
end;
else
Len := 0;
Result := nil;
end;
end;
procedure TSocket.SetCachedAddress(const Addr: TSockAddr; Len: Integer);
begin
case Addr.sa_family of
AF_INET: if Len = SizeOf(TSockAddrIn) then FCachedPeerAddr.ipv4 := PSockAddrIn(@Addr)^;
AF_INET6: if Len = SizeOf(TSockAddrIn6) then FCachedPeerAddr.ipv6 := PSockAddrIn6(@Addr)^;
end;
FPeerAddress := '';
FPeerHost := '';
FPeerPort := 0;
end;
procedure TSocket.SetLinger(LingerOn: Boolean; LingerVal: Integer);
var
L: TLinger;
begin
FLingerOn := LingerOn;
FLingerVal := LingerVal;
if Socket = INVALID_SOCKET then
Exit;
L.l_onoff := IfThen(FLingerOn, 1, 0);
L.l_linger := LingerVal;
if setsockopt(Socket, SOL_SOCKET, SO_LINGER, @L, SizeOf(L)) = SOCKET_ERROR then
LogDelegate(Format('TSocket.SetLinger() setsockopt() %s %s', [SocketInfo, SysErrorMessage(WSAGetLastError)]));
end;
function TSocket.Peek: Boolean;
var
Retries: Longword;
Fds: TFdSet;
TVal: TTimeVal;
PTVal: PTimeVal;
Ret: Integer;
ErrnoCopy: Integer;
Buf: Byte;
begin
if not IsOpen then Exit(False);
if Assigned(FInterruptListener) then begin
Retries := 0;
while true do begin
FD_ZERO(Fds);
_FD_SET(Socket, Fds);
_FD_SET(FInterruptListener, Fds);
if RecvTimeout > 0 then begin
TVal.tv_sec := RecvTimeout div 1000;
TVal.tv_usec := (RecvTimeout mod 1000) * 1000;
PTVal := @TVal;
end
else
PTVal := nil;
Ret := select(2, @Fds, nil, nil, PTVal);
ErrnoCopy := WSAGetLastError;
if Ret < 0 then begin
// error cases
if (ErrnoCopy = WSAEINTR) and (Retries < FMaxRecvRetries) then begin
Inc(Retries);
Continue;
end;
LogDelegate(Format('TSocket.Peek() select() %s', [SysErrorMessage(ErrnoCopy)]));
raise TTransportExceptionUnknown.Create(Format('Unknown: %s', [SysErrorMessage(ErrnoCopy)]));
end
else if Ret > 0 then begin
// Check the interruptListener
if FD_ISSET(FInterruptListener, Fds) then
Exit(False);
// There must be data or a disconnection, fall through to the PEEK
Break;
end
else
// timeout
Exit(False);
end;
end;
// Check to see if data is available or if the remote side closed
Ret := recv(Socket, Buf, 1, MSG_PEEK);
if Ret = SOCKET_ERROR then begin
ErrnoCopy := WSAGetLastError;
if ErrnoCopy = WSAECONNRESET then begin
Close;
Exit(False);
end;
LogDelegate(Format('TSocket.Peek() recv() %s %s', [SocketInfo, SysErrorMessage(ErrnoCopy)]));
raise TTransportExceptionUnknown.Create(Format('recv(): %s', [SysErrorMessage(ErrnoCopy)]));
end;
Result := Ret > 0;
end;
function TServerSocket.CreateSocketObj(Client: Winapi.Winsock2.TSocket): TSocket;
begin
if FInterruptableChildren then
Result := TSocket.Create(Client, FChildInterruptSockReader)
else
Result := TSocket.Create(Client);
end;
procedure TServerSocket.Notify(NotifySocket: Winapi.Winsock2.TSocket);
var
Byt: Byte;
begin
if NotifySocket <> INVALID_SOCKET then begin
Byt := 0;
if send(NotifySocket, Byt, SizeOf(Byt), 0) = SOCKET_ERROR then
LogDelegate(Format('TServerSocket.Notify() send() %s', [SysErrorMessage(WSAGetLastError)]));
end;
end;
procedure TServerSocket.SetInterruptableChildren(AValue: Boolean);
begin
if FListening then
raise Exception.Create('InterruptableChildren cannot be set after listen()');
FInterruptableChildren := AValue;
end;
procedure TServerSocket.CommonInit;
begin
inherited CommonInit;
FInterruptableChildren := True;
FAcceptBacklog := DEFAULT_BACKLOG;
FAcceptTimeout := 0;
FRetryLimit := 0;
FRetryDelay := 0;
FTcpSendBuffer := 0;
FTcpRecvBuffer := 0;
FListening := False;
FInterruptSockWriter := INVALID_SOCKET;
FInterruptSockReader := INVALID_SOCKET;
FChildInterruptSockWriter := INVALID_SOCKET;
end;
constructor TServerSocket.Create(APort: Integer; ALogDelegate: TBaseSocket.TLogDelegate = nil);
begin
// Unnecessary, but here for documentation purposes
inherited Create(APort, ALogDelegate);
end;
constructor TServerSocket.Create(APort: Integer; ASendTimeout, ARecvTimeout: Longword; ALogDelegate: TBaseSocket.TLogDelegate);
begin
inherited Create(APort, ALogDelegate);
SendTimeout := ASendTimeout;
RecvTimeout := ARecvTimeout;
end;
constructor TServerSocket.Create(AAddress: string; APort: Integer; ALogDelegate: TBaseSocket.TLogDelegate);
begin
inherited Create(APort, ALogDelegate);
FAddress := AAddress;
end;
procedure TServerSocket.Listen;
function CreateSocketPair(var Reader, Writer: Winapi.Winsock2.TSocket): Integer;
label
Error;
type
TSAUnion = record
case Integer of
0: (inaddr: TSockAddrIn);
1: (addr: TSockAddr);
end;
var
a: TSAUnion;
listener: Winapi.Winsock2.TSocket;
e: Integer;
addrlen: Integer;
flags: DWORD;
reuse: Integer;
begin
addrlen := SizeOf(a.inaddr);
flags := 0;
reuse := 1;
listener := Winapi.Winsock2.socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if listener = INVALID_SOCKET then
Exit(SOCKET_ERROR);
FillChar(a, SizeOf(a), 0);
a.inaddr.sin_family := AF_INET;
a.inaddr.sin_addr.s_addr := htonl(INADDR_LOOPBACK);
a.inaddr.sin_port := 0;
Reader := INVALID_SOCKET;
Writer := INVALID_SOCKET;
// ignore errors coming out of this setsockopt. This is because
// SO_EXCLUSIVEADDRUSE requires admin privileges on WinXP, but we don't
// want to force socket pairs to be an admin.
setsockopt(listener, SOL_SOCKET, Integer(SO_EXCLUSIVEADDRUSE), @reuse, SizeOf(reuse));
if bind(listener, a.addr, SizeOf(a.inaddr)) = SOCKET_ERROR then
goto Error;
if getsockname(listener, a.addr, addrlen) = SOCKET_ERROR then
goto Error;
if Winapi.Winsock2.listen(listener, 1) = SOCKET_ERROR then
goto Error;
Reader := WSASocket(AF_INET, SOCK_STREAM, 0, nil, 0, flags);
if Reader = INVALID_SOCKET then
goto Error;
if connect(Reader, a.addr, SizeOf(a.inaddr)) = SOCKET_ERROR then
goto Error;
Writer := Winapi.Winsock2.accept(listener, nil, nil);
if Writer = INVALID_SOCKET then
goto Error;
closesocket(listener);
Exit(0);
Error:
e := WSAGetLastError;
closesocket(listener);
closesocket(Reader);
closesocket(Writer);
WSASetLastError(e);
Result := SOCKET_ERROR;
end;
var
TempIntReader,
TempIntWriter: Winapi.Winsock2.TSocket;
One: Cardinal;
ErrnoCopy: Integer;
Ling: TLinger;
Retries: Integer;
AddrInfo: IGetAddrInfoWrapper;
SA: TSockAddrStorage;
Len: Integer;
begin
// Create the socket pair used to interrupt
if CreateSocketPair(TempIntReader, TempIntWriter) = SOCKET_ERROR then begin
LogDelegate(Format('TServerSocket.Listen() CreateSocketPair() Interrupt %s', [SysErrorMessage(WSAGetLastError)]));
FInterruptSockReader := INVALID_SOCKET;
FInterruptSockWriter := INVALID_SOCKET;
end
else begin
FInterruptSockReader := TempIntReader;
FInterruptSockWriter := TempIntWriter;
end;
// Create the socket pair used to interrupt all clients
if CreateSocketPair(TempIntReader, TempIntWriter) = SOCKET_ERROR then begin
LogDelegate(Format('TServerSocket.Listen() CreateSocketPair() ChildInterrupt %s', [SysErrorMessage(WSAGetLastError)]));
FChildInterruptSockReader := TSmartPointer<Winapi.Winsock2.TSocket>.Create(INVALID_SOCKET, nil);
FChildInterruptSockWriter := INVALID_SOCKET;
end
else begin
FChildInterruptSockReader := TSmartPointer<Winapi.Winsock2.TSocket>.Create(TempIntReader, DestroyerOfFineSockets);
FChildInterruptSockWriter := TempIntWriter;
end;
if (Port < 0) or (Port > $FFFF) then
raise TTransportExceptionBadArgs.Create('Specified port is invalid');
AddrInfo := CreateSocket(FAddress, Port);
// Set SO_EXCLUSIVEADDRUSE to prevent 2MSL delay on accept
One := 1;
setsockopt(Socket, SOL_SOCKET, Integer(SO_EXCLUSIVEADDRUSE), @one, SizeOf(One));
// ignore errors coming out of this setsockopt on Windows. This is because
// SO_EXCLUSIVEADDRUSE requires admin privileges on WinXP, but we don't
// want to force servers to be an admin.
// Set TCP buffer sizes
if FTcpSendBuffer > 0 then begin
if setsockopt(Socket, SOL_SOCKET, SO_SNDBUF, @FTcpSendBuffer, SizeOf(FTcpSendBuffer)) = SOCKET_ERROR then begin
ErrnoCopy := WSAGetLastError;
LogDelegate(Format('TServerSocket.Listen() setsockopt() SO_SNDBUF %s', [SysErrorMessage(ErrnoCopy)]));
raise TTransportExceptionNotOpen.Create(Format('Could not set SO_SNDBUF: %s', [SysErrorMessage(ErrnoCopy)]));
end;
end;
if FTcpRecvBuffer > 0 then begin
if setsockopt(Socket, SOL_SOCKET, SO_RCVBUF, @FTcpRecvBuffer, SizeOf(FTcpRecvBuffer)) = SOCKET_ERROR then begin
ErrnoCopy := WSAGetLastError;
LogDelegate(Format('TServerSocket.Listen() setsockopt() SO_RCVBUF %s', [SysErrorMessage(ErrnoCopy)]));
raise TTransportExceptionNotOpen.Create(Format('Could not set SO_RCVBUF: %s', [SysErrorMessage(ErrnoCopy)]));
end;
end;
// Turn linger off, don't want to block on calls to close
Ling.l_onoff := 0;
Ling.l_linger := 0;
if setsockopt(Socket, SOL_SOCKET, SO_LINGER, @Ling, SizeOf(Ling)) = SOCKET_ERROR then begin
ErrnoCopy := WSAGetLastError;
LogDelegate(Format('TServerSocket.Listen() setsockopt() SO_LINGER %s', [SysErrorMessage(ErrnoCopy)]));
raise TTransportExceptionNotOpen.Create(Format('Could not set SO_LINGER: %s', [SysErrorMessage(ErrnoCopy)]));
end;
// TCP Nodelay, speed over bandwidth
if setsockopt(Socket, IPPROTO_TCP, TCP_NODELAY, @One, SizeOf(One)) = SOCKET_ERROR then begin
ErrnoCopy := WSAGetLastError;
LogDelegate(Format('TServerSocket.Listen() setsockopt() TCP_NODELAY %s', [SysErrorMessage(ErrnoCopy)]));
raise TTransportExceptionNotOpen.Create(Format('Could not set TCP_NODELAY: %s', [SysErrorMessage(ErrnoCopy)]));
end;
// Set NONBLOCK on the accept socket
if ioctlsocket(Socket, Integer(FIONBIO), One) = SOCKET_ERROR then begin
ErrnoCopy := WSAGetLastError;
LogDelegate(Format('TServerSocket.Listen() ioctlsocket() FIONBIO %s', [SysErrorMessage(ErrnoCopy)]));
raise TTransportExceptionNotOpen.Create(Format('ioctlsocket() FIONBIO: %s', [SysErrorMessage(ErrnoCopy)]));
end;
// prepare the port information
// we may want to try to bind more than once, since THRIFT_NO_SOCKET_CACHING doesn't
// always seem to work. The client can configure the retry variables.
Retries := 0;
while True do begin
if bind(Socket, AddrInfo.Res^.ai_addr^, AddrInfo.Res^.ai_addrlen) = 0 then
Break;
Inc(Retries);
if Retries > FRetryLimit then
Break;
Sleep(FRetryDelay * 1000);
end;
// retrieve bind info
if (Port = 0) and (Retries < FRetryLimit) then begin
Len := SizeOf(SA);
FillChar(SA, Len, 0);
if getsockname(Socket, PSockAddr(@SA)^, Len) = SOCKET_ERROR then
LogDelegate(Format('TServerSocket.Listen() getsockname() %s', [SysErrorMessage(WSAGetLastError)]))
else begin
if SA.ss_family = AF_INET6 then
Port := ntohs(PSockAddrIn6(@SA)^.sin6_port)
else
Port := ntohs(PSockAddrIn(@SA)^.sin_port);
end;
end;
// throw an error if we failed to bind properly
if (Retries > FRetryLimit) then begin
LogDelegate(Format('TServerSocket.Listen() BIND %d', [Port]));
Close;
raise TTransportExceptionNotOpen.Create(Format('Could not bind: %s', [SysErrorMessage(WSAGetLastError)]));
end;
if Assigned(FListenCallback) then
FListenCallback(Socket);
// Call listen
if Winapi.Winsock2.listen(Socket, FAcceptBacklog) = SOCKET_ERROR then begin
ErrnoCopy := WSAGetLastError;
LogDelegate(Format('TServerSocket.Listen() listen() %s', [SysErrorMessage(ErrnoCopy)]));
raise TTransportExceptionNotOpen.Create(Format('Could not listen: %s', [SysErrorMessage(ErrnoCopy)]));
end;
// The socket is now listening!
end;
function TServerSocket.Accept: TSocket;
var
Fds: TFdSet;
MaxEInters,
NumEInters: Integer;
TVal: TTimeVal;
PTVal: PTimeVal;
ErrnoCopy: Integer;
Buf: Byte;
ClientAddress: TSockAddrStorage;
Size: Integer;
ClientSocket: Winapi.Winsock2.TSocket;
Zero: Cardinal;
Client: TSocket;
Ret: Integer;
begin
MaxEInters := 5;
NumEInters := 0;
while True do begin
FD_ZERO(Fds);
_FD_SET(Socket, Fds);
_FD_SET(FInterruptSockReader, Fds);
if FAcceptTimeout > 0 then begin
TVal.tv_sec := FAcceptTimeout div 1000;
TVal.tv_usec := (FAcceptTimeout mod 1000) * 1000;
PTVal := @TVal;
end
else
PTVal := nil;
// TODO: if WSAEINTR is received, we'll restart the timeout.
// To be accurate, we need to fix this in the future.
Ret := select(2, @Fds, nil, nil, PTVal);
if Ret < 0 then begin
// error cases
if (WSAGetLastError = WSAEINTR) and (NumEInters < MaxEInters) then begin
// THRIFT_EINTR needs to be handled manually and we can tolerate
// a certain number
Inc(NumEInters);
Continue;
end;
ErrnoCopy := WSAGetLastError;
LogDelegate(Format('TServerSocket.Accept() select() %s', [SysErrorMessage(ErrnoCopy)]));
raise TTransportExceptionUnknown.Create(Format('Unknown: %s', [SysErrorMessage(ErrnoCopy)]));
end
else if Ret > 0 then begin
// Check for an interrupt signal
if (FInterruptSockReader <> INVALID_SOCKET) and FD_ISSET(FInterruptSockReader, Fds) then begin
if recv(FInterruptSockReader, Buf, SizeOf(Buf), 0) = SOCKET_ERROR then
LogDelegate(Format('TServerSocket.Accept() recv() interrupt %s', [SysErrorMessage(WSAGetLastError)]));
raise TTransportExceptionInterrupted.Create('interrupted');
end;
// Check for the actual server socket being ready
if FD_ISSET(Socket, Fds) then
Break;
end
else begin
LogDelegate('TServerSocket.Accept() select() 0');
raise TTransportExceptionUnknown.Create('unknown error');
end;
end;
Size := SizeOf(ClientAddress);
ClientSocket := Winapi.Winsock2.accept(Socket, @ClientAddress, @Size);
if ClientSocket = INVALID_SOCKET then begin
ErrnoCopy := WSAGetLastError;
LogDelegate(Format('TServerSocket.Accept() accept() %s', [SysErrorMessage(ErrnoCopy)]));
raise TTransportExceptionUnknown.Create(Format('accept(): %s', [SysErrorMessage(ErrnoCopy)]));
end;
// Make sure client socket is blocking
Zero := 0;
if ioctlsocket(ClientSocket, Integer(FIONBIO), Zero) = SOCKET_ERROR then begin
ErrnoCopy := WSAGetLastError;
closesocket(ClientSocket);
LogDelegate(Format('TServerSocket.Accept() ioctlsocket() FIONBIO %s', [SysErrorMessage(ErrnoCopy)]));
raise TTransportExceptionUnknown.Create(Format('ioctlsocket(): %s', [SysErrorMessage(ErrnoCopy)]));
end;
Client := CreateSocketObj(ClientSocket);
if SendTimeout > 0 then
Client.SendTimeout := SendTimeout;
if RecvTimeout > 0 then
Client.RecvTimeout := RecvTimeout;
if KeepAlive then
Client.KeepAlive := KeepAlive;
Client.SetCachedAddress(PSockAddr(@ClientAddress)^, Size);
if Assigned(FAcceptCallback) then
FAcceptCallback(ClientSocket);
Result := Client;
end;
procedure TServerSocket.Interrupt;
begin
Notify(FInterruptSockWriter);
end;
procedure TServerSocket.InterruptChildren;
begin
Notify(FChildInterruptSockWriter);
end;
procedure TServerSocket.Close;
begin
inherited Close;
if FInterruptSockWriter <> INVALID_SOCKET then
closesocket(FInterruptSockWriter);
if FInterruptSockReader <> INVALID_SOCKET then
closesocket(FInterruptSockReader);
if FChildInterruptSockWriter <> INVALID_SOCKET then
closesocket(FChildInterruptSockWriter);
FChildInterruptSockReader := TSmartPointer<Winapi.Winsock2.TSocket>.Create(INVALID_SOCKET, nil);
FListening := False;
end;
{$ENDIF} // not for OLD_SOCKETS
end.