SOCKET Object
Updated: June 4, 2006

As in the rest of HotBasic, "buffer overflows" are not allowed in the SOCKET
object.  The number of bytes to be transferred from a socket buffer is always
known and application buffers are allocated accordingly.


PROPERTIES (Read/Write):
~~~~~~~~~~ ~~~~~~~~~~~~~
Family     Default=2 (AF_INET)

Port

Protocol   Default=0 (IP)

Type       Default=1 (STREAM)


NAME$ and IP$ refer to string values for host name and IP address.
S is a socket handle.

PROPERTIES (Read Only String):
~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~
AddrByName (NAME$) Lookup of IP address based on host name
           One use is to get IP$ from NAME$ for .Connect.
           If lookup fails, returned string$ = NULL
           If NAME$ = an IP address, string$ = NAME$

AddrToStr  (Addr) Returns string value for IP address
           Used to extract readable IP addresses from packets. 

GetHostIP  No arguments

GetHostName
           No arguments
           Local host network NAME$ is obtained with .NameByAddr.

GetPeerName
           (S) Returns IP address of client by .Accept socket

LastError **
           Returns text description of last error

NameByAddr (IP$) Reverse lookup host name for IP address IP$

Peek       (S, nbytes) Reads data which remains in socket buffer

Read       (S, nbytes) Reads data from socket receive buffer

ReadByte   (S) Reads one byte (Note: byte is returned as string.)

ReadLine   (S) Reads line.  Use with caution if S is blocking

  Note: If S is blocking, read and recv functions can hang the application
  if the peer computer sends no data.  Use .IsServerReady first or better,
  design internet applications with non-blocking sockets.

  A null return string is possible and should be checked before proceeding.

  IF r$.length THEN Goto NextStep ELSE Goto WeGotZip

Generally, a return value of -1 is error.
S is a method and in this text also refers to a LONG var S for a socket
handle.
You may create an array of socket handles.  Therefore, there is usually
little reason to dimension more than one socket object.

R refers to a LONG variable used to receive the function return values.
PORT refers to a LONG variable or value for port number.


PROPERTIES (Read Only Numeric):
~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
Accept     (S handle for .Listen or .Open port) Returns client socket handle

Bind       (S, IP$) Binds socket to specific network interface

Block      (S) Sets socket S to blocking mode (default)

CheckSum   (PacketSegment$) Returns IP checksum

Close      (S) Closes socket handle

CloseSocket
           (S) Closes socket handle.  Same as .Close

Connect    (S, IP$, PORT)  Return value depends on .Block state of S

  If S is Blocking (default) and R <= 0, connetion attempt failed.
  If S is NonBlocking, R < 0 is error or not connected yet.  Periodically,
  use .IsConnected to determine if socket is connected, i.e., writeable,
  or if there is a socket error. 

ConnectionReady
           (S) If R > 0, syn packet from client received

  Used for S of .Open listening ports and usually followed by .Accept(S).
  If R < 0, error condition in listening socket.  

EndConnection
           (S, mode) Does .Shutdown with mode

Handle     Handle of last created socket

IsConnected
           (S, secs) Tests if S is writeable

  If so, a connection exists and send buffer can accept data.
  secs is timeout or zero for non-blocking sockets.
  If R < 0, error condition in connection.  

  Note: When secs = 0, the .IsConnected(S, 0) function returns with an answer
  immediately (is non-blocking).  However, if secs is greater than 0, then
  .IsConnected will block until a connection outcome occurs or until the
  timeout has expired.  Generally speaking, multi-socket applications will
  use a timeout of zero.

IsServerReady
           (S) If R > 0, data has been received ("Server" = any peer host)
           If R < 0, error condition in connection.  

LastError  Result of WSAGetLastError

Listen     (S) Used after .S, .Bind and assigning .Port
           Used in place of .Open where a specific .Bind is used.

MySocket   Same as Handle

NonBlock   (S) Sets socket S to non-blocking mode

  For multiplexed clients and servers, all sockets are set to non-blocking
  after socket creation.  

  Note: This means that certain results are not guaranteed and the internet
  application must check all function results.  E.g., .IsConnected may = 0
  because the socket send/write buffer is full.  However, if .IsConnected < 0,
  the connection is lost.

  .Send and other write functions should always use the bytes sent return
  value as the best indicator of how many bytes were posted for sending.
  If .IsConnected = 0, wait before further send procedures.

