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.
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
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
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 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
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
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)
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%
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)
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)
wVersion AS INTEGER
wHighVersion AS
INTEGER
szDescription AS
STRING * 257
szSystemStatus AS
STRING * 129
iMaxSockets AS
INTEGER
lpVendorInfo AS LONG
sinFamily
AS INTEGER
sinPort
AS INTEGER
sinAddr
AS inAddr
sinZero
AS STRING * 8
This function will close and terminate a socket for usage.
The parameter given is the socket handle.
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.
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]
This will return the address thing? Parameter is the
HostHandle retrieved with gethostbyname.
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.
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.
This function will
create a word in the selected integer. I don’t know exactly what it really
does.
Returns:
[unknown]
This function will retrieve the error code of the last error
that occurred.
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.
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
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
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.
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]
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)