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