DSock Tutorial Document

Draft version 01

 

By Marcade


Introduction

This document will describe in easy steps, how to use V1ctors DSock library. You will need the library itself, and the library only, to be able to use it. The code that comes with the library, dsock.bi, scc.bas and scs.bas can be used for learning references, but are very confusing at first sight. This document was written to clearify it, making it possible to practically copy/paste what you need, from this document into your program.

 

The DSock.bi file that comes with DSock is filled with allot of information, declarations and constants, fromwhich most of them, you will never use in your program. It is strongly suggested not to use DSock.bi and create your own .BI include. Every section of this document will contain a Blue Square Field with Dark Blue code. This code was found in the DSock.bi and is needed for the code in that section, to work properly. So basically you only need that code in your .BI file if you are going to use the code described in the section. Beware, if you will be using code from different sections, the blue squares might already contain code you found in a previous blue square. Do not include that code, twice in your .BI file.

 

All code written in blue, which is outside the DSock.bi declaration square, is code that will need code included in the Blue Square Field on top.

 

All code written in Red, mostly being comments, should be read as pseudo code. The user self has to decide what to do then, at that spot, since it differs from every program.

 

All code written in Purple, is mostly a variable, depending on code written in another section of this document. For example lSocketHandle cannot immediately be used. It needs to contain the handle of a socket that was opened in another piece of code, written in a different section of this document.

Initializations

Before you can use Dsock, you will have to initialize it first. This initialization code will do that, and request extended information about the type of Winsock you have on your system.

 

 

Lines that need to be included

 

Type WSAData

wVersion AS INTEGER

wHighVersion AS INTEGER

szDescription AS STRING * WSADESCRIPTIONLEN%

szSystemStatus AS STRING * WSASYSSTATUSLEN%

iMaxSockets AS INTEGER

lpVendorInfo AS LONG

End Type

 

DECLARE FUNCTION WSAStartup% (BYVAL wVersionRequired AS INTEGER, SEG lpWSAData AS WSaData)

DECLARE FUNCTION MAKEWORD% (BYVAL lsb AS INTEGER, BYVAL msb AS INTEGER)

 

CONST WSADESCRIPTIONLEN% = 256  + 1

CONST WSASYSSTATUSLEN% = 128 + 1

 

 

DIM iRetVal AS INTEGER

DIM wsaDat AS WSAData

 

wVersionRequested = MAKEWORD(1, 1)

iRetVal = WSAStartup(wVersionRequested, wsaDat)

 

IF (wsaDat.wVersion <> wVersionRequested) THEN

        Error! WinSock version not supported. Do NOT try to use DSock.

END IF

 


Opening a New Socket

When you want to make a single connection, you first will have to request and open a new socket which you can use. When you have opened a socket, you will get a Socket Handle you will have to use for the rest of the time. Of course, you can open multiple sockets, and thus you then will have to manage multiple handles.

 

Since by default we are going to use the socket in a way where we do not want it to lag our program, we put it in NonBlockingMode. NonBlockingMode means, whenever we are polling the sockets, it will NOT wait until something happens, and it will NOT wait for the time to run out. NonBlockingMode means we will merely poll the socket.

 

If opening a socket fails, it is best just to close the socket again, display an error message, and continue your program.

 

Lines that need to be included

 

DECLARE FUNCTION socket& (BYVAL addrfamily AS INTEGER, BYVAL socktype AS INTEGER, BYVAL protocol AS INTEGER)

DECLARE FUNCTION ioctlsocket% (BYVAL s AS LONG, BYVAL cmd AS LONG, SEG argp AS LONG)

DECLARE FUNCTION closesocket% (BYVAL s AS LONG)

 

CONST AF.INET% = 2%

CONST SOCK.STREAM% = 1%

CONST IPPROTO.TCP% = 6%

CONST INVALID.SOCKET& = -1&

CONST FIONBIO& = -2147195266&

CONST SOCKET.ERROR% = -1%

 

 

DIM lSocketHandle AS LONG

DIM iRetVal AS INTEGER

 

‘ Create a new socket

lSocketHandle = socket(AF.INET%, SOCK.STREAM%, IPPROTO.TCP%)

IF lSocketHandle = INVALID.SOCKET& THEN

        Error! Socket could not be reserved.

END IF

 

‘ Put socket in non-blocking mode.

IF (ioctlsocket(lSocketHandle, FIONBIO&, 1) = SOCKET.ERROR%) THEN

        Error! Socket could not be put in non-blocking mode

        ‘ Close socket because we cannot use it in non-blocking mode

        IretVal = closesocket(lSocketHandle)

END IF

Connecting

When connecting to a remote computer, you first have to resolve the hostname or IP address. Using the DSock resolve functions gethostbyname&() or gethostbyaddr&(), it will return a pointer where more information about the host is found. Using different functions and the pointer, the information can be retrieved.

 

Note:     When using Windows 2000 or Windows XP, you also can resolve IP addresses with gethostbyname&(), but under Windows 95/98/ME this will not work and you have to use the gethostbyaddr&() function.

 

After the hostname has been resolved, the connect() function will be called with a special sockaddrIn record, and the socket will try to connect to that host.

 

Wether the connection succeeds or fails cannot be determined at this point. This should be checked when polling the sockets.

 

Lines that need to be included

 

TYPE inAddr

        Saddr AS LONG

END TYPE

TYPE sockaddrIn

        sinFamily AS INTEGER

        sinPort AS INTEGER

        sinAddr AS inAddr

        sinZero AS STRING * 8

END TYPE

 

DECLARE FUNCTION gethostbyname& (hname AS STRING)

DECLARE FUNCTION gethostbyaddr& (SEG addr AS ANY, BYVAL length AS INTEGER, BYVAL addrtype AS

                                INTEGER)

DECLARE FUNCTION hostent.hAddrList& ALIAS "hent_addr" (BYVAL entry AS LONG)

DECLARE FUNCTION hostent.hName$ ALIAS "hent_name" (BYVAL entry AS LONG)

DECLARE FUNCTION htons% (BYVAL hostshort AS INTEGER)

DECLARE FUNCTION inetAddr& ALIAS "inet_addr" (cp AS STRING)

DECLARE FUNCTION connect% (BYVAL s AS LONG, SEG connname AS ANY, BYVAL namelen AS INTEGER)

DECLARE FUNCTION closesocket% (BYVAL s AS LONG)

DECLARE FUNCTION WSAGetLastError% ()

 

CONST AF.INET% = 2%

CONST SOCKET.ERROR% = -1%

CONST INADDR.NONE& = &hffffffff&

 

CONST WSABASEERR% = 10000%

CONST WSAEWOULDBLOCK% = WSABASEERR + 35%

 

 

DIM iaHost AS inAddr

DIM lHostPointer AS LONG

DIM stHostName AS STRING

DIM SocketAddress AS sockaddrIn

DIM iRetVal AS INTEGER

DIM iPortNumber AS INTEGER

 

stHostName=”Hostname or IP address of computer you want to connect with.

iPortNumber=PortNumber you want to connect with.

 

iaHost.sAddr = inetAddr(stHostName)

 

‘ Retrieve information about the hostname

        IF (iaHost.sAddr = INADDR.NONE&) THEN

               ' The address isn't an IP address string, assume it is a name

               lHostPointer = gethostbyname(stHostName)

               if (lHostPointer = 0) then

                       Error! Did not retrieve a pointer. Cannot connect.

                       EXIT SUB

               END IF

        ELSE

               ' It was a valid IP address string.

               lHostPointer = gethostbyaddr(iaHost, LEN(iaHost), AF.INET%)

               IF (lHostPointer = 0) THEN

                       Error! Did not retrieve a pointer. Cannot connect.

                       EXIT SUB

               END IF

              

               ‘ Fetching the Server hostname from the IP address.

               lpServerName = hostent.hName(lHostPointer)

        END IF

 

SocketAddress.sinFamily = AF.INET%

SocketAddress.saddr = hostent.hAddrList(lHostPointer)

SocketAddress.sinport = htons(port)

 

iRetVal = connect(lSocketHandle, SocketAddress, LEN(SocketAddress))

IF iRetVal=SOCKET.ERROR% THEN

        IF WSAGetLastError% <> WSAEWOULDBLOCK% THEN

               Error! Failed to perform connect function.

               iRetVal = closesocket(lSocketHandle)

               EXIT SUB

        END IF

END IF

Polling the sockets for events.

Always poll your sockets. Polling the sockets will check if a socket is connected, if data has been received, or if an error has occurred. This is a part of code you will be calling over and over. Do not just call it if you are waiting for data; because then you will not know if your sockets still are connected or not, and if an error has occurred.

 

The general idea of this routine is, you will be calling selectsocket() and supply three lists, each list containing a list of sockets you want to poll for a certain state. The first list would be to check if new data has arrived, the second list to check if you can write to the socket, and thus if you’re (still) connected, and the thirth list to see if an error has occurred. Each list starts of with the number of sockets being in this list.

 

Our lists in this piece of code are called rfds() for the read list, wfds() for the write list, and efds() for the error list. We Dimensioned them from 0 to 1, because we are only polling a single socket here. If we’d be polling a list of sockets, we put the number of sockets as the first element of the list, and the socket handles in the following elements. Meaning we’d dimension the list bigger.

 

We also supply a timeval record with the selectsocket() function, which really has no use in our case. This only has meaning when we would be working in BlockingMode. Meaning the selectsocket() function will wait for an event to happen, or to time out, using the timevalues in the timeval record you’d supply. By default, in NonBlockingMode, set them to zero.

 

When the selectsocket() is performed, it will return a zero, if no events have happened. This is case when your sockets aren’t connected in some way. If it is lower than zero, an error occurred. If it is bigger than zero, it is the number of sockets which something happened with.

 

If something did happen, selectsocket() will return lists of the sockets in which something happened. For example, if data has arrived and stored in the receive buffer of one socket, rfds(0) would contain 1, meaning one socket has data in it’s received buffer, and rfds(1) would contain the handle of the socket. If two sockets have received data, rfds(0) would contain 2, and rfds(1) and rfds(2) would contain the handles of those two sockets, which received data.

 

Same goes for writing and errors. Whenever a socket is connected, you can write to it. Meaning wfds() will return the sockets that are connected. This is a way to test if your connection in the previous chapter has succeeded. If the connection would fail, the socket handle eventually would end up in the efds() list.

 

If there is a socket in the efds() list, close the socket, the connection has been terminated.

 

Lines that need to be included

TYPE timeval

        tvSec AS LONG ' Seconds

        tvUsec AS LONG ' And MicroSeconds

END TYPE

 

DECLARE FUNCTION closesocket% (BYVAL s AS LONG)

DECLARE FUNCTION selectsocket% ALIAS "select" (BYVAL nfds AS INTEGER, SEG readfds AS LONG, SEG

                              writefds AS LONG, SEG exceptfds AS LONG, SEG timeout AS timeval)

 

 

 

DIM iRetVal AS INTEGER

DIM rfds(0 TO 1) AS LONG

DIM wfds(0 TO 1) AS LONG

DIM efds(0 TO 1) AS LONG

DIM TimeOutValues AS timeval

 

TimeOutValues.tvSec = 0 ‘ Wait zero seconds.

TimeOutValues.tvUsec = 0 ‘ Also wait zero MicroSeconds

 

rfds(0) = 1 ‘ Number of sockets included in the write list

rfds(1) = lSocketHandle

wfds(0) = 1 ‘ Number of sockets included in the write list

wfds(1) = lSocketHandle

efds(0) = 1 ‘ Number of sockets included in the error list

efds(1) = lSocketHandle

 

‘ Poll the lists of sockets.

iRetVal = selectsocket(0, rfds(0), wfds(0), efds(0), TimeOutValues)

 

IF iRetVal = 0 THEN

        Socket has no new updates. Thus the socket is not yet connected.

        EXIT SUB

END IF

 

IF iRetVal > 0 AND (rfds(0) <> 0) THEN

        Data has been received in the socket’s receive buffer.

END IF

 

IF iRetVal > 0 AND (wfds(0) <> 0) THEN

        It is possible to send data. The socket is connected.

END IF

 

IF iRetVal < 0 OR (efds(0) <> 0) THEN

        Error! Something wend wrong with the socket, close it.

        iRetVal = closesocket(lSocketHandle)

        EXIT SUB

END IF

Disconnect

Disconnecting and closing the socket is pretty straightforward. The number given with the shutdown() function determines how brutal the socket must kill the connection. There isn’t much that can go wrong here.

 

Lines that need to be included

 

DECLARE FUNCTION closesocket% (BYVAL s AS LONG)

DECLARE FUNCTION shutdown% (BYVAL s AS LONG, BYVAL how AS INTEGER)

 

 

DIM iRetVal AS INTEGER

 

iRetVal = shutdown(lSocketHandle, 2)

iRetVal = closesocket(lSocketHandle)

Deinitializing DSock

When you’re completely done with DSock, do not forget to uninitialize the library. Not doing so might result in some programs to remain in memory. Before you do a cleanup, make sure you shutdown and closed all the sockets first! Not much to it, really.

 

Lines that need to be included

 

DECLARE FUNCTION WSACleanup% ()

 

 

DIM iRetVal AS INTEGER

 

iRetVal = WSACleanup%

Sending Data

When sending data, make sure you did poll the socket first, and that it is connected. Or else you’ll surely get an error while trying to send data. For some reason, the send() function will not accept a simple STRING. It always wants the data to be sent away, to be in a record structure. That’s why the record was temporary made. Whenever you are going to code a decent program using DSock, you probably have all that stuff in a record anyways.

 

Lines that need to be included

 

DECLARE FUNCTION send% (BYVAL s AS LONG, BYVAL buf AS LONG, BYVAL length AS INTEGER, BYVAL flags

                        INTEGER)

DECLARE FUNCTION MAKELONG& (BYVAL lsw AS INTEGER, BYVAL msw AS INTEGER)

 

 

        TYPE SocketStuff

               lSocketHandle AS LONG

               stSendData AS STRING * 256

               iLengthOfData AS INTEGER

        END TYPE

 

DIM iRetVal AS INTEGER

DIM iBytesSent AS INTEGER

DIM Sock(0) AS SocketStuff

 

Sock(0).lSocketHandle = lSocketHandle

Sock(0).stSendData = ”Message you want to send

Sock(0).iLengthOfData = LEN(”Message you want to send”)

 

‘ Make SURE you do NOT get the length by performing LEN(Sock(0).stSendData) because that will just be

‘ 256, always.

 

iBytesSent = send(Sock(0).lSocketHandle, MAKELONG(VARPTR(Sock(0).stSendData),

   VARSEG(Sock(0).stSendData)), Sock(0).iLengthOfData, 0)

Receiving Data

When receiving data, make sure you did poll the socket first, and that it is connected. Or else you’ll surely get an error while trying to receive data. For some reason, the recv() function, just like the send() function, will not accept a simple STRING. It always wants the data to be sent away, to be in a record structure. That’s why the record was temporary made. Whenever you are going to code a decent program using DSock, you probably have all that stuff in a record anyways.

 

Lines that need to be included

 

DECLARE FUNCTION recv% (BYVAL s AS LONG, BYVAL buf AS LONG, BYVAL length AS INTEGER, BYVAL flags

                        AS INTEGER)

DECLARE FUNCTION MAKELONG& (BYVAL lsw AS INTEGER, BYVAL msw AS INTEGER)

 

 

        TYPE SocketStuff

               lSocketHandle AS LONG

               stReceivedData AS STRING * 256

               iLengthOfDataReceived AS INTEGER

        END TYPE

 

DIM iRetVal AS INTEGER

DIM stMessageReceived AS STRING

DIM Sock(0) AS SocketStuff

 

Sock(0).lSocketHandle = lSocketHandle

 

iRetVal = recv(lSocketHandle, MAKELONG(VARPTR(Sock(0). stReceivedData), VARSEG(Sock(0). stReceivedData)), LEN(Sock(0).stReceivedData), 0)

 

- Ignore the lower part of the document. It is badly written and barely helps you -

Type Definitions

WSAData

wVersion AS INTEGER

wHighVersion AS INTEGER

szDescription AS STRING * 257

szSystemStatus AS STRING * 129

iMaxSockets AS INTEGER

lpVendorInfo AS LONG

inAddr

Saddr AS LONG

sockaddrIn

sinFamily AS INTEGER

sinPort AS INTEGER

sinAddr AS inAddr

sinZero AS STRING * 8

 

Subs/Functions

closesocket([long]) AS INTEGER

This function will close and terminate a socket for usage. The parameter given is the socket handle.

connect([long], [any], [integer])

This function will connect a socket. The first parameter is the socket handle, the second parameter is the SocketAddress record that was defined earlier. The thirth parameter is the Length of the SocketAddress record.

gethostbyname([string]) AS LONG

This function will resolve a hostname and retrieve information. It returns a pointer that can be used by the hostent functions of Dsock.

 

Returns:

[Pointer to be used by hostend functions]

hostent.hAddrList ([long])

This will return the address thing? Parameter is the HostHandle retrieved with gethostbyname.

htons ([integer]) AS INTEGER

Returns a port thing that can be used for the host address record passed to a connect function. Parameter is the portnumber you want to use in your hostaddress record.

ioctrlsocket([long], [long], [long]) AS INTEGER

This function will help putting the socket into non-blocking mode. The first parameter is the Socket Handle, the second parameter the command what to do (Non blocking). The thirth I am not sure.

MAKEWORD([integer], [integer]) AS INTEGER

This function will create a word in the selected integer. I don’t know exactly what it really does.

 

Returns:

[unknown]

WSAGetLastError() AS INTEGER

This function will retrieve the error code of the last error that occurred.

WSAStartup([integer], [WSAData]) AS INTEGER

This function will start up Dsock and request the winsock version number. It will put all the information in a predefined record WSAData.

If the first [integer] does not matches wVersion in [WSAData] then the Winsock version requested, is not supported.

 

Returns:

0                                  -                       Ok.

<>0                               -                       Not Ok.

 

recv([long], [long], [integer], [integer])

Receives data from a socket. First parameter specifies the Socket Handle. Second Parameter specifies the Segment of the string where the array is at, created with MAKELONG. The Thirth Parameter specifies how long the message is. And the fourth is a flag to specify if it is an OOB message.

 

Returns:

-1                                 -                       Error Occured

Number of bytes received

selectsocket([integer], [long], [long], [long], [long]) AS INTEGER

This function checks for status changes of a list of sockets. First parameter is zero, this is for compatibility reasons. Second record is to check readability of the socket(s). Thirth checks the writability of the socket(s).Fouth checks for OOB stuff of the socket(s), and fifth parameters specifies timeout values. If the timeout values are zero, it will just check and immediately return the statuses. Else it will wait for the time.

 

Returns:

Above zero                     -                       Number of sockets ready

0                                  -                       Time out

-1                                 -                       Error occured

send([long], [long], [integer], [integer])

Sends data away through a socket. First parameter specifies the Socket Handle. Second Parameter specifies the Segment of the string where the array is at, created with MAKELONG. The Thirth Parameter specifies how long the message is. And the fourth is a flag to specify if it is an OOB message (Out Of Band; a message sent to the other party with priority, meaning it will disrupt the flow. For example, server is sending to client “ABCDEF” and then an OOB message “G” immediately after that. The client will receive something like “ABCGEF” or “AGBCDEF”. The G will get there ASAP.)

 

Returns:

-1                                 -                       Error occured

Number of bytes sent away. Does not have to comply with the length of the message.

socket([integer], [integer], [integer]) AS LONG

This function will create/reserve a new socket for you to use. First parameter specifies what kind of network you will be operating on. The second parameter will specify the type of connection you will make. The final parameter will specify what protocol you will be using.

 

Returns:

-1                                 -                       Could not open a new socket.

[Socket Handle]

shutdown([long], [integer]) AS INTEGER

This function will disconnect the socket selected in the first parameter. The second parameter is how brutal the socket should disconnect. (Standard it is 2)