TCP/IP in qbasic


BACK

Requirements

To use TCP/IP in qbasic, you will need the following:

Installation

First, download a packet driver for your network card. You can click HERE to download a free collection of common packet drivers. Alternatively, I believe it is possible to use PPPD to connect to the internet via modem, although I have not tested it with qbasic. If you did try to use it, you would probably have to use the EPPPD.EXE driver. Next, you need to know the IO address and IRQ for your network card. You should only try loading packet drivers from real mode ms-dos, so if you are in windows, shut down to dos. This will not work properly in a dos box (you will get weird errors). You can load a packet driver for an NE2000 compatible card, using 0x300 on IRQ10, by changing to the directory containing the packet drivers and typing:
ne2000 0x7e 10 0x300
A little warning: if you do enter the wrong IRQ for the card, the driver will still load without error. However, the card will be able to transmit data, but not receive (yes, I'm talking from experience, I spent hours trying to figure out why the damn thing wouldn't work properly from QB, only to find I'd entered the wrong IRQ here).

Next, you need to set up the TCP/IP driver. If you decided to use PPPD, you can probably just type 'ntcpdrv' and it will configure itself (I haven't tested this…).
If you have installed a packet driver for your card, you will need to specify the IP, netmask, and gateway for your card. I have found that setting the netmask to '255.255.255.0' and the gateway to '255.255.255.255' seems to work best. If you want to use the IP 198.0.0.1, for example, then change to the directory containing the driver and type the following:
ntcpdrv -ip=198.0.0.1 -netmask=255.255.255.0 -Gateway=255.255.255.255
Once you have found a working packet driver, and got the TCP/IP driver to load, I suggest you put the two commands into a batch file.

Next, download and unzip my qbasic library into your qbasic directory. The zip file contains four files: To load qbasic & the library, type:
qb /l qbtcp.qlb
All programs written in qbasic using the library must include the header file 'qbtcp.bi'. This can be done by adding the line:
REM $INCLUDE: 'QBTCP.BI'
to the top of the qbasic program. Now you are ready to start wringing programs in QB that use TCP/IP. I will now describe the five functions used in TCP/IP programming in qbasic, along with examples.

Functions

TCPINIT (func AS INTEGER)

This can either be called with a value of 1 or 0. Call TCPINIT(1) at the start of the program to start automatic processing of packets. It is very important that you also call TCPINIT(0) at the end of the program. Warning: Calling TCPINIT more that once can crash the computer. It should only be called once with the value of 1 at the start of the program, and once at the end. Anything else can result in a crash. If you are interested, this routine hooks the timer interrupt (IRQ0) to call the TCP/IP drivers 'doio' function.

Socket% (ipaddress AS STRING, port AS INTEGER)

This function takes the IP address and port you want to connect to, and returns a handle for that connection. IP address must be in the form xxx.xxx.xxx.xxx. The handle returned must be saved, as it is needed for all the other functions. If, for whatever reason, a connection cannot be established, it will return -1. E.g.:
DIM handle AS INTEGER

PRINT "Enter IP address"
INPUT ipaddr$
PRINT "Enter port"
INPUT p

handle = socket(ipaddr$, p)
IF handle >= 0 THEN PRINT "Connected to " + ipaddr$ ELSE PRINT "error"

Send% (buffer AS STRING, handle AS INTEGER)

This function is used to send data to a server once connected. It will return the number of bytes actually sent. A return value of -1 indicates an error. For example, the following qbasic code will send the line 'USER A101 blah blah :blah' to the connected server (in case your wondering, that the first line you would need to send when you connect to an IRC server):
Buffer$ = "USER A101 blah blah :blah" + CHR$(10) + CHR$(13)
Retval = send(Buffer$, handle)
IF Retval < 0 THEN PRINT "Error!" ELSE PRINT "Sent"
The "CHR$(10) + CHR$(13)" is used to add a CR+LF to the data. This is needed for most protocols, and is roughly equivalent to pushing ENTER in a telnet window.

Recv% (buffer AS STRING, handle AS INTEGER)

This is probably the most difficult function in the library to use. When executed, it will read data up to the first CR+LF and store it in 'buffer'. It will either return -1 indicating an error (such as not connected), 0 if no data was received, or if greater than 0 the number of bytes received. For example, the following code will display any incoming data, until 'q' if pushed:
retval = 0
DO WHILE (retval >= 0) AND (INKEY$ <> "q")
retval = recv(buffer$, handle)
IF retval > 0 THEN PRINT LEFT$(buffer$, retval)
LOOP
Note: Pushing 'Q' will take up to 5 seconds to exit the loop. The 'LEFT$(buffer$, retval)' is used to remove the extra blank data stored in Buffer$, as retval contains the number of bytes of data stored in Buffer$. If you connect to a server running the chargen service, on port 19, this will display page after page of test data.

TCPClose% (handle AS INTEGER)

This is used to close the connection. At the moment, I am having difficulty with this function, so it's probably best to ignore its return value (it should return -1 on error, 0 for success), and call it twice to be sure the connection has actually been closed. E.g.:
retval = TCPClose (handle)
retval = TCPClose (handle)

Example program

As I only got TCP/IP working in qbasic a few days ago (to day being 06/4/03), I don't really have any useful programs using TCP/IP written in qbasic yet. However, the following program should connect to an IRC server, and list all available channels:
REM $INCLUDE: 'qbtcp.bi'

Tcpinit (1)
DIM handle AS INTEGER
CLS

PRINT "Enter IP Address of IRC server to connect to:"
INPUT ircIP$
PRINT "Connecting to " + ircIP$ + " ..."
handle = socket(ircIP$, 6667)   '6667 = usual IRC port
IF handle < 0 THEN PRINT "Error connecting!": Tcpinit (0): END


buffer$ = "user a101 blah blah :blah" + CHR$(10) + CHR$(13)
Retval = send(buffer$, handle)
'The return value from send really should be checked here...

buffer$ = "nick Anon101" + CHR$(10) + CHR$(13)
Retval = send(buffer$, handle)
'...and here

buffer$ = "list" + CHR$(10) + CHR$(13)
Retval = send(buffer$, handle)
'Ditto


PRINT "Starting receive, push 'q' to quit"
DO WHILE (Retval >= 0) AND (INKEY$ <> "q")
Retval = recv(buffer$, handle)
IF Retval > 0 THEN PRINT ">" + LEFT$(buffer$, Retval)
LOOP

Retval = TCPClose(handle)
Retval = closesocket(handle)
Tcpinit (0)

Limitations/to-do

At the moment, this QB library cannot resolve DNS names, meaning that the only way you can connect to a server is if you know its IP address.
It also has no support for It is also unsuitable at the moment for binary data transfers, because of the way it handles CR+LF's.
If you are wondering something like "Does this library support feature xxx" the answer is almost certainly no.
Finally, if you do get this to work, or have any problems, please tell me! (Either on the forum or by email).

BACK