unit kolTCPSocket;

////////////////////////////////////////////////////////////////////
//
//               TTTTTTTTTT   CCCCCCCC   PPPPPPPPP
//               T  TTTT  T  CCCC  CCCC  PPPP  PPPP
//                  TTTT     CCCC        PPPP  PPPP
//                  TTTT     CCCC        PPPP  PPPP
//                  TTTT     CCCC        PPPPPPPPP
//                  TTTT     CCCC  CCCC  PPPP
//                  TTTT      CCCCCCCC   PPPP
//
//                S     O     C     K     E     T
//
//   TCPServer, TCPClient implementation for Key Objects Library
//
//   (c) 2002 by Vorobets Roman
//                     Roman.Vorobets@p25.f8.n454.z2.fidonet.org
//
////////////////////////////////////////////////////////////////////

interface

uses
  kol,windows,winsock,messages;

const
  WM_SOCKET=WM_USER+1;
  WM_SOCKETDESTROY=WM_USER+2;

type
  twndmethod=procedure(var message:tmessage) of object;

  PTCPBase=^TTCPBase;
  PTCPServer=^TTCPServer;
  PTCPClient=^TTCPClient;
  PTCPServerClient=^TTCPServerClient;

  TKOLTCPClient=PTCPClient;
  TKOLTCPServer=PTCPServer;

  TOnTCPClientEvent = procedure(Sender: PTCPClient) of object;
  TOnTCPStreamSend = TOnTCPClientEvent;
  TOnTCPStreamReceive = TOnTCPClientEvent;
  TOnTCPConnect = TOnTCPClientEvent;
  TOnTCPManualReceive = TOnTCPClientEvent;
  TOnTCPDisconnect = TOnTCPClientEvent;

  TOnTCPReceive = procedure(Sender: PTCPClient; var Buf: array of byte; const Count: Integer) of object;
  TOnTCPResolve = procedure(Sender: PTCPClient; const IP: String) of object;
  TOnTCPAccept = function(Sender: PTCPServer; const IP: String;
    const Port: SmallInt):boolean of object;
  TOnTCPClientConnect = procedure(Sender: PTCPServerClient) of object;
  TOnTCPError = procedure(Sender: PObj; const Error:integer) of object;

  TTCPBase=object(TObj)
  private
    FWnd:HWnd;
    FConnecting: Boolean;
    function GetWnd: HWnd;
    procedure Method(var message:tmessage);virtual;
    procedure DoClose;
  private
    FPort: SmallInt;
    FOnConnect: TOnTCPConnect;
    FOnDisconnect: TOnTCPDisconnect;
    FOnError: TOnTCPError;
    FHandle: TSocket;
    FConnected: Boolean;
    FSection: TRTLCriticalSection;
    property Wnd:HWnd read GetWnd;
    function GetPort: SmallInt;
    procedure SetPort(const Value: SmallInt);
    procedure SetOnConnect(const Value: TOnTCPConnect);
    procedure SetOnDisconnect(const Value: TOnTCPDisconnect);
    procedure SetOnError(const Value: TOnTCPError);
    procedure SetHandle(const Value: TSocket);
    function ErrorTest(const e: integer): boolean;
  protected
    procedure Creating;virtual;
    destructor Destroy;virtual;
  public
    property Connected:Boolean read FConnected;
    property Online:Boolean read FConnected;
    property Connecting:Boolean read FConnecting;
    property Handle:TSocket read FHandle write SetHandle;
    property Port:SmallInt read GetPort{FPort} write SetPort;
    property OnError:TOnTCPError read FOnError write SetOnError;
    property OnConnect:TOnTCPConnect read FOnConnect write SetOnConnect;
    property OnDisconnect:TOnTCPDisconnect read FOnDisconnect write SetOnDisconnect;
    procedure Lock;
    procedure Unlock;
    procedure Disconnect;virtual;
  end;

  TTCPServer=object(TTCPBase)
  private
    FConnections: PList;
    FOnAccept: TOnTCPAccept;
    FOnClientConnect: TOnTCPClientConnect;
    FOnClientDisconnect: TOnTCPDisconnect;
    FOnClientError: TOnTCPError;
    FOnClientReceive: TOnTCPReceive;
    FOnClientManualReceive: TOnTCPManualReceive;
    FOnClientStreamReceive: TOnTCPStreamReceive;
    FOnClientStreamSend: TOnTCPStreamSend;
    procedure SetOnAccept(const Value: TOnTCPAccept);
    procedure SetOnClientConnect(const Value: TOnTCPClientConnect);
    procedure SetOnClientDisconnect(const Value: TOnTCPDisconnect);
    procedure SetOnClientError(const Value: TOnTCPError);
    procedure SetOnClientReceive(const Value: TOnTCPReceive);
    function GetConnection(Index: Integer): PTCPServerClient;
    function GetCount: Integer;
    procedure Method(var message: tmessage); virtual;
    procedure SetOnClientManualReceive(const Value: TOnTCPManualReceive);
    procedure SetOnClientStreamReceive(const Value: TOnTCPStreamReceive);
    procedure SetOnClientStreamSend(const Value: TOnTCPStreamSend);
  protected
    procedure Creating;virtual;
    destructor Destroy;virtual;
  public
    property OnAccept:TOnTCPAccept read FOnAccept write SetOnAccept;
    property OnClientError:TOnTCPError read FOnClientError write SetOnClientError;
    property OnClientConnect:TOnTCPClientConnect read FOnClientConnect write SetOnClientConnect;
    property OnClientDisconnect:TOnTCPDisconnect read FOnClientDisconnect write SetOnClientDisconnect;
    property OnClientReceive:TOnTCPReceive read FOnClientReceive write SetOnClientReceive;
    property OnClientManualReceive:TOnTCPManualReceive read FOnClientManualReceive write SetOnClientManualReceive;
    property OnClientStreamSend:TOnTCPStreamSend read FOnClientStreamSend write SetOnClientStreamSend;
    property OnClientStreamReceive:TOnTCPStreamReceive read FOnClientStreamReceive write SetOnClientStreamReceive;
    property Count:Integer read GetCount;
    property Connection[Index: Integer]: PTCPServerClient read GetConnection;
    procedure Listen;
    procedure Disconnect;virtual;
  end;

  TTCPClient=object(TTCPBase)
  private
    FHost: String;
    FBuffer: array[0..4095] of byte;
    FOnResolve: TOnTCPResolve;
    FOnReceive: TOnTCPReceive;
    FOnStreamSend: TOnTCPStreamSend;
    FSendStream: PStream;
    FSendAutoFree: Boolean;
    FReceiveStream: PStream;
    FReceiveAutoFree: Boolean;
    FReceiveAutoFreeSize: Integer;
    FReceiveStartPos: Integer;
    FOnManualReceive: TOnTCPManualReceive;
    FOnStreamReceive: TOnTCPStreamReceive;
    FIndex: Integer;
    procedure SetHost(const Value: String);
    procedure SetOnResolve(const Value: TOnTCPResolve);
    procedure SetOnReceive(const Value: TOnTCPReceive);
    procedure SetOnStreamSend(const Value: TOnTCPStreamSend);
    procedure Method(var message:tmessage);virtual;
    function SendStreamPiece: Boolean;
    procedure SetOnManualReceive(const Value: TOnTCPManualReceive);
    procedure SetOnStreamReceive(const Value: TOnTCPStreamReceive);
    procedure SetIndex(const Value: Integer);virtual;
  protected
    destructor Destroy;virtual;
  public
    property OnReceive:TOnTCPReceive read FOnReceive write SetOnReceive;
    property OnManualReceive:TOnTCPManualReceive read FOnManualReceive write SetOnManualReceive;
    property OnResolve:TOnTCPResolve read FOnResolve write SetOnResolve;
    property OnStreamSend:TOnTCPStreamSend read FOnStreamSend write SetOnStreamSend;
    property OnStreamReceive:TOnTCPStreamReceive read FOnStreamReceive write SetOnStreamReceive;
    property Host:String read FHost write SetHost;
    property Index:Integer read FIndex write SetIndex;
    function StreamSending:Boolean;
    function StreamReceiving:Boolean;
    procedure Connect;virtual;
    function Send(var Buf; const Count: Integer): Integer;
    procedure SendString(S: String);
    function SendStream(Stream: PStream; const AutoFree: Boolean): Boolean;
    procedure SetReceiveStream(Stream: PStream; const AutoFree: Boolean=false;
      const AutoFreeSize: Integer=0);
    function ReceiveLength: Integer;
    function ReceiveBuf(var Buf; Count: Integer): Integer;
  end;

  TTCPServerClient=object(TTCPClient)
  private
    FIP: String;
    FServer: PTCPServer;
    procedure SetIndex(const Value: Integer);virtual;
  public
    property IP: String read FIP;
    procedure Connect;virtual;
    procedure Disconnect;virtual;
  end;

function NewTCPServer: PTCPServer;
function NewTCPClient: PTCPClient;
function Err2Str(const id: integer): string;
function TCPGetHostByName(name: pchar): string;

procedure Startup;
procedure Cleanup;

implementation

type
  pobjectinstance=^tobjectinstance;
  tobjectinstance=packed record
    code:byte;
    offset:integer;
    case integer of
    0:(next:pobjectinstance);
    1:(method:twndmethod);
  end;

  pinstanceblock=^tinstanceblock;
  tinstanceblock=packed record
    next:pinstanceblock;
    code:array[1..2] of byte;
    wndprocptr:pointer;
    instances: array[0..$ff] of tobjectinstance;
  end;

var
  instblocklist:pinstanceblock;
  instfreelist:pobjectinstance;

  wsadata:twsadata;

function NewTCPServerClient(Server: PTCPServer): PTCPServerClient;forward;

function stdwndproc(window:hwnd;message:dword;wparam:WPARAM;
  lparam:LPARAM):LRESULT;stdcall;assembler;
asm
        XOR     EAX,EAX
        PUSH    EAX
        PUSH    LParam
        PUSH    WParam
        PUSH    Message
        MOV     EDX,ESP
        MOV     EAX,[ECX].Longint[4]
        CALL    [ECX].Pointer
        ADD     ESP,12
        POP     EAX
end;

function calcjmpoffset(src,dest:pointer):longint;
begin
  result:=longint(dest)-(longint(src)+5);
end;

function MakeObjectInstance(Method: TWndMethod): Pointer;
const
  blockcode:array[1..2] of byte=($59,$E9);
  pagesize=4096;
var
  block:pinstanceblock;
  instance:pobjectinstance;
begin
  if instfreelist=nil then
  begin
    block:=virtualalloc(nil,PageSize, MEM_COMMIT,PAGE_EXECUTE_READWRITE);
    block^.next:=instblocklist;
    move(blockcode,block^.code,sizeof(blockcode));
    block^.wndprocptr:=pointer(calcjmpoffset(@block^.code[2],@stdwndproc));
    instance:=@block^.instances;
    repeat
      instance^.code:=$E8;
      instance^.offset:=calcjmpoffset(instance,@block^.code);
      instance^.next:=instfreelist;
      instfreelist:=instance;
      inc(longint(instance),sizeof(tobjectinstance));
    until longint(instance)-longint(block)>=sizeof(tinstanceblock);
    instblocklist:=block;
  end;
  result:=instfreelist;
  instance:=instfreelist;
  instfreelist:=instance^.next;
  instance^.method:=method;
end;

procedure FreeObjectInstance(ObjectInstance: Pointer);
begin
  if objectinstance<>nil then
  begin
    pobjectinstance(objectinstance)^.next:=instfreelist;
    instfreelist:=objectinstance;
  end;
end;

var
  utilclass:twndclass=(lpfnwndproc:@defwindowproc;lpszclassname:'TCPSocket');

function AllocateHWnd(Method: TWndMethod): HWND;
var
  tempclass:twndclass;
  classregistered:boolean;
begin
  utilclass.hinstance:=hinstance;
  classregistered:=getclassinfo(hinstance,utilclass.lpszclassname,tempclass);
  if not classregistered or (tempclass.lpfnwndproc<>@defwindowproc) then
  begin
    if classregistered then unregisterclass(utilclass.lpszclassname,hinstance);
    registerclass(utilclass);
  end;
  result:=createwindowex(WS_EX_TOOLWINDOW,utilclass.lpszclassname,nil,
    WS_POPUP,0,0,0,0,0,0,hinstance,nil);
  if assigned(method) then setwindowlong(result,GWL_WNDPROC,longint(makeobjectinstance(method)));
end;

procedure DeallocateHWnd(Wnd: HWND);
var
  instance:pointer;
begin
  instance:=pointer(getwindowlong(wnd,GWL_WNDPROC));
  destroywindow(wnd);
  if instance<>@defwindowproc then freeobjectinstance(instance);
end;

procedure Startup;
begin
  if bool(wsastartup($101,wsadata)) then showmessage('WSAStartup error.');
end;

procedure Cleanup;
begin
  if bool(wsacleanup) then showmessage('WSACleanup error');
end;

{ TTCPBase }

procedure TTCPBase.Creating;
begin
  startup;
  initializecriticalsection(fsection);
  fhandle:=SOCKET_ERROR;
end;

destructor TTCPBase.Destroy;
begin
  if fwnd<>0 then deallocatehwnd(fwnd);
  doclose;
  disconnect;
  deletecriticalsection(fsection);
  cleanup;
end;

procedure TTCPBase.Disconnect;
begin
  if fhandle<>SOCKET_ERROR then
  begin
    doclose;
    if fconnected then
    begin
      fconnected:=false;
      if assigned(ondisconnect) then ondisconnect(@self);
    end;
    fconnecting:=false;
  end;
end;

procedure TTCPBase.DoClose;
begin
  if fhandle<>SOCKET_ERROR then
  begin
    errortest(closesocket(fhandle));
    fhandle:=SOCKET_ERROR;
  end;  
end;

function TTCPBase.ErrorTest(const e: integer): boolean;
var
  wsae: Integer;
begin
{ msgok(int2str(e));
 msgok(int2str(SOCKET_ERROR));
 msgok(int2str(INVALID_SOCKET)); }

  result:= (e = SOCKET_ERROR) or (e = INVALID_SOCKET);
  if result then begin
    wsae:=wsagetlasterror;
    if wsae<>WSAEWOULDBLOCK then
    begin
      if assigned(onerror) then onerror(@self,wsae) else
        showmessage('Socket error '+err2str(wsae)+' on socket '+int2str(fhandle));
    end else result:=false;   
  end;
end;

function TTCPBase.GetWnd: HWnd;
begin
  if fwnd=0 then fwnd:=allocatehwnd(method);
  result:=fwnd;
end;

procedure TTCPBase.Lock;
begin
  entercriticalsection(fsection);
end;

procedure TTCPBase.Method(var message: tmessage);
begin
  if message.msg<>WM_SOCKET then exit;
  if message.lparamhi>WSABASEERR then
  begin
    wsasetlasterror(message.lparamhi);
    errortest(SOCKET_ERROR);
    if fconnecting then doclose;
    fconnecting:=false;
  end;
  case message.lparamlo of
  FD_CLOSE:begin
      fconnected:=false;
      fconnecting:=false;
      if assigned(ondisconnect) then ondisconnect(@self);
      if fhandle<>SOCKET_ERROR then doclose;
    end;
  end;
end;

procedure TTCPBase.SetHandle(const Value: TSocket);
begin
  FHandle := Value;
end;

procedure TTCPBase.SetOnDisconnect(const Value: TOnTCPDisconnect);
begin
  FOnDisconnect := Value;
end;

procedure TTCPBase.SetOnError(const Value: TOnTCPError);
begin
  FOnError := Value;
end;

procedure TTCPBase.SetPort(const Value: SmallInt);
begin
  FPort := Value;
end;

function TTCPBase.GetPort: SmallInt;
var buf: sockaddr_in; bufSz: Integer;
begin
  if FConnected then
  begin
    bufSz := SizeOf(buf);
    ZeroMemory( @buf, bufSz );
    getsockname(fhandle, buf, bufSz);
    FPort := htons(buf.sin_port);
  end;
  Result := FPort;
end;

function NewTCPServer: PTCPServer;
begin
  new(result,create);
  result.creating;
end;

function NewTCPClient: PTCPClient;
begin
  new(result,create);
  result.creating;
end;

function NewTCPServerClient(Server: PTCPServer): PTCPServerClient;
begin
  new(result,create);
  result.creating;
  result.fserver:=server;
end;

procedure TTCPBase.Unlock;
begin
  leavecriticalsection(fsection);
end;

{ TTCPClient }

procedure TTCPClient.Connect;
var
  adr: TSockAddr;
begin
  disconnect;
  fhandle:= socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if not errortest(fhandle) then begin
    WSAAsyncSelect(fhandle, wnd, WM_SOCKET, FD_READ or FD_WRITE or FD_CONNECT or FD_CLOSE);
    with adr do begin
      sin_family:=        AF_INET;
      sin_port:=          htons(port);
      //Integer(sin_addr):= inet_addr(PChar(host));
      sin_addr.S_addr:= inet_addr(PChar(host));

      if Integer(sin_addr) = SOCKET_ERROR then begin
        sin_addr.S_addr:= PInAddr(gethostbyname(PChar(Host)).h_addr_list^)^.S_addr;
      end;

//msgok('bly' + int2str(sin_addr.S_addr));
      {if Integer(sin_addr) = SOCKET_ERROR then begin
      // must be WSAAsyncGetHostByName
        ph:= winsock.gethostbyname(pchar(host));
        if ph=nil then showmessage('gethostbyname() error');
        move(ph.h_addr^^,sin_addr,ph.h_length);
        if assigned(onresolve) then onresolve(@self,inet_ntoa(adr.sin_addr));
      end;}
      
    end;
    fconnecting:= not errortest(Integer(adr.sin_addr)) and not errortest(WinSock.connect(fhandle, adr, SizeOf(adr)));
    if not fconnecting then doclose;
  end;
end;

destructor TTCPClient.Destroy;
begin
  if fsendautofree and (fsendstream<>nil) then fsendstream.free;
  fsendstream:=nil;
  inherited;
end;

function TTCPClient.StreamReceiving: Boolean;
begin
  Result:= Assigned(FReceiveStream);
end;

function TTCPClient.StreamSending: Boolean;
begin
  Result:= Bool(fsendstream);
end;

procedure TTCPClient.Method(var message: tmessage);
var
  sz:integer;
begin
  inherited;
  if (message.msg<>WM_SOCKET) then exit;
  if message.lparamhi>WSABASEERR then
  begin
    if message.lparamlo=FD_CLOSE then
    begin
      if streamsending then
      begin
        if fsendautofree then fsendstream.free;
        if assigned(onstreamsend) then onstreamsend(@self);
      end;
      if streamreceiving then
      begin
        if freceiveautofree then freceivestream.free;
        if assigned(onstreamreceive) then onstreamreceive(@self);
      end;
    end;
  end else
  case message.lparamlo of
  FD_CONNECT:begin
        fconnected:=true;
        fconnecting:=false;
        if assigned(onconnect) then onconnect(@self);
      end;
  FD_READ:if (freceivestream=nil) and assigned(onmanualreceive) then onmanualreceive(@self) else
      begin
        lock;
//        repeat
          ioctlsocket(fhandle,FIONREAD,sz);
          if sz>0 then
          begin
            if sz>sizeof(fbuffer) then sz:=sizeof(fbuffer);
            sz:=receivebuf(fbuffer,sz);
            errortest(sz);
            if freceivestream<>nil then
            begin
              freceivestream.write(fbuffer,sz);
              if assigned(onstreamreceive) then onstreamreceive(@self);
            end else if assigned(onreceive) then onreceive(@self,fbuffer,sz);
          end;
//        until (sz<=0) or //not fmaxsendstreamspeed or
//          ((freceivestream<>nil) and freceiveautofree and
//           (freceivestream.size>=freceiveautofreesize));
        unlock;
        if (freceivestream<>nil) and freceiveautofree and
           (integer(freceivestream.position)+freceivestartpos>=freceiveautofreesize) then
        begin
          freceivestream.free;
          freceivestream:=nil;
          if assigned(onstreamreceive) then onstreamreceive(@self);
        end;
      end;
  FD_WRITE:if streamsending then sendstreampiece;// else if assigned(onwrite) then onwrite(@self);
  end;
end;

function TTCPClient.ReceiveBuf(var Buf; Count: Integer): Integer;
begin
  result:=0;
  if not fconnected or (fhandle=SOCKET_ERROR) or (count<=0) then exit;
  lock;
  result:=recv(fhandle,buf,count,0);
  errortest(result);
  unlock;
end;

function TTCPClient.ReceiveLength: Integer;
begin
  ioctlsocket(fhandle,FIONREAD,result);
end;

function TTCPClient.Send(var Buf; const Count: Integer): Integer;
begin
  result:=winsock.send(fhandle,buf,count,0);
end;

function TTCPClient.SendStream(Stream: PStream; const AutoFree: Boolean): Boolean;
begin
  result:=false;
  if fsendstream=nil then
  begin
    fsendstream:=stream;
    fsendautofree:=autofree;
    result:=sendstreampiece;
  end;
end;

function TTCPClient.SendStreamPiece: Boolean;
var
  buf:array[0..4095] of byte;
  startpos,amountinbuf,amountsent:integer;
begin
  result:=false;
  if not fconnected or (fhandle=SOCKET_ERROR) or (fsendstream=nil) then exit;
  lock;
  repeat
    startpos:=fsendstream.position;
    amountinbuf:=fsendstream.read(buf,sizeof(buf));
    if amountinbuf>0 then
    begin
      amountsent:=send(buf,amountinbuf);
      if amountsent=SOCKET_ERROR then
      begin
        if errortest(SOCKET_ERROR) then
        begin
          fsendstream:=nil;
          break;
        end else
        begin
          fsendstream.position:=startpos;
          break;
        end;
      end else
      if amountinbuf>amountsent then fsendstream.position:=startpos+amountsent else
      if fsendstream.position=fsendstream.size then
      begin
        if fsendautofree then fsendstream.free;
        fsendstream:=nil;
        break;
      end;
    end else
    begin            
      fsendstream:=nil;
      break;
    end;
  until false;
  result:=true;
  unlock;
  if assigned(onstreamsend) then onstreamsend(@self);
end;

procedure TTCPClient.SendString(S: String);
begin
  send(s[1], length(s));
end;                    

procedure TTCPClient.SetHost(const Value: String);
begin
  FHost := Value;
end;

procedure TTCPClient.SetIndex(const Value: Integer);
begin
  FIndex := Value;
end;

procedure TTCPBase.SetOnConnect(const Value: TOnTCPConnect);
begin
  FOnConnect := Value;
end;

procedure TTCPClient.SetOnManualReceive(const Value: TOnTCPManualReceive);
begin
  FOnManualReceive := Value;
end;

procedure TTCPClient.SetOnReceive(const Value: TOnTCPReceive);
begin
  FOnReceive := Value;
end;

procedure TTCPClient.SetOnResolve(const Value: TOnTCPResolve);
begin
  FOnResolve := Value;
end;

procedure TTCPClient.SetOnStreamReceive(const Value: TOnTCPStreamReceive);
begin
  FOnStreamReceive := Value;
end;

procedure TTCPClient.SetOnStreamSend(const Value: TOnTCPStreamSend);
begin
  FOnStreamSend := Value;
end;

procedure TTCPClient.SetReceiveStream(Stream: PStream; const AutoFree: Boolean = False; const AutoFreeSize: Integer=0);
begin
  if Autofree and (AutoFreeSize = 0) then Exit;
  if Assigned(FReceiveStream) then FReceiveStream.free;
  FReceiveAutoFree:= AutoFree;
  FReceiveAutoFreeSize:= AutoFreeSize;
  FReceiveStartpos:= Stream.Position;
  FReceiveStream:= Stream;
end;

{ TTCPServer }

procedure TTCPServer.Creating;
begin
  inherited;
  fconnections:=newlist;
end;

destructor TTCPServer.Destroy;
var
  i:integer;
begin
  for i:=0 to pred(count) do connection[i].free;
  fconnections.free;
  fconnections:=nil;
  inherited;
end;

procedure TTCPServer.Disconnect;
begin
  if fconnections=nil then exit;
  lock;
  while count>0 do connection[0].disconnect;
  unlock;
  inherited;
end;

function TTCPServer.GetConnection(Index: Integer): PTCPServerClient;
begin
  result:=ptcpserverclient(fconnections.items[index]);
end;

function TTCPServer.GetCount: Integer;
begin
  result:=fconnections.count;
end;

procedure TTCPServer.Listen;
var
  adr:tsockaddr;
begin
  if fhandle<>SOCKET_ERROR then exit;
  fhandle:=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
  if not errortest(fhandle) then
  begin
    with adr do
    begin
      sin_family:=AF_INET;
      sin_port:=htons(port);
      integer(sin_addr):=INADDR_ANY;
    end;
    if errortest(bind(fhandle,adr,sizeof(adr))) then doclose else
    begin
      wsaasyncselect(fhandle,wnd,WM_SOCKET,FD_ACCEPT or FD_CLOSE or FD_CONNECT);
      if errortest(winsock.listen(fhandle,64)) then
        doclose
      else
      begin
        FConnected := True;
        if assigned(onconnect) then onconnect(@self);
      end;
    end;
  end;
end;

procedure TTCPServer.Method(var message: tmessage);
var
  adr:tsockaddr;
  sz:integer;
  sock:TSocket;
  sclient:ptcpserverclient;
begin
  inherited;
  case message.msg of
  WM_SOCKET:
    if message.lparamhi<=WSABASEERR then
    case message.lparamlo of
    FD_ACCEPT:begin
        sz:=sizeof(adr);
        sock:=accept(fhandle,@adr,@sz);
        if not errortest(sock) then
        begin
          if not assigned(onaccept) or onaccept(@self,inet_ntoa(adr.sin_addr),htons(adr.sin_port)) then
          begin
            sclient:=newtcpserverclient(@self);
            with sclient^ do
            begin
              wsaasyncselect(sock,wnd,WM_SOCKET,FD_READ or FD_WRITE or FD_CLOSE);
              fhost:=inet_ntoa(adr.sin_addr);
              fport:=htons(adr.sin_port);
              fip:=fhost;
              fhandle:=sock;
              fconnected:=true;
              fconnecting:=false;
              findex:=fconnections.count;
              onerror:=onclienterror;
              ondisconnect:=onclientdisconnect;
              onreceive:=onclientreceive;
              onmanualreceive:=onclientmanualreceive;
              onstreamsend:=onclientstreamsend;
              onstreamreceive:=onclientstreamreceive;
            end;
            fconnections.add(sclient);
            if assigned(onclientconnect) then onclientconnect(sclient);
          end else closesocket(sock);
        end;  
      end;
    end;
  WM_SOCKETDESTROY:ptcpserverclient(message.wparam).free;
  end;
end;

procedure TTCPServer.SetOnAccept(const Value: TOnTCPAccept);
begin
  FOnAccept := Value;
end;

procedure TTCPServer.SetOnClientConnect(const Value: TOnTCPClientConnect);
begin
  FOnClientConnect := Value;
end;

procedure TTCPServer.SetOnClientDisconnect(const Value: TOnTCPDisconnect);
begin
  FOnClientDisconnect := Value;
end;

procedure TTCPServer.SetOnClientError(const Value: TOnTCPError);
begin
  FOnClientError := Value;
end;

procedure TTCPServer.SetOnClientManualReceive( const Value: TOnTCPManualReceive);
begin
  FOnClientManualReceive := Value;
end;

procedure TTCPServer.SetOnClientReceive(const Value: TOnTCPReceive);
begin
  FOnClientReceive := Value;
end;

function Err2Str(const id: integer): string;
begin
  case id of
  WSAEINTR:result:='WSAEINTR';
  WSAEBADF:result:='WSAEBADF';
  WSAEACCES:result:='WSAEACCES';
  WSAEFAULT:result:='WSAEFAULT';
  WSAEINVAL:result:='WSAEINVAL';
  WSAEMFILE:result:='WSAEMFILE';
  WSAEWOULDBLOCK:result:='WSAEWOULDBLOCK';
  WSAEINPROGRESS:result:='WSAEINPROGRESS';
  WSAEALREADY:result:='WSAEALREADY';
  WSAENOTSOCK:result:='WSAENOTSOCK';
  WSAEDESTADDRREQ:result:='WSAEDESTADDRREQ';
  WSAEMSGSIZE:result:='WSAEMSGSIZE';
  WSAEPROTOTYPE:result:='WSAEPROTOTYPE';
  WSAENOPROTOOPT:result:='WSAENOPROTOOPT';
  WSAEPROTONOSUPPORT:result:='WSAEPROTONOSUPPORT';
  WSAESOCKTNOSUPPORT:result:='WSAESOCKTNOSUPPORT';
  WSAEOPNOTSUPP:result:='WSAEOPNOTSUPP';
  WSAEPFNOSUPPORT:result:='WSAEPFNOSUPPORT';
  WSAEAFNOSUPPORT:result:='WSAEAFNOSUPPORT';
  WSAEADDRINUSE:result:='WSAEADDRINUSE';
  WSAEADDRNOTAVAIL:result:='WSAEADDRNOTAVAIL';
  WSAENETDOWN:result:='WSAENETDOWN';
  WSAENETUNREACH:result:='WSAENETUNREACH';
  WSAENETRESET:result:='WSAENETRESET';
  WSAECONNABORTED:result:='WSAECONNABORTED';
  WSAECONNRESET:result:='WSAECONNRESET';
  WSAENOBUFS:result:='WSAENOBUFS';
  WSAEISCONN:result:='WSAEISCONN';
  WSAENOTCONN:result:='WSAENOTCONN';
  WSAESHUTDOWN:result:='WSAESHUTDOWN';
  WSAETOOMANYREFS:result:='WSAETOOMANYREFS';
  WSAETIMEDOUT:result:='WSAETIMEDOUT';
  WSAECONNREFUSED:result:='WSAECONNREFUSED';
  WSAELOOP:result:='WSAELOOP';
  WSAENAMETOOLONG:result:='WSAENAMETOOLONG';
  WSAEHOSTDOWN:result:='WSAEHOSTDOWN';
  WSAEHOSTUNREACH:result:='WSAEHOSTUNREACH';
  WSAENOTEMPTY:result:='WSAENOTEMPTY';
  WSAEPROCLIM:result:='WSAEPROCLIM';
  WSAEUSERS:result:='WSAEUSERS';
  WSAEDQUOT:result:='WSAEDQUOT';
  WSAESTALE:result:='WSAESTALE';
  WSAEREMOTE:result:='WSAEREMOTE';
  WSASYSNOTREADY:result:='WSASYSNOTREADY';
  WSAVERNOTSUPPORTED:result:='WSAVERNOTSUPPORTED';
  WSANOTINITIALISED:result:='WSANOTINITIALISED';
  WSAHOST_NOT_FOUND:result:='WSAHOST_NOT_FOUND';
  WSATRY_AGAIN:result:='WSATRY_AGAIN';
  WSANO_RECOVERY:result:='WSANO_RECOVERY';
  WSANO_DATA:result:='WSANO_DATA';
  else result:='WSAEUNKNOWN';
  end;
end;

procedure TTCPServer.SetOnClientStreamReceive( const Value: TOnTCPStreamReceive);
begin
  FOnClientStreamReceive := Value;
end;

procedure TTCPServer.SetOnClientStreamSend(const Value: TOnTCPStreamSend);
begin
  FOnClientStreamSend := Value;
end;

{ TTCPServerClient }

procedure TTCPServerClient.Connect;
begin
  showmessage('Can''t connect ServerClient');
end;

procedure TTCPServerClient.Disconnect;
var
  i,j:integer;
  srv:ptcpserver;
begin
  if fserver<>nil then
  begin
    srv:=fserver;
    fserver:=nil;
    srv.lock;
    i:=srv.fconnections.indexof(@self);
    for j:=pred(srv.fconnections.count) downto succ(i) do dec(srv.connection[j].findex);
    srv.fconnections.delete(i);
    srv.unlock;
    postmessage(srv.wnd,WM_SOCKETDESTROY,integer(@self),0);
  end;
  inherited;
end;

function TCPGetHostByName(name: pchar): string;
var
  host:phostent;
  adr:in_addr;
begin
  host:=gethostbyname(name);
  move(host.h_addr^^,adr,host.h_length);
  result:=inet_ntoa(adr);
end;

procedure TTCPServerClient.SetIndex(const Value: Integer);
begin
  showmessage('Can''t set index of ServerClient');
end;

initialization
  instblocklist:=nil;
  instfreelist:=nil;

end.
