DSock Tutorial Document
QB Express Issue #1
Written by Marcade
A note from the editor
This is a tutorial on using DSock written some time ago by Marcade. This covers most of what you need to know to implement DSock into your QB program, though it is not yet complete: it does not explain how to listen and accept to incoming connections. But according to Marcade, "that is very easy .. people can look at the source of Connect 4 to figure it out .. basically.
"Connect 4 was released with the source code for people to learn from it... The Networking code of Connect 4 can be used by other people if they want it (as long as credit is given). It's programmed in a way that it can be reused."
You can find Connect 4 QB and DSock at http://www.neozones.com/c4/
Introduction
This document will describe in easy steps, how to use V1ctor's 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 clarify 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 Fuchsia , 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 includedDECLARE 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.
Whether the connection succeeds or fails cannot be determined at this point. This should be checked when polling the sockets.
Lines that need to be includedTYPE inAddr
Saddr AS LONG
END TYPE
TYPE sockaddrIn
sinFamily AS INTEGER
sinPort AS INTEGER
sinAddr AS inAddr
sinZero AS STRING * 8
END TYPEDECLARE 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 INTEGERstHostName="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 IFSocketAddress.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
Lines that need to be includedDECLARE 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 includedDECLARE 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 TYPEDIM iRetVal AS INTEGER
DIM iBytesSent AS INTEGER
DIM Sock(0) AS SocketStuffSock(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 includedDECLARE 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 TYPEDIM iRetVal AS INTEGER
DIM stMessageReceived AS STRING
DIM Sock(0) AS SocketStuffSock(0).lSocketHandle = lSocketHandle
iRetVal = recv (lSocketHandle , MAKELONG(VARPTR(Sock(0). stReceivedData), ...
VARSEG(Sock(0). stReceivedData)), LEN(Sock(0).stReceivedData), 0)
Appendices
"Ignore this lower part of the document. It is badly written and barely helps you." - Marcade
Appendix A: 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
Appendix B: 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)