Open       (PORT) Creates S according to SOCKET properties
           and sets that S to listen for clients on PORT.

Recv       (S, string$, nbytes)
           Reads nbytes bytes from socket S to buffer string$.
           string$.Length must be >= nbytes prior to use.
           Returns number of bytes transferred or error (-1).
           Note: HotBasic automatically uses string$ pointer.

RecvFrom   (S, string$, nbytes, lpRecvAddr)
           lpRecvAddr [out] is a pointer to sockaddr structure which
           will contain the IP address of the source IP host.
           Please see Recv.
           Used to read raw received packet IP header and data.
           
S          Creates socket with current .Family, .Protocol and .Type.

Select **  Raw wsock32 function

Send       (S, string$, nbytes)
           Writes nbytes bytes from buffer string$ to socket S.
           Returns number of bytes transferred or error (-1).
           Note: HotBasic automatically uses string$ pointer.

SendTo     (S, string$, nbytes, lpDestAddr)
           lpDestAddr [in] is a pointer to a sockaddr structure 
           with the destination address for the IP packet.
           string$ is the data section of the IP packet, which
           itself may have header and data depending on protocol.
           E.g., for TCP, string$ is TCP header and optional data.
           Please see Send.  Used to send custom packets.

SetSockOpt **

Shutdown   (S, mode) Same as .EndConnection
           Used before .Close to stop send and receive operations.

Socket     (Family, Protocol, Type) Returns socket handle S

StrToAddr  (IP$) Returns network long value for IP address
           R <= 0 indicates invalid IP$.

Transferred Number of bytes in last read or write

Write      (S, string$, nbytes)  Returns bytes written

WriteByte  (S, string$) One byte written

WriteLine  (S, string$) Writes line adding CRLF

Note: Professional socket programs will almost always check for error
or result values, since peer hosts may be exhibit unpredictable behavior.

Note: In HotBasic, strings, like arrays and memory streams, are "low level"
objects which may contain binary data.  For example, a string may contain
null bytes and its length property will still report the correct length of
the data it contains.  In brief, the functions below correctly return text-
only or binary data as might be used in particular network exchanges.  If
you are programming a binary protocol, like DNS or Socks, you can use string
buffers or if you are more comfortable, use an array or memory stream.
This applies for string arguments (above) or return values (below).


** Planned future additions to SOCKET object.

Note: .ConnectionReady, .IsServerReady and .IsConnected all use .Select on
a single socket and check for error state before checking for readability
or writeability.  These functions can test multiple sockets one at a time.
The raw .Select function allows test of multiple sockets in a single call.


Closing Connections
~~~~~~~~~~~~~~~~~~~
Always do .EndConnection when you are done with a connected S.  This is
a graceful manner to shutdown a TCP connection.  Depending on the mode you
choose, reads and writes are disabled and a FIN packet is sent to the peer
indicating the impending closure of the connection.

To free system resources, do .Close(S) when you are done with a particular
attempt to connect, whether or not a connection occurs.  This means another
.S must be done to get a new S handle.  Reliable sockets programming uses
this procedure to insure that events with the last socket handle operation
do not affect the results of a subsequent connection.


Multiplexed Clients or Servers
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For example, for n sockets in an array Peer(n), one can start a loop:

  DIM Sock As SOCKET  'one SOCKET object does everything here
  'these arrays are for anything specific for a specific client
  DIM Peer(1 to MaxSockets) As LONG, Flag(1 to MaxSockets) As LONG
  DIM Time(1 to MaxSockets) As LONG  'add other status values you need
  n = 1
  NextSock:
  IF Peer(n) <= 0 THEN Peer(n)=Sock.S: Flag(n) = 0  'if <= 0, slot not in use.
  S = Peer(n): Sleep 0.05  'sleep because internet is slow compared to CPU

  '.Connect or .IsConnected depending on Flag(n), Timer-Time(n), status of S
  'free the socket if you are done with it, else go to LoopSock
  R = Sock.EndConnection(S,2): R = Sock.Close(S)
  Peer(n) = 0  'so at NextSock, we know slot n is free

  LoopSock:
  IF n < MaxSockets THEN INC(n) ELSE n = 1
  DoEvents: Goto NextSock


Multiplexed Servers
~~~~~~~~~~~~~~~~~~~
For servers, you have two arrays of sockets:  one for listening ports, the
other for clients (on any port).

  'use this to indicate which service a client is connected to.
  DIM Serv(1 to MaxSockets) As Long
  DIM OpenPorts(1 to nServers) As Long

  'once at program startup
  FOR m = 1 to nServers
  OpenPort(m) = Sock.Open(Port(m))  'Port(m) is your list of ports
  IF OpenPort(m) <= 0 THEN
    PRINT "Could not open Port "+STR$(Port(m)): Goto ServerDone
  END IF
  R = Sock.NonBlock(OpenPort(m))
  NEXT m 

  'in NewConnectionLoop using index m for OpenPorts and n for Client ID,
  NewConnectionLoop:
  FOR m = 1 to nServers
  S = OpenPort(m)
  IF Sock.ConnectionReady(S) <= 0 THEN Goto NextListenPort

  'cS = client socket and t$ = client IP address
  cS = Sock.Accept(S): R = Sock.NonBlock(cS): t$ = Sock.GetPeerName(cS)

  'you can optionally check if client IP is in "blocked list" here.

  'if the server chooses to allow further access then find unused n
  IF cS > 0 THEN
    Peer(n) = cS: Serv(n) = Port(m): cIP(n) = t$
  ELSE
    e$ = "Failed to Accept client": Goto ServerError
  ENDIF
  'code
  NextListenPort:
  NEXT m

  n=1
  PeerLoop:
  IF Serv(n) = 0 THEN Goto NextPeer ELSE S = Peer(n)
  i = Sock.IsConnected(S,0)

  'ClientCleanUp closes socket, logs and clears ALL data for client n
  IF i < 0 THEN Call ClientCleanUp: Goto NextPeer  '-1 = dead.

  'Time(n) is updated with activity by your code; if not, timeout!
  IF Timer-Time(n) > TimeOut THEN Call ClientCleanUp: Goto NextPeer 

  'if not timeout
  IF i = 0 THEN Goto NextPeer 'means connection open, send buffer full

  'finally we have a client with a writeable connection
  'now to see if anything is received, etc, and update Time(n)
  SELECT CASE Serv(n)
  CASE 80: Call httpserver
  CASE 21: Call ftpserver
  'etc
  END SELECT  
  NextPeer:
  INC(n)
  IF n <= MaxSockets THEN Goto PeerLoop
  Sleep 0.05: Goto NewConnectionLoop
  'Don't forget the sleep ... or you will beat your CPU to death.
  'Thus, we continue repeat processing of new and existing connections

These code fragments may provide a "jump start" in writing a server.
Be sure to Dimension everything.  These code fragments just show ideas.


RapidQ-style Connect
~~~~~~~~~~~~~~~~~~~~
Here is code to use HotBasic functions to simulate a RapidQ-style .Connect.
This is for illustration only and should not be used because the function
contains four different error possibilities, but returns -1 without detail
on which error occurred.  In brief, the following code shows exactly why
the HotBasic .Connect is not implemented like a RapidQ .Connect.

DIM sock As SOCKET, S As LONG, r$ As STRING, R As LONG
DIM host$ As STRING, hostport As LONG
Declare FUNCTION QConnect(IP$ As STRING, Port As LONG) As LONG
'...
S=QConnect(host$,hostport)  'RQ-style syntax
IF S<=0 THEN Goto SockError
'...
FUNCTION QConnect(IP$ As STRING, Port As LONG) As LONG
Result=-1
r$=sock.AddrbyName(IP$)
IF r$.length=0 THEN Exit Function
S=sock.S  'assuming no change in defaults for family, protocol & type
IF S<=0 THEN Exit Function
IF Port<=0 THEN Exit Function
R=sock.Connect(S,r$,Port)
IF R>=0 THEN Result=S	
END FUNCTION


Copyright 2003-2005 James J Keene PhD
Original Publication: Sep 2, 2003
