| (* |
| * 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; |
| strict 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>) |
| strict 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); |
| strict 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; |
| 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. |