unit Sockets;
{ Install this component using Component, Install, Add within
  Delphi 2.0. This component high-levels the winsock functionallity
  and provides a simple interface to TCP/IP socket functions.

  The code herein is released to the public domain without conditions.

  Written By:      Gary T. Desrosiers
  Date:            March 27th, 1995.
  Modified:        March 18th, 1996 for Delphi 2.0
  Copyright:       (R) Copyright by Gary T. Desrosiers, 1995. All Rights Reserved
  UserID(s):       71062,2754
                   desrosi@pcnet.com

  Description:     This control performs WinSock TCP/IP functions.

  Prerequisites:   You must have the TCP/IP protocol installed and the
                   WSOCK32.DLL available. This code has been tested under
                   Windows 95 and Windows NT 4.0.
 

  Modifications:   Version 3 - March 18th, 1996
                   - Added properties;
                     - HostName, Returns the name of the local host.
                     - MaximumReceiveLength, Sets the maximum receive buffer size.
                   - Added Methods;
                     - GetLocalIPAddr, returns the IP address of the local host
                   - Added Events;
                     - OnDataNeeded, called when the socket needs data and it's
                        okay to write.

                   Version 2 - July 5th, 1995
                   - Added properties;
                     - MasterSocket, Gets the listener's socket
                     - Peek, Preview data in the input buffer.
                     - NonBlocking, Blocking vs Non-Blocking sockets
                     - Timeout, For blocking mode timeouts
                     - OOB, Sends and receives data out of band (urgent data)
                   - Modified properties;
                     - SocketNumber to read/write
                     - Text (no longer published)
                   - Added Methods;
                     - SCancelListen, new method cancels the listener socket
                     - GetPeerIPAddr, returns partners IP address
                     - GetPeerPort, returns partners port
                   - Modified Methods;
                     - GetIPAddr, Documented and bug fix
                     - GetPort, Documented
                     - SClose, Added shutdown, etc.
                     - SReceive, Modified to use PChar instead of Pascal strings
                     - SSend, Modified to use PChar instead of Pascal strings
                     - SetText, Now loops until entire buffer sent
                   - Added Events
                     - OnErrorOccurred, Called on WinSock errors.

  Properties:       Authorized, true for authorized port assignment
                    default is false which allocates ports > 1024

                    IPAddr, Design time and runtime read/write.
                     Sets the IP Address of the partner that you will
                     eventually SConnect to. You may specify this as
                     dotted decimal or a literal name to be converted
                     via DNS.
                     examples;
                       Sockets1.IPAddr := 'desrosi';
                       Sockets1.IPAddr := '127.0.0.1';
                       addr := Sockets1.IPAddr;

                   Port, Design time and runtime read/write.
                     Sets the Port number of the remote port to connect
                     to or the local port to listen on depending on
                     whether you subsequently issue a SConnect or SListen.
                     This can be specified as a number or a literal name
                     to be converted via DNS.
                     examples;
                       Sockets1.Port := 'echo';
                       Sockets1.Port := '7';
                       port := Sockets1.Port;

                   SocketNumber, Runtime Read/write.
                     Returns (or sets) the socket number of the currently
                     allocated connection.
                     example;
                       sock := Sockets1.SocketNumber;

                   MasterSocket, Runtime Read/Write.
                     Returns (or sets) the master socket number (listener)
                     example;
                       msock := Sockets1.MasterSocket;

                   Text, Design time and runtime read/write.
                     if set, sends the text to the partner.
                     if read, receives some text from the partner.
                     examples;
                       buffer := Sockets1.Text; (* Receive data *)
                       Sockets1.Text := 'This is a test'; (* Send Data *)

                   Peek, runtime read only.
                     Returns up to 255 characters of data waiting to
                     be received but does not actually receive the
                     data.

                   OOB, runtime read/write.
                     if set, sends the text to the partner as urgent (out of
                       band) data.
                     if read, receives urgent (out of band) data.
                     examples;
                       buffer := Sockets1.OOB;
                       Sockets1.OOB := 'This is a test';

                   NonBlocking, Design time and runtime read/write
                     Set to False for blocking mode and True for non-blocking
                     mode (the default). When the socket is in blocking
                     mode, none of the event callback functions (with the
                     exception of OnErrorOccurred) will function.

                   Timeout, Design time and runtime read/write
                     When NonBlocking = 0 (blocking mode) this value
                     specifies the maximum amount of time that
                     a socket operation can take. After this time
                     limit expires, the operation is canceled and
                     an error occurs. The default is 30 (seconds).
                     The Valid range is 0-60 seconds. Setting Timeout
                     to zero causes the operation to wait indefinitely.

                   MaximumReceiveLength, Runtime read/write
                     Set to limit the size of buffers retrieved using
                     .Text, .PeekData, and .OOB Default is 8192.

                   HostName, Runtime read only.
                     Returns the name of the local host.

Methods:           SConnect - Connects to the remote (or local) system
                     specified in the IPAddr and Port properties.
                     example;
                       Sockets1.SConnect; (* Connect to partner *)

                   SListen - Listens on the port specified in the Port
                     property.
                     example;
                       Sockets1.SListen; (* Establish server environment *)

                   SCancelListen - Cancels listens on the socket.
                     example;
                       Sockets1.SCancelListen; (* Dont accept further clients *)

                   SAccept - Accepts a client request. Usually issued in
                     OnSessionAvailable event.
                     example;
                       Sock := Sockets1.SAccept; (* Get client connection *)

                   SClose - Closes the socket.
                     example;
                       Sockets1.SClose; (* Close connection *)

                   SReceive - Receives data from partner, similar to
                     reading the property Text although this function
                     uses PChar instead of Pascal strings.
                     example;
                       len := Sockets1.SReceive(Sockets1.SocketNumber,szBuffer,4096);

                   SSend - Sends data to the partner, similar to
                     setting the property Text although this function
                     uses PChar instead of Pascal strings.
                     example;
                       len := Sockets1.SSend(Sockets1.SocketNumber,szBuff,32000);

                   GetPort - Returns the actual port number of the socket
                     specified as the argument. Generally used when you've
                     specified a port of zero and need to retrieve the
                     assigned port number.

                   GetIPAddr - Returns the IP Address of the socket specified
                     as the argument.

                   GetPeerPort - Returns the partners port number of the socket
                     specified as the argument.

                   GetPeerIPAddr - Returns partners IP Address of the socket
                     specified as the argument.

                   GetLocalIPAddr - Returns the local host's IP Address.

Events:            OnDataAvailable - Called when data is available to
                     be received from the partner. You should issue;
                     buffer := Sockets1.Text; or a SReceive method to
                     receive the data from the partner.

                   OnDataNeeded - Called when it is okay to write
                     data to the socket.

                   OnSessionAvailable - Called when a client has requested
                     to connect to a 'listening' server. You can call
                     the method SAccept here.

                   OnSessionClosed - Called when the partner has closed
                     a socket on you. Normally, you would close your side
                     of the socket when this event happens.

                   OnSessionConnected - Called when the SConnect has
                     completed and the session is connected. This is a
                     good place to send the initial data of a conversation.
                     Also, you may want to enable certain controls that
                     allow the user to send data on the conversation here.

                   OnErrorOccurred - Called when an error occurs on the socket.
                     If defined, the OnErrorOccurred procedure is called when
                     the error occurs. If the procedure isn't defined then
                     a dialog box is displayed with the error text and the
                     program is halted.
}
interface

uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, WinSock;
const
  { User Windows Messages }
  WM_ASYNCSELECT = WM_USER + 0;
type
  TDataAvailable = procedure (Sender: TObject; Socket: TSocket) of object;
  TDataNeeded = procedure (Sender: TObject; Socket: TSocket) of object;
  TSessionClosed = procedure (Sender: TObject; Socket: TSocket) of object;
  TSessionAvailable = procedure (Sender: TObject; Socket: TSocket) of object;
  TSessionConnected = procedure (Sender: TObject; Socket: TSocket) of object;
  TErrorOccurred = procedure (Sender: TObject; Socket: Integer; Error: integer; Msg: string) of object;

  TSockets = class(TWinControl)
  private
    Pse: PServEnt;
    Phe: PHostEnt;
    Ppe: PProtoEnt;
    sin: TSockAddrIn;
    initdata: TWSAData;
    FAuthorized: Boolean;
    FPort: String;
    FIPAddr: String;
    FSocket: TSocket;
    FMSocket: TSocket;
    FMode: longint;
    FTimeout: integer;
    FMaximumReceiveLength: integer;
    FDataAvailable: TDataAvailable;
    FDataNeeded : TDataNeeded;
    FSessionClosed: TSessionClosed;
    FSessionAvailable: TSessionAvailable;
    FSessionConnected: TSessionConnected;
    FErrorOccurred: TErrorOccurred;
    procedure SetText(Text: string);
    function GetText : string;
    procedure SetTextOOB(Text: string);
    function GetTextOOB : string;
    function PeekData : string;
    function SocketErrorDesc(error: integer) : string;
    procedure SocketError(Socket: TSocket; sockfunc: string; error: integer);
    procedure TWMPaint(var msg:TWMPaint); message WM_PAINT;
    procedure SetTimeout;
    procedure ResetTimeout;
    function GetLocalHostName: string;
  protected
    procedure WMASyncSelect(var msg: TMessage); message WM_ASYNCSELECT;
    procedure WMTimer(var msg: TMessage); message WM_TIMER;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    { I'd like to call these methods Connect, Close, Listen, etc but
      they would conflict with the WSock32.DLL function names ! }
    procedure SConnect;
    procedure SClose;
    procedure SListen;
    procedure SCancelListen;
    function SAccept: TSocket;
    function SReceive(Socket: TSocket; szBuff: PChar; var rlen: integer): integer;
    function SSend(Socket: TSocket; szBuff: PChar; var slen: integer): integer;
    function GetIPAddr(aSocket: TSocket): string;
    function GetLocalIPAddr: string;
    function GetPort(aSocket: TSocket): string;
    function GetPeerIPAddr(aSocket: TSocket): string;
    function GetPeerPort(aSocket: TSocket): string;
    function GetBlocking: Boolean;
    procedure SetBlocking(flag: Boolean);
    property Text: string read GetText write SetText;
    property Authorized: Boolean read FAuthorized write FAuthorized;
    property Peek: string read PeekData;
    property OOB: string read GetTextOOB write SetTextOOB;
    property SocketNumber: TSocket read FSocket write FSocket;
    property MasterSocket: TSocket read FMSocket write FMSocket;
    property HostName: string read GetLocalHostName;
  published
    property MaximumReceiveLength: integer read FMaximumReceiveLength write FMaximumReceiveLength;
    property IPAddr: string read FIPAddr write FIPAddr;
    property Port: string read FPort write FPort;
    property NonBlocking: Boolean read GetBlocking write SetBlocking default True;
    property Timeout: integer read FTimeout write FTimeout default 30;
    property OnDataAvailable: TDataAvailable read FDataAvailable
      write FDataAvailable;
    property OnDataNeeded: TDataNeeded read FDataNeeded
      write FDataNeeded;
    property OnSessionClosed: TSessionClosed read FSessionClosed
      write FSessionClosed;
    property OnSessionAvailable: TSessionAvailable read FSessionAvailable
      write FSessionAvailable;
    property OnSessionConnected: TSessionConnected read FSessionConnected
      write FSessionConnected;
    property OnErrorOccurred: TErrorOccurred read FErrorOccurred
      write FErrorOccurred;
  end;

procedure Register;

implementation
 

procedure Register;
begin
  RegisterComponents('Samples', [TSockets]);
end;
 

constructor TSockets.Create(AOwner: TComponent);
var
  iStatus: integer;
begin
  inherited Create(AOwner);
  FAuthorized := False;
  FMode := 1;
  FTimeout := 30;
  FMaximumReceiveLength := 8192;
  FSocket := INVALID_SOCKET;
  FMSocket := INVALID_SOCKET;
  iStatus := WSAStartup($101,initdata);
  if iStatus <> 0 then
    SocketError(0,'Constructor (WSAStartup)',WSAGetLastError);
  invalidate;
end;

destructor TSockets.Destroy;
var
  iStatus: integer;
begin
  iStatus := WSACleanup;
  if iStatus < 0 then
    SocketError(INVALID_SOCKET,'Destructor (WSACleanup)',WSAGetLastError);
  inherited Destroy;
end;

procedure TSockets.TWMPaint(var msg: TWMPaint);
var
  icon: HIcon;
  dc: HDC;
begin
  if csDesigning in ComponentState then
  begin
    icon := LoadIcon(HInstance,MAKEINTRESOURCE('TSOCKETS'));
    dc := GetDC(Handle);
    Width := 32;
    Height := 32;
    DrawIcon(dc,0,0,icon);
    ReleaseDC(Handle,dc);
    FreeResource(icon);
  end;
  ValidateRect(Handle,nil);
end;

function TSockets.GetBlocking: Boolean;
begin
  if FMode = 1 then
    Result := True
  else
    Result := False;
end;

procedure TSockets.SetBlocking(flag: Boolean);
begin
  if flag then
    FMode := 1
  else
    FMode := 0;
end;

procedure TSockets.SetText(Text: string);
var
  BytesSent: integer;
  pBuff: PChar;
begin
  pBuff := StrAlloc(Length(Text)+1);
  StrPCopy(pBuff,Text);
  if FMode = 0 then
    SetTimeout;
  BytesSent := send(FSocket,pBuff^,Length(Text),0);
  if FMode = 0 then
    ResetTimeout;
  if BytesSent < 0 then
    SocketError(FSocket,'SetText (Send)',WSAGetLastError);
  StrDispose(pBuff);
end;

function TSockets.GetText: string;
var
  len: integer;
  pBuff: PChar;
begin
  if FSocket <> INVALID_SOCKET then
  begin
    pBuff := StrAlloc(FMaximumReceiveLength);
    if FMode = 0 then
      SetTimeout;
    len := recv(FSocket,pBuff^,FMaximumReceiveLength,0);
    if FMode = 0 then
      ResetTimeout;
    if len < 0 then
      SocketError(FSocket,'GetText (Recv)',WSAGetLastError);
    pBuff[len] := chr(0);
    Result := pBuff;
    StrDispose(pBuff);
  end
  else Result := '';
end;

procedure TSockets.SetTextOOB(Text: string);
var
  BytesLeft, BytesSent: integer;
  pBuff: PChar;
begin
  pBuff := StrAlloc(Length(Text)+1);
  StrPCopy(pBuff,Text);
  if FMode = 0 then
    SetTimeout;
  BytesSent := send(FSocket,pBuff^,Length(Text),MSG_OOB);
  if FMode = 0 then
    ResetTimeout;
  if BytesSent < 0 then
    SocketError(FSocket,'SetText (Send)',WSAGetLastError);
  StrDispose(pBuff);
end;

function TSockets.GetTextOOB: string;
var
  len: integer;
  pBuff: PChar;
begin
  if FSocket <> INVALID_SOCKET then
  begin
    pBuff := StrAlloc(FMaximumReceiveLength);
    if FMode = 0 then
      SetTimeout;
    len := recv(FSocket,pBuff^,FMaximumReceiveLength,MSG_OOB);
    if FMode = 0 then
      ResetTimeout;
    if len < 0 then
      SocketError(FSocket,'GetText (Recv)',WSAGetLastError);
    Result := pBuff;
    StrDispose(pBuff);
  end
  else Result := '';
end;
 

function TSockets.PeekData: string;
var
  len: integer;
  pBuff: PChar;
begin
  if FSocket <> INVALID_SOCKET then
  begin
    pBuff := StrAlloc(FMaximumReceiveLength);
    if FMode = 0 then
      SetTimeout;
    len := recv(FSocket,pBuff^,FMaximumReceiveLength,MSG_PEEK);
    if FMode = 0 then
      ResetTimeout;
    if len < 0 then
      SocketError(FSocket,'PeekData (Peek)',WSAGetLastError);
    Result := pBuff;
    StrDispose(pBuff);
  end
  else Result := '';
end;

function TSockets.GetPort(aSocket: TSocket): string;
var
  addr: TSockAddrIn;
  addrlen: integer;
begin
  addrlen := sizeof(addr);
  getsockname(aSocket,addr,addrlen);
  Result := IntToStr(ntohs(addr.sin_port));
end;

function TSockets.GetIPAddr(aSocket: TSocket): string;
var
  addr: TSockAddrIn;
  addrlen: integer;
  szIPAddr: PChar;
begin
  addrlen := sizeof(addr);
  getsockname(aSocket,addr,addrlen);
  szIPAddr := inet_ntoa(addr.sin_addr);
  Result := StrPas(szIPAddr);
end;

function TSockets.GetLocalIPAddr: string;
var
  addr: TSockAddrIn;
  Phe: PHostEnt;
  szHostName: array[0..128] of char;
begin
  GetHostName(szHostName,128);
  Phe := GetHostByName(szHostName);
  if Phe = nil then
    Result := '0.0.0.0'
  else
  begin
    addr.sin_addr.S_addr := longint(plongint(Phe^.h_addr_list^)^);
    Result := inet_ntoa(addr.sin_addr);
  end;
end;

function TSockets.GetLocalHostName: string;
var
  szHostName: array[0..128] of char;
begin
  GetHostName(szHostName,128);
  Result := szHostName;
end;
 

function TSockets.GetPeerPort(aSocket: TSocket): string;
var
  addr: TSockAddrIn;
  addrlen: integer;
begin
  addrlen := sizeof(addr);
  getpeername(aSocket,addr,addrlen);
  Result := IntToStr(ntohs(addr.sin_port));
end;

function TSockets.GetPeerIPAddr(aSocket: TSocket): string;
var
  addr: TSockAddrIn;
  addrlen: integer;
  szIPAddr: PChar;
begin
  addrlen := sizeof(addr);
  getpeername(aSocket,addr,addrlen);
  szIPAddr := inet_ntoa(addr.sin_addr);
  Result := StrPas(szIPAddr);
end;
 

function TSockets.SReceive(Socket: TSocket; szBuff: PChar; var rlen: integer) : integer;
begin
  if Socket <> INVALID_SOCKET then
  begin
    if FMode = 0 then
      SetTimeout;
    Result := recv(Socket,szBuff^,rlen,0);
    if FMode = 0 then
      ResetTimeout;
    if rlen < 0 then
      SocketError(FSocket,'SReceive',WSAGetLastError);
  end
  else Result := -1;
end;

function TSockets.SSend(Socket: TSocket; szBuff: PChar; var slen: integer): integer;
begin
  if Socket <> INVALID_SOCKET then
  begin
    if FMode = 0 then
      SetTimeout;
    slen := send(Socket,szBuff^,slen,0);
    if FMode = 0 then
      ResetTimeout;
    if slen < 0 then
      SocketError(FSocket,'SSend',WSAGetLastError);
    Result := slen;
  end;
end;

procedure TSockets.WMASyncSelect(var msg: TMessage);
var
  err: integer;
  errfn: string;
begin
  err := WSAGetSelectError(msg.LParam);
  if err > WSABASEERR then
  begin
    case WSAGetSelectEvent(msg.lParam) of
      FD_READ: errfn := 'FD_READ';
      FD_WRITE: errfn := 'FD_WRITE';
      FD_CLOSE: errfn := 'FD_CLOSE';
      FD_ACCEPT: errfn := 'FD_ACCEPT';
      FD_CONNECT: errfn := 'FD_CONNECT';
    end;
    SocketError(msg.wParam,errfn,err);
  end
  else
  case WSAGetSelectEvent(msg.lParam) of
    FD_READ:
    begin
      if Assigned(FDataAvailable) then
        FDataAvailable(Self,msg.wParam);
    end;
    FD_WRITE:
    begin
      if Assigned(FDataNeeded) then
        FDataNeeded(Self,msg.wParam);
    end;
    FD_CLOSE:
    begin
      if Assigned(FSessionClosed) then
        FSessionClosed(Self,msg.wParam);
    end;
    FD_ACCEPT:
    begin
      if Assigned(FSessionAvailable) then
        FSessionAvailable(Self,msg.wParam);
    end;
    FD_CONNECT:
    begin
      if Assigned(FSessionConnected) then
        FSessionConnected(Self,msg.wParam);
    end;
  end;
end;

procedure TSockets.WMTimer(var msg: TMessage);
var
  szErrMsg: array[0..255] of char;
begin
  KillTimer(Handle,10);
  if WSAIsBlocking then
  begin
    WSACancelBlockingCall;
    if Assigned(FErrorOccurred) then
      FErrorOccurred(Self,FSocket,WSAETIMEDOUT,'Blocking call timed out')
    else
      begin
        StrPCopy(szErrMsg,'Error ' + IntToStr(WSAETIMEDOUT) + #13#10 +
          'Blocking call timed out');
        Application.MessageBox(szErrMsg, 'WINSOCK CALL CANCELED', mb_OKCancel +
          mb_DefButton1);
      end;
  end;
end;
 

procedure TSockets.SConnect;
var
  iStatus: integer;
  szTcp: PChar;
  szPort: array[0..31] of char;
  szData: array[0..256] of char;
  bind_sin: TSockAddrIn;
  alport: TSocket;
begin
  if FPort = '' then
  begin
    Application.MessageBox('No Port Specified', 'WINSOCK ERROR', mb_OKCancel +
      mb_DefButton1);
    exit;
  end;
  if FIPAddr = '' then
  begin
    Application.MessageBox('No IP Address Specified', 'WINSOCK ERROR', mb_OKCancel +
      mb_DefButton1);
    exit;
  end;
  sin.sin_family := AF_INET;
  StrPCopy(szPort,FPort);
  szTcp := 'tcp';
  Pse := getservbyname(szPort,szTcp);
  if Pse = nil then
     sin.sin_port := htons(StrToInt(StrPas(szPort)))
  else sin.sin_port := Pse^.s_port;
  StrPCopy(szData,FIPAddr);
  sin.sin_addr.s_addr := inet_addr(szData);
  if sin.sin_addr.s_addr = INADDR_NONE then
    begin
      Phe := gethostbyname(szData);
      if Phe = nil then
        begin
          StrPCopy(szData,'Cannot convert host address');
          Application.MessageBox(szData, 'WINSOCK ERROR', mb_OKCancel +
             mb_DefButton1);
          exit;
        end;
      sin.sin_addr.S_addr := longint(plongint(Phe^.h_addr_list^)^);
    end;
  Ppe := getprotobyname(szTcp);
  FSocket := socket(PF_INET,SOCK_STREAM,Ppe^.p_proto);
  if FSocket < 0 then
    SocketError(INVALID_SOCKET,'SConnect (socket)',WSAGetLastError);
  if FAuthorized = True then
  begin
    alport := IPPORT_RESERVED;
    bind_sin.sin_family := AF_INET;
    bind_sin.sin_addr.s_addr := 0;
    repeat
      bind_sin.sin_port := htons(alport);
      if bind(FSocket,bind_sin,sizeof(bind_sin)) = 0 then
        break;
      if WSAGetLastError <> WSAEADDRINUSE then
        SocketError(FSocket,'SConnect bind()',WSAGetLastError);
      dec(alport);
    until(alport <= (IPPORT_RESERVED div 2));
  end;
  if FMode = 1 then
  begin
    iStatus := WSAASyncSelect(FSocket,Handle,WM_ASYNCSELECT,
      FD_READ or FD_CLOSE or FD_CONNECT or FD_WRITE);
    if iStatus <> 0 then
      SocketError(FSocket,'WSAAsyncSelect',WSAGetLastError);
  end
  else
    iStatus := ioctlsocket(FSocket,FIONBIO,FMode);
  if FMode = 0 then
    SetTimeout;
  iStatus := connect(FSocket,sin,sizeof(sin));
  if FMode = 0 then
    ResetTimeout;
  if iStatus <> 0 then
    begin
    iStatus := WSAGetLastError;
    if iStatus <> WSAEWOULDBLOCK then
       SocketError(FSocket,'SConnect',WSAGetLastError);
    end;
end;

procedure TSockets.SListen;
var
  iStatus: integer;
  szTcp: PChar;
  szPort: array[0..31] of char;
  szData: array[0..256] of char;
begin
  if FPort = '' then
  begin
    Application.MessageBox('No Port Specified', 'WINSOCK ERROR', mb_OKCancel +
      mb_DefButton1);
    exit;
  end;
  sin.sin_family := AF_INET;
  sin.sin_addr.s_addr := INADDR_ANY;
  szTcp := 'tcp';
  StrPCopy(szPort,FPort);
  Pse := getservbyname(szPort,szTcp);
  if Pse = nil then
     sin.sin_port := htons(StrToInt(StrPas(szPort)))
  else sin.sin_port := Pse^.s_port;
  Ppe := getprotobyname(szTcp);
  FMSocket := socket(PF_INET,SOCK_STREAM,Ppe^.p_proto);
  if FMSocket < 0 then
    SocketError(INVALID_SOCKET,'socket',WSAGetLastError);
  iStatus := bind(FMSocket, sin, sizeof(sin));
  if iStatus <> 0 then
    SocketError(FMSocket,'Bind',WSAGetLastError);
  iStatus := listen(FMSocket,5);
  if iStatus <> 0 then
    SocketError(FMSocket,'Listen',WSAGetLastError);
  if FMode = 1 then
  begin
    iStatus := WSAASyncSelect(FMSocket,Handle,WM_ASYNCSELECT,
      FD_READ or FD_WRITE or FD_ACCEPT or FD_CLOSE);
    if iStatus <> 0 then
      SocketError(FMSocket,'WSAASyncSelect',WSAGetLastError);
  end
  else ioctlsocket(FMSocket,FIONBIO,FMode);
end;

procedure TSockets.SCancelListen;
var
  iStatus: integer;
begin
  if FMode = 1 then
    WSAASyncSelect(FMSocket,Handle,WM_ASYNCSELECT,0);
  shutdown(FMSocket,2);
  iStatus := closesocket(FMSocket);
  if iStatus <> 0 then
    SocketError(FMSocket,'CancelListen (closesocket)',WSAGetLastError);
  FMSocket := 0;
end;
 

function TSockets.SAccept: TSocket;
var
  iStatus: integer;
  len: integer;
begin
  len := sizeof(sin);
  if FMode = 0 then
    SetTimeout;
  FSocket := accept(FMSocket,sin,len);
  if FMode = 0 then
  begin
    ResetTimeout;
    ioctlsocket(FSocket,FIONBIO,FMode);
  end;
  if FMSocket < 0 then
    SocketError(FSocket,'Accept',WSAGetLastError);
  Result := FSocket;
end;

procedure TSockets.SClose;
var
  iStatus: integer;
  lin: TLinger;
  linx: array[0..3] of char absolute lin;
begin
  if FMode = 1 then
    WSAASyncSelect(FSocket,Handle,WM_ASYNCSELECT,0);
  if WSAIsBlocking then
    WSACancelBlockingCall;
  shutdown(FSocket,2);
  lin.l_onoff := 1;
  lin.l_linger := 0;
  setsockopt(FSocket,SOL_SOCKET,SO_LINGER,linx,sizeof(lin));
  iStatus := closesocket(FSocket);
  if iStatus <> 0 then
    SocketError(FSocket,'Disconnect (closesocket)',WSAGetLastError);
  FSocket := INVALID_SOCKET;
end;
 

procedure TSockets.SocketError(Socket: TSocket; sockfunc: string; error: Integer);
var
  szLine: array[0..255]  of char;
  line, ErrMsg: string;
begin
  ErrMsg := SocketErrorDesc(error);
  line := 'Error '+ IntToStr(error) + ' in function ' + sockfunc +
  #13#10 + ErrMsg;
  if Assigned(FErrorOccurred) then
    FErrorOccurred(Self,Socket,error,ErrMsg)
  else
    begin
      StrPCopy(szLine,line);
      Application.MessageBox(szLine, 'WINSOCK ERROR', mb_OKCancel +
        mb_DefButton1);
      halt;
    end;
end;

function TSockets.SocketErrorDesc(error: integer) : string;
begin
  case error of
    WSAEINTR:
      SocketErrorDesc := 'Interrupted system call';
    WSAEBADF:
      SocketErrorDesc := 'Bad file number';
    WSAEACCES:
      SocketErrorDesc := 'Permission denied';
    WSAEFAULT:
      SocketErrorDesc := 'Bad address';
    WSAEINVAL:
      SocketErrorDesc := 'Invalid argument';
    WSAEMFILE:
      SocketErrorDesc := 'Too many open files';
    WSAEWOULDBLOCK:
      SocketErrorDesc := 'Operation would block';
    WSAEINPROGRESS:
      SocketErrorDesc := 'Operation now in progress';
    WSAEALREADY:
      SocketErrorDesc := 'Operation already in progress';
    WSAENOTSOCK:
      SocketErrorDesc := 'Socket operation on non-socket';
    WSAEDESTADDRREQ:
      SocketErrorDesc := 'Destination address required';
    WSAEMSGSIZE:
      SocketErrorDesc := 'Message too long';
    WSAEPROTOTYPE:
      SocketErrorDesc := 'Protocol wrong type for socket';
    WSAENOPROTOOPT:
      SocketErrorDesc := 'Protocol not available';
    WSAEPROTONOSUPPORT:
      SocketErrorDesc := 'Protocol not supported';
    WSAESOCKTNOSUPPORT:
      SocketErrorDesc := 'Socket type not supported';
    WSAEOPNOTSUPP:
      SocketErrorDesc := 'Operation not supported on socket';
    WSAEPFNOSUPPORT:
      SocketErrorDesc := 'Protocol family not supported';
    WSAEAFNOSUPPORT:
      SocketErrorDesc := 'Address family not supported by protocol family';
    WSAEADDRINUSE:
      SocketErrorDesc := 'Address already in use';
    WSAEADDRNOTAVAIL:
      SocketErrorDesc := 'Can''t assign requested address';
    WSAENETDOWN:
      SocketErrorDesc := 'Network is down';
    WSAENETUNREACH:
      SocketErrorDesc := 'Network is unreachable';
    WSAENETRESET:
      SocketErrorDesc := 'Network dropped connection on reset';
    WSAECONNABORTED:
      SocketErrorDesc := 'Software caused connection abort';
    WSAECONNRESET:
      SocketErrorDesc := 'Connection reset by peer';
    WSAENOBUFS:
      SocketErrorDesc := 'No buffer space available';
    WSAEISCONN:
      SocketErrorDesc := 'Socket is already connected';
    WSAENOTCONN:
      SocketErrorDesc := 'Socket is not connected';
    WSAESHUTDOWN:
      SocketErrorDesc := 'Can''t send after socket shutdown';
    WSAETOOMANYREFS:
      SocketErrorDesc := 'Too many references: can''t splice';
    WSAETIMEDOUT:
      SocketErrorDesc := 'Connection timed out';
    WSAECONNREFUSED:
      SocketErrorDesc := 'Connection refused';
    WSAELOOP:
      SocketErrorDesc := 'Too many levels of symbolic links';
    WSAENAMETOOLONG:
      SocketErrorDesc := 'File name too long';
    WSAEHOSTDOWN:
      SocketErrorDesc := 'Host is down';
    WSAEHOSTUNREACH:
      SocketErrorDesc := 'No route to host';
    WSAENOTEMPTY:
      SocketErrorDesc := 'Directory not empty';
    WSAEPROCLIM:
      SocketErrorDesc := 'Too many processes';
    WSAEUSERS:
      SocketErrorDesc := 'Too many users';
    WSAEDQUOT:
      SocketErrorDesc := 'Disc quota exceeded';
    WSAESTALE:
      SocketErrorDesc := 'Stale NFS file handle';
    WSAEREMOTE:
      SocketErrorDesc := 'Too many levels of remote in path';
    WSASYSNOTREADY:
      SocketErrorDesc := 'Network sub-system is unusable';
    WSAVERNOTSUPPORTED:
      SocketErrorDesc := 'WinSock DLL cannot support this application';
    WSANOTINITIALISED:
      SocketErrorDesc := 'WinSock not initialized';
    WSAHOST_NOT_FOUND:
      SocketErrorDesc := 'Host not found';
    WSATRY_AGAIN:
      SocketErrorDesc := 'Non-authoritative host not found';
    WSANO_RECOVERY:
      SocketErrorDesc := 'Non-recoverable error';
    WSANO_DATA:
      SocketErrorDesc := 'No Data';
    else SocketErrorDesc := 'Not a WinSock error';
  end;
end;

procedure TSockets.SetTimeout;
begin
  if FTimeout > 0 then
    SetTimer(Handle,10,FTimeout*1000,nil);
end;

procedure TSockets.ResetTimeout;
begin
  if FTimeout > 0 then
    KillTimer(Handle,10);
end;

end.