Mit QuickBASIC direkt aufs Internet

Einleitung und Motivation

Im Moment, während Sie diese Seite lesen, sind Sie mit ziemlicher Sicherheit gerade online mit Ihrem Provider verbunden. Doch haben Sie sich dabei, während die Statusdioden an Ihrem Modem so richtig schön am Blinken und Flackern sind, nicht auch schon überlegt, aus Ihrem selbstgeschriebenen BASIC-Programmierprojekt heraus direkt auf Ihr Modem oder lokales Netzwerk (LAN) zuzugreifen, um beispielsweise ein voll netzwerkfähiges Spiel wie Rasterbike für mehrere Spieler zu programmieren? Was die Profis bei Spieltitel wie Quake, Duke & Co. können, können Sie mit QuickBASIC genauso gut, wie Sie im folgenden soeben sehen werden.

In diesem Sinne soll dieser Artikel auch bei Ihnen das Wasser im Mund fliessen lassen :-).

Grundlagen zu TCP/IP

Die Grundbasis jeder Kommunikation übers Internet bilden sog. Sockets, also durchgeschaltete, virtuelle Verbindungen ähnlich wie beim Telefonnetz. Als Kommunikationsprotokoll kommt dabei das Ihnen bestens bekannte TCP/IP zum Einsatz. Verbindungen erfolgen immer zwischen zwei Hosts, wobei der eine Rechnerknoten die Rolle vom Server übernimmt und dort mit bind() auf einer beliebigen Portnummer passiv mit listen() auf eine eingehende Verbindung wartet. Sobald es »klingelt«, wird der Verbindungswunsch mit accept() entgegengenommen. Der andere Rechnerknoten, also der Client, nimmt mit connect() die Verbindung mit dem Server auf, wobei die IP-Adresse zusammen mit der Dienstportnummer der »Telefonnummer« entspricht. Steht die Verbindung (in UNIX/Linux sehen Sie dies mit netstat, auch Windows bietet Ihnen dieses Kommando an), so können die beiden Hosts einander gegenseitig nach Belieben Bytes zusenden. Aus Ihrer Sicht als BASIC-Programmierer funktioniert das Ganze nahezu identisch wie wenn Sie mit OPEN "COM2:9600,N,8,1" AS #1 eine RS-232-Verbindung aufgebaut haben. Wenn Sie also bereits eine Anwendung haben, die über RS-232 kommuniziert, ist es also keinen grossen Schritt mehr, die Anwendung voll Internet-tauglich zu erweitern. Analog dem Auflegen beim Telefon kann einer der Rechner mit close() die Verbindung schliessen, der Partner sieht dann den Trennwunsch als gesetztes EOF() ganz analog, wie Sie es von sequentiellen Dateien her kennen und führt dann ebenfalls close() aus.

Wie weit unterstützt QuickBASIC überhaupt Netzwerke?

Das Höchste der Gefüge, welches Ihnen QuickBASIC mit seinem internen Befehlssatz anbietet, ist das ACCESS-Schlüsselwort, um auf einem Netzwerklaufwerk exklusiven Zugriff auf Dateien zu ermöglichen. Ansonsten hat QuickBASIC leider :-( keine weiteren Befehle für die direkte Socket-Programmierung, weshalb die hier gezeigte Lösung vollständig auf DOS-Interrupts basiert.

Für die hier gezeigten Beispiele benötigen Sie wegen CALL INTERRUPT den QuickBASIC-Compiler! Falls Sie aber noch mit dem bei MS-DOS beiliegenden QBASIC.EXE oder gar GWBASIC.EXE arbeiten, müssen Sie eine mit CALL Adresse aufrufbare Maschinensprache-Routine für den DOS-Interrupt-Aufruf selber schreiben, wie im Artikel über DOS-Interrupts beschrieben.

Software-Architektur bei TCP/IP

Netzwerke wie das Internet sind nach dem bekannten ISO-OSI-7-Schichtenmodell aufgebaut. Auf der untersten Schicht befindet sich dabei Ihr Modem oder die Netzwerkkarte, auf dieser setzt dann der TCP/IP-Stack als softwaremässige Basis-Infrastruktur für das Bereitstellen von Sockets auf. Zwischen Hardware und TCP/IP-Stack fungiert dann noch der Netzwerkkarten- oder DFÜ-Treiber. Innerhalb Ihres BASIC-Programms kommunizieren Sie daher nie direkt mit dem Modem oder der Netzwerkkarte selber, sondern immer über die Anwendungsschnittstelle vom TCP/IP-Stack selber!

Gewählte Lösung

Als ich meinen ersten Netzwerk-Programmierversuch mit QuickBASIC unternahm, war das Hauptproblem einen geeigneten TCP/IP-Stack zu finden, denn das in jedem Windows 95/98/NT vorhandene WINSOCK.DLL arbeitet nur voll 32-Bit, und bisher ist mir aber noch keinen Weg bekannt, aus einer reinen Realmodus-DOS-Anwendung heraus auf 32-Bit-Routinen zugreifen zu können. Aus diesem Grund machte ich mich im Internet selber auf die Suche nach einem TCP/IP-Stack für reines DOS, welcher die Kriterien

erfüllte. Mit Hilfe von Ralf Browns Interrupt-Liste stiess ich dann auf DOSISODE, einer Programmierschnittstelle für Waterloo TCP/IP (WATTCP), welches treibermässig auf dem Pakettreiber basiert.

Installation von DOSISODE

Die hier beschriebene Anleitung ist auch für Benützer von Rasterbike gedacht.

Voraussetzungen

DOSISODE läuft unter reinem MS-DOS. Im Falle von Windows 3.1x sollten Sie Windows daher ganz beenden. Besitzer von Windows 95 und 98 sollten mit Start, Beenden..., Im MS-DOS-Modus neu starten oder beim Einschalten des PCs mit der Taste F8, Nur Eingabeaufforderung in den reinen DOS-Modus gehen. Als Kommunikationshardware eignet sich entweder ein lokales Netzwerk (LAN) mit Ethernet oder Token Ring oder ein ganz gewöhnlicher Dial-Up-Internetzugang mit PPP-Protokoll über ein Modem, wie ihn beispielsweise BlueWindow oder T-Online für Privatkunden anbieten.

Installation für ein Modem

Voraussetzungen

Voraussetzung ist ein zum Hayes-Befehlssatz kompatibles Modem. Ob Ihr Modem diese Anforderung erfüllt, können Sie mit folgendem QuickBASIC-Programm im DOS-Modus testen:

INPUT "Wo ist das Modem angeschlossen"; comx$
OPEN comx$ + ":9600,n,8,1,rs" FOR RANDOM AS 1
DO
  t$ = INKEY$
  IF t$ <> "" THEN
    PRINT #1, t$;
  END IF
  n% = LOC(1)
  IF n% > 0 THEN
    h$ = INPUT$(n%, 1)
    PRINT h$;
  END IF
LOOP UNTIL t$ = CHR$(27)
CLOSE 1

Dieses Programm stellt ein oberprimitives Terminalprogramm dar. Zu Beginn geben Sie die COMx-Schnittstelle entsprechend an, anschliessend sollten Sie AT gefolgt von Enter eingeben:

Wo ist das Modem angeschlossen? com2
AT
OK

Mit Esc beenden Sie die das Programm wieder. Antwortet Ihr Modem mit OK, so sind sämtliche Voraussetzungen optimal erfüllt :-). Falls es nicht auf Anhieb klappt, so können Sie ATE1 gefolgt von Enter eingeben, um den Echo-Modus zu aktivieren.

Ziemlich ungeeignete Modems sind diese seit neuestem erhältlichen sogenannten WinModems, bei welchen die gesamte Datenpumpe und übrige Geräteintelligenz zwecks Kosteneinsparung weggelassen wurde, sodass die CPU Ihres Rechners dann die gesamte Modulations- und Demodulationsarbeit verrichten muss. Sowieso sind solche Geräte mit den Prozessorgenerationen vor Pentium II völlig unbrauchbar! Ebenso scheiden auch sämtliche USB-Modems aus, ausser ihr Modemtreiber erzeugt eine virtuelle COM-Schnittstelle. Interne ISDN-Karten sind beinahe alle auch ungeeignet, dagegen arbeiten externe Terminaladapter auch wieder mit dem Hayes-Befehlssatz, so dass Sie hiermit ebenfalls eine PPP-Einwählverbindung hinbringen sollten.

Wenn Sie sich gerade erst ein Modem kaufen möchten, so achten Sie am besten gleich zu Beginn darauf, dass es wie hier beschrieben über AT-Befehle angesteuert werden kann. Am besten testen Sie nach Möglichkeit mit dem obigen BASIC-Programm Ihr Wunschgerät schon im Laden.

DOSPPP innerhalb von Windows nutzen

Die folgende Information konnte ich erst später zusammenstellen: Es ist grundsätzlich möglich, PPP für DOS auch innerhalb einer MS-DOS-Eingabeaufforderung zu nutzen, weil viele Modemtreiber eine virtuelle COM2 oder COM3-Schnittstelle erzeugen. Häufig funktioniert der obige Test über dieses virtuelle Gerät dann doch noch erfolgreich.

Im Falle eines WinModems mit virtueller COMx-Schnittstelle beenden Sie einfach das Windows-eigene DFÜ-Netzwerk (rechte Maustaste über dem Piktogramm rechts unten und dort Trennen wählen), damit das Modem für PPP unter DOS frei wird.

Nötige Downloads

Sämtliche Downloads und Dateivorbereitungsarbeiten können vom normalen Windows-Betrieb aus erledigt werden.

Falls Ihr Modem den Test erfolgreich bestanden hat, sollten Sie als nächstes aus dem Citilink FAQ die Datei DOSPPP05.ZIP herunterladen und im DOS-Bereich Ihrer Festplatte entpacken, beispielsweise nach C:\BASICPRG\DOSPPP.

Was Sie von Ihrem Provider (ISP) kennen müssen

Nun kommt etwas Konfigurationsarbeit, denn DOS ist ein Betriebssystem, welches noch keinerlei Konfigurationsassistenten kennt wie Windows, so dass Sie manuell im Texteditor EDIT.COM arbeiten müssen. Zunächst einmal sollten Sie Ihre Internet-Zugangsdaten im Detail kennen:

Diese Daten können Sie zum einen aus Ihrer Windows-Installation entnehmen, in dem Sie auf Start -> Einstellungen -> Systemsteuerung, Internetoptionen, Registerkarte Verbindungen gehen. Unter DFÜ-Einstellungen können Sie dann von Ihrem Providerkonto mit Einstellungen... alle Details abrufen. Ausserdem besitzen Sie vielfach auch ein Datenblatt Ihres Providers mit allen Angaben, vielfach finden Sie aber auch auf der Heimseite Ihres Providers im Supportbereich die entsprechenden Informationen, beispielsweise für Blue Window und T-Online.

Die obige Aufzählung dient für eine vollständige Konfiguration, welche sich auch für andere DOS-Internetanwendungen eignet. Falls Sie bestimmte Daten nicht kennen, so können Sie diese auch weglassen.

Detailkonfiguration

Die folgenden Angaben stützen sich auf den in der Anleitungsdatei SAMPLES.TXT enthaltenen Beispielen und sind von mir lediglich etwas optimiert worden. Die Konfiguration funktioniert jedoch recht ähnlich wie Linux, da PPPD.EXE eigentlich eine Portierung von pppd aus Linux darstellt. Falls Sie mit Linux arbeiten, werden Sie viel Vertrautes wieder finden. Nachfolgende Dateien sollten Sie also erstellen:

Datei C:\BASICPRG\DOSPPP\PPP-UP.BAT
@echo off

if _%1==_ goto DIALER

if %1==h goto HANGUP
if %1==H goto HANGUP
goto SYNTERR

:HANGUP
termin 0x60
echo Connection closed
goto END

:SYNTERR
echo Syntax error, call as %0 or %0 H
goto END

:DIALER
if exist ip-up.bat del ip-up.bat
epppdd debug kdebug 1 >>logger.out
if errorlevel goto CONNERR
if not exist ip-up.bat goto CONNERR
call ip-up.bat
if exist wattcp.cfg del wattcp.cfg
echo my_ip=%MYIP% >wattcp.cfg
echo gateway=%REMIP% >>wattcp.cfg
echo netmask=%NETMASK% >>wattcp.cfg
REM echo netmask=255.255.255.255 >>wattcp.cfg
type myisp.dat >>wattcp.cfg
REM echo mss=%PEERMRU% >>wattcp.cfg
set WATTCP.CFG=C:\BASICPRG\DOSPPP
echo Connection succesful
echo Falls das erste Mal gestartet, bitte noch
echo SOCKETS eingeben. Zum Auflegen %0 H eingeben
goto END

:CONNERR
echo Connection failed...

:END

Ganz wichtig ist hierbei die Verwendung von EPPPDD.EXE anstelle vom normalen PPPDD.EXE, weil DOSISODE nur sog. Class 1-Geräte (Ethernet-Netzwerkkarten) unterstützt.

Eine PPP-Verbindung gehört zur Class 6. EPPPDD.EXE simuliert im Grunde genommen das Verhalten einer Ethernet-Karte.

Datei C:\BASICPRG\DOSPPP\MYISP.DAT

Die Beispieldaten beziehen sich hier auf T-Online.

nameserver=194.25.2.129
nameserver=194.25.0.125
domainslist="btx.dtag.de"
smtphost=mailto.t-online.de
nntphost=news.btx.dtag.de
mailaddr=ihre.mail.adresse@t-online.de

Daten, die Sie von Ihrem Provider nicht ausfindig machen konnten, können Sie auch weglassen, da DOSISODE schon mit sehr wenig Angaben funktioniert.

Datei C:\BASICPRG\DOSPPP\PPPDRC.CFG
COM2
115200
irq 3
crtscts
local
asyncmap 0
connect "chat -v -r ausgabe.txt -f chat.txt"
user ihrbenutzername
passwd ihrpasswort

Falls Ihr Modem an COM1 angeschlossen ist, benötigen Sie auch häufig IRQ 4. Schauen Sie im Gerätemanager von Windows oder mit MSD.EXE nach, wie die Interrupts auf Ihrem PC belegt sind! Vielfach können (und müssen Sie sogar!) die irq-Zeile weglassen, dies gilt vornehmlich für virtuelle COM-Schnittstellen von WinModems.

Datei C:\BASICPRG\DOSPPP\CHAT.TXT
TIMEOUT 94
ABORT "NO CARRIER"
ABORT BUSY
ABORT "NO DIALTONE"
ABORT ERROR
"" \r+++ATZ
"" ~\r~AT\sS7=45\sS0=0\sL1\sV1\sX4\s&c1\sE1\sQ0
OK ATDT0191011
CONNECT \c

Auch diese Angaben habe ich aus der T-Online-Heimseite entnommen.

Verbindung testen

Mit der Erstellung ist die Konfiguration soweit abgeschlossen, so dass Sie jetzt Ihre Windows-Sitzung beenden und im DOS-Modus neustarten sollten und Ihre Verbindung testen können:

C:\WINDOWS>cd \basicprg\dosppp

C:\BASICPRG\DOSPPP>ppp-up
Resident size: 75616
coreleft: 4720
COM2 2F8 3: [NS16550A] [cts flow control] 115200 bps
 MC: int 1  DTR On  RTS On  CTS On  DSR On  RI Off  CD On
 RX: int 0  chars 0  hw over 0  hw hi 0  fifo TO 0  sw over 0  sw hi 0
 TX: int 0  chars 0  THRE TO 0
main: connect ppp0 <--> COM2.
sifdown: IP interface inactive.
local  IP address 212.185.216.211
remote IP address 212.185.216.1
sifup: IP interface active.
Installed packet driver handler at vector 0x60.
Connection succesful
Falls das erste Mal gestartet, bitte noch
SOCKETS eingeben. Zum Auflegen ppp-up H eingeben
C:\BASICPRG\DOSPPP>_

Wenn alles wie in dieser Ausgabe geklappt hat, so sehen Sie Ihre dynamisch zugewiesene IP-Adresse sowie die Meldung Connection succesful.

Falls Fehler erscheinen, so findet man in der Protokolldatei C:\BASICPRG\DOSPPP\LOGGER.OUT die genaue Fehlerursache wie beispielsweise ein falsches Passwort, falsche Einwählnummer usw. Diese Datei können Sie mit type oder more betrachten, dabei sind jeweils nur die letzten paar Zeilen interessant. Da MS-DOS keinen tail-Befehl wie UNIX/Linux kennt, mit welchem man diese paar letzten Zeilen herausextrahieren kann, können Sie vor einem Einwählversuch LOGGER.OUT jeweils löschen, um unnötig langes Blättern zu vermeiden.

Verbindung trennen

Für das Auflegen des Modems genügt ppp-up h:

C:\BASICPRG\DOSPPP>ppp-up h
Packet driver terminator version 11.1 copyright 1988-1992, Russell Nelson.
This program is free software; see the file COPYING for details.
NO WARRANTY; see the file COPYING for details.

termin: terminate completedConnection closed
C:\BASICPRG\DOSPPP>_

Damit können Sie das Kapitel LAN überspringen und direkt zur Installation DOSISODE übergehen.

Installation für ein lokales Netzwerk (LAN)

Voraussetzung

Voraussetzung ist eine manuelle und feste IP-Adresse, da DOSISODE kein DHCP unterstützt. Am besten fragen Sie im Falle einer Firma Ihren Netzwerkadministrator oder verwenden ganz einfach dieselben Parameter wie in Windows oder Linux, falls Ihr PC mit einem Boot-Manager mehrere Betriebssysteme verwaltet.

Beschaffung und Installation eines Pakettreibers

Hierfür benötigen Sie den sog. Packet Driver zu Ihrer Netzwerkkarte. Bei einer hochwertigen Marken-Netzwerkkarte finden Sie diesen auf den beiliegenden Treiberdisketten, so beispielsweise bei 3Com im Verzeichnis A:\PKTDVR. Diesen können Sie direkt in die Datei AUTOEXEC.BAT eintragen:

REM Beispiel für 3Com Etherlink III PCI 3C900
LH C:\3C900\PKTDVR\3C90XPD

REM Beispiel für 3Com Etherlink 3C590
LH C:\3C590\PKTDVR\3C59XPD

Achten Sie dabei darauf, den Standard-Interrupt 0x60 zu verwenden. Falls bei Ihrer Ethernet-Netzwerkkarte kein solcher Treiber beiliegt, schauen Sie bitte auf der Heimseite des Herstellers unter dem technischen Supportbereich im Treiber-Download-Bereich nach, und zwar für das Betriebssystem DOS. Befolgen Sie dann die Anweisungen vom Hersteller, was die nötigen Anpassungen innerhalb der CONFIG.SYS und AUTOEXEC.BAT betrifft.

Falls Sie nicht fündig werden, sollten Sie den Downloadbereich von Crynwr aufsuchen, denn Crynwr bietet Ihnen zu fast jeder Netzwerkkarte den passenden Pakettreiber an, und dies sogar als kostenlose GNU-Programme :-) Für eine alte 3Com 3C503-Ethernetkarte in einem 386er in meinem privaten LAN brauchte ich beispielsweise pktd11.zip und zwar die Datei 3C503.COM mit

REM Beispiel für 3Com Etherlink 3C503
LH C:\3C503N\PKTDVR\3C503 0x60 5 0x310

in der AUTOEXEC.BAT zu laden. Am besten verwenden Sie in Ihrem Fall das Online-Inhaltsverzeichnis von Crynwr, um die für Ihre Netzwerkkarte passende Datei zu finden.

Detailkonfiguration

Zunächst einmal erstellen Sie ein Verzeichnis C:\DOSISODE und erstellen dort eine Datei C:\DOSISODE\WATTCP.CFG mit folgendem Inhalt (eigene Daten vom Netzwerk einsetzen!):

my_ip = 192.168.0.4
gateway = 192.168.0.2
netmask = 255.255.255.240
nameserver = 192.168.0.2
nameserver = 192.168.0.3
sockdelay = 60
mss=576
domainslist="hofenlokal.ch"
mailserver=mail.hofen.ch
nntphost=news.swissworld.com
mailaddr=info@dreael.ch

Das Beispiel stammt aus meinem Privat-LAN. Damit DOSISODE und Waterloo TCP/IP generell die Konfiguration findet, sollten Sie noch die Zeile

SET WATTCP.CFG=C:\DOSISODE

in Ihre AUTOEXEC.BAT einfügen. Damit ist die Pakettreiber-Installation abgeschlossen. Im Falle eines Modems wird die Datei WATTCP.CFG von PPP-UP.BAT wegen der dynamisch zugewiesenen IP-Adresse beim Einwählen dynamisch erzeugt.

Modem und LAN: Eigentlicher TCP/IP-Stack installieren

Zu diesem Zweck laden Sie aus Sunsite die Datei dosisode-runtime.zip (Alternative 1 Alternative 2) herunter. Von dieser 2 MB grossen Datei wird übrigens nur die Datei SOCKETS.EXE benötigt, welche Sie im Falle eines Modems nach C:\BASICPRG\DOSPPP kopieren. Im Falle eines LAN kopieren Sie SOCKETS.EXE nach C:\DOSISODE und ergänzen ausserdem die Datei AUTOEXEC.BAT mit:

REM TCP/IP-Stack
LH C:\DOSISODE\SOCKETS

Modembenützer starten dieses Programm jeweils manuell mit sockets, nachdem sie sich eingewählt haben:

C:\BASICPRG\DOSPPP>ppp-up
Resident size: 75616
..
.. (Ausgaben weggelassen)
..
Connection succesful
Falls das erste Mal gestartet, bitte noch
SOCKETS eingeben. Zum Auflegen ppp-up H eingeben
C:\BASICPRG\DOSPPP>sockets

C:\BASICPRG\DOSPPP>_

Der grosse Augenblick: Die erste Socketverbindung aus QuickBASIC heraus

Zu diesem Zweck laden Sie bitte mein Beispielprogramm TCPIP_D.BAS herunter und kopieren es beispielsweise nach C:\BASICPRG. Gehen Sie nun ins DOS und aktivieren Sie die Modemverbindung zu Ihrem Provider. Im Falle eines LAN starten Sie einfach in der entsprechenden DOS-Konfiguration, welche Ihnen SOCKETS.EXE ordnungsgemäss einrichtet.

Starten Sie anschliessend QB.EXE mit /l qb und öffnen Sie dort das Test- und Demonstrationsprogramm TCPIP_D.BAS. Dieses können Sie normal starten. Als Beispiel rufen wir vom Web-Server des Magazins »Spiegel« die Haupt-Webseite ab:

IP-Adresse des Hostes (z.B. 192.168.0.2)? 194.163.254.145
Portnummer (z.B. 23=UNIX-Telnet-Sitzung, 80=WWW-Server)? 80
Socket erzeugt
Verbunden
Bereit für Eingaben

Nun geben Sie

GET / HTTP/1.1Return*
Host: www.spiegel.deReturn*

*Da viele Webserver unter UNIX laufen, kann es nötig sein, noch zusätzlich Strg+J einzugeben, um ein CHR$(10) zu senden.

Es ist übrigens normal, wenn von der Eingabe nichts erscheint, da ein Webserver kein Terminal-Echo erzeugt. Wenn Sie alles richtig gemacht haben, so sollten Sie als Ausgabe direkt den HTML-Quellcode bekommen:

HTTP/1.1 200 OK
Date: Fri, 19 May 2000 21:04:23 GMT
Server: Apache/1.3.1 (Unix) mod_oas/4.62 PHP/3.0.7
Cache-Control: max-age=7200
Expires: Fri, 19 May 2000 23:04:23 GMT
Transfer-Encoding: chunked
Content-Type: text/html

<!-- Vignette StoryServer 4 Fri May 19 22:55:04 2000 -->
<HTML>
<HEAD>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html">
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="EXPIRES" content="0">
<meta http-equiv="CACHE-CONTROL" content="PRIVATE">
<meta name="DESCRIPTION" content="Home - SPIEGEL ONLINE">
..
.. (weggelassen)
..
href="http://www.manager-magazin.de/" target=_blank>manager&nbsp;magazin</a>
| <a href="/gruppe/" target="_top">SPIEGEL-Gruppe</a>
| <a href="http://media.spiegel.de/" target=_blank>SPIEGEL&nbsp;Media</a>]
</font><br><br></td></tr></table>
</BODY>
</HTML>
Verbindung vom Partner geschlossen
Telnet-Sitzung beendet.

Mit diesem Programm könnten Sie bereits einen ganz einfachen Web-Browser in QuickBASIC selber implementieren! :-) Nach Beenden von QuickBASIC vergessen Sie bitte nicht, die Verbindung mit ppp-up h wieder zu trennen.

Damit ist das Thema Netzwerk-Installation soweit abgeschlossen, so dass Sie sich eine Runde Rasterbike gönnen können bzw. dort mit der Installation weiterfahren können.

Programmierung von DOSISODE

DOSISODE setzt seine Schnittstelle auf den DOS-Interrupt 17h, hier finden Sie die zugehörige Dokumentation des API. Falls Sie sich etwas mit ANSI-C auskennen, empfehle ich Ihnen sehr, einen Blick in den Quellcode von SOCKETS.EXE zu werfen, und zwar in die Datei SOCKETS.C, denn aus diesem Code erfahren Sie viele wichtige Details.

Kommentare zum Beispielprogramm

Für Ihre eigenen QuickBASIC-Projekte empfehle ich Ihnen die Verwendung von TCPIP_D.BAS als Vorlage zu verwenden. Dieses Beispiel sollte aufgrund seines verhältnismässig einfachen Aufbaus weitgehend selbsterklärend sein. Ansonsten gehe ich hier kurz durch das ganze Programm hindurch:

' Demonstrationsprogramm für TCP/IP unter DOS aus QuickBASIC heraus:
' Oberprimitiver Telnet-Client
' (c) 2000 by Andreas Meile, CH-8242 Hofen SH
' e-Mail: info@dreael.ch  WWW: http://www.hofen.ch/~andreas/

' $INCLUDE: 'qb.bi'

DIM dosIntEin AS RegType, dosIntAus AS RegType

Beachten Sie lediglich den Einsatz von QB.QLB wegen den CALL INTERRUPT-Aufrufen, in dem Sie QuickBASIC mit /l qb aufrufen.

' Testen, ob Waterloo TCP mit DOSISODE-Interface geladen wurde
dosIntEin.ax = &HE00  ' SI_CHECKLOAD
CALL INTERRUPT(&H17, dosIntEin, dosIntAus)
IF dosIntAus.cx <> &H1234 THEN
  PRINT "Sie müssen noch SOCKETS.EXE zusammen mit dem Packet Driver"
  PRINT "Ihrer Netzwerkkarte laden"
  END
END IF

Mit diesem ersten Aufruf testen wir, ob die TCP/IP-Software überhaupt ordnungsgemäss installiert und geladen wurde. Dabei ist zu sagen, dass die Funktionsnummer immer in AX angegeben wird.

DO
  INPUT "IP-Adresse des Hostes (z.B. 192.168.0.2)"; ipAdr$
  ' R und S sind eigentlich nur "Notnägel", um im Falle eines
  ' unordnungsgemässen Abbruchs den Socket schliessen zu können
  ipBin$ = ""
  SELECT CASE ipAdr$
  CASE "s", "S"
    GOSUB Schliessen
    PRINT "SI_CLOSE ausgeführt"
  CASE "r", "R"
    GOSUB Beenden
    PRINT "SI_SHUTDOWN ausgeführt"
  CASE ELSE
    DO
      p% = INSTR(ipAdr$, ".")
    IF p% = 0 THEN EXIT DO
      ipBin$ = ipBin$ + CHR$(VAL(LEFT$(ipAdr$, p% - 1)))
      ipAdr$ = MID$(ipAdr$, p% + 1)
    LOOP
    ipBin$ = ipBin$ + CHR$(VAL(ipAdr$))
  END SELECT
LOOP UNTIL LEN(ipBin$) = 4
INPUT "Portnummer (z.B. 23=UNIX-Telnet-Sitzung, 80=WWW-Server)"; portNr%

Hier erfolgt die Benutzereingabe des Zielhosts in Form der IP-Adresse. Unter dem Punkt Einschränkungen von DOSISODE werden Sie übrigens noch erfahren, warum derzeit noch keine Hostnamen wie www.spiegel.de möglich sind. Ansonsten wird hier die Adresse bereits DOSISODE-gerecht in ipBin$ vorbereitet.

' Initialisieren
dosIntEin.ax = &H300  ' SI_SOCKET
dosIntEin.cx = 1      ' Typ: 1=Stream (TCP), 2=Dgram (UDP)
dosIntEin.dx = 1      ' Socket-ID = 1
CALL INTERRUPT(&H17, dosIntEin, dosIntAus)
IF dosIntAus.ax <> 0 THEN
  PRINT "Fehler beim Initialisieren"
  PRINT "Code:"; dosIntEin.cx
  END
END IF
PufSeg% = dosIntAus.cx
PufOffs% = dosIntAus.dx
DEF SEG = PufSeg%
PRINT "Socket erzeugt"

Ab hier wird unser TCP/IP-Socket aufgebaut; dabei folgt DOSISODE dem auf jedem UNIX und Linux üblichen Berkeley-Standard (BSD). Analog der Dateinummer von BASIC, um bei PRINT# und INPUT$() zwischen mehreren geöffneten Dateien unterscheiden zu können, übergeben Sie auch hier im DX-Register eine solche Nummer. Gemäss Quellcode SOCKETS.C können Sie maximal 6 Sockets gleichzeitig geöffnet haben.

Der Parameterdaten-Austausch erfolgt übrigens über einen von DOSISODE reservierten Speicherbereich, von welchem wir die Segment- und Offsetadresse zurückgeliefert bekommen. Auf diesen Speicherbereich werden wir in QuickBASIC über PEEK() und POKE zugreifen.

TCP/IP unterscheidet übrigens noch zwischen Stream- (TCP) und Datagramm-Sockets (UDP). Bei Stream-Sockets gewährleistet Ihnen das Netzwerk vollständige Daten in der korrekten Reihenfolge, wofür aber auch mehr Overhead in Form von Quittungen nötig ist. UDP arbeitet daher schneller und schont die Bandbreite Ihres Modems, dafür können die Daten unvollständig und in irgend einer Reihenfolge am Zielhost angelangen. Bei UDP ist es also so, wie wenn Sie eine Brieftaubenschar wegschicken (Eine Taube entspricht einem Datagramm), bei welcher jede ihre eigene Route wählt und dabei die eine oder andere Taube einem Raubtier zum Opfer fällt. Bei TCP passiert dieser Effekt genauso, nur dass Sie dort bei beiden Poststellen Diener angestellt haben, welche die Lieferung auf Vollständigkeit hin überprüfen und bei Bedarf fehlende Nachrichten nochmals anfordern (Taube mit Dienstmeldung heimwärts schicken), so dass der Empfänger die Botschaft erst bekommt, sobald sie vollständig ist.

' Socket aufbauen
dosIntEin.ax = &H500   ' SI_CONNECT
dosIntEin.dx = 1
rsockaddr$ = RIGHT$(MKI$(portNr%), 1) + LEFT$(MKI$(portNr%), 1) + ipBin$
' Daten übertragen: Nur ab dos_buffer+2 ändern
FOR i% = 1 TO LEN(rsockaddr$)
  POKE PufOffs% + i% + 1, ASC(MID$(rsockaddr$, i%, 1))
NEXT i%
CALL INTERRUPT(&H17, dosIntEin, dosIntAus)
IF dosIntAus.ax <> 0 THEN
  PRINT "Fehler beim Verbindungsaufbau"
  PRINT "Code: "; dosIntAus.cx
  GOSUB Beenden
  END
END IF
PRINT "Verbunden"

In diesem Programmteil nimmt Ihr PC den heiss ersehnten Kontakt mit der Aussenwelt auf. Dazu initialisieren wir die rsockaddr-Struktur mit der IP-Adresse von Server-Zielhost und geben ausserdem die Portnummer ebenfalls an. Das Ganze erfolgt mittels POKE in diesen Speicherpuffer. Wenn der Partner die Verbindung akzeptiert hat (dieser ruft bei sich also SI_ACCEPT auf), dann ist der Aufruf erfolgreich, was Sie mit AX=0 erkennen können. Ansonsten erhalten Sie wie überall den Fehler in CX. Ein typischer Fehler ist CX=61 (ECONNREFUSED), was dem bekannten Connection refused, also vom Server abgelehnten Verbindungsaufbauwunsch entspricht.

' Zuerst noch sog. Non-Blocking IO aktivieren, damit Tastatur und Netzwerk-
' Socket zusammen abgefragt werden können

dosIntEin.ax = &HB00  ' SI_IOCTL
dosIntEin.dx = 1      ' Socket-ID
CALL INTERRUPT(&H17, dosIntEin, dosIntAus)
PRINT "Bereit für Eingaben"

Dieser Teil ist im Zusammenhang mit Ereignisabfragen noch ganz wichtig: Weil unser Telnet-Clientprogramm imstande sein muss, die Tastatur und das Netzwerk gleichzeitig abfragen zu können, müssen wir verhindern können, dass eine SI_RECVFROM-Operation das ganze System blockiert, wenn gerade kein Byte empfangen wurde.

Drinbleib% = -1
WHILE Drinbleib%

Ab hier erfolgt der interaktive Telnet-Terminalbetrieb, wofür wir eine grosse Schleife benötigen.

  ' Netzwerk-Verbindung prüfen
  dosIntEin.ax = &H800  ' SI_RECVFROM
  dosIntEin.cx = 200 ' maximal 200 Zeichen entgegennehmen
  dosIntEin.dx = 1   ' Socket-ID
  CALL INTERRUPT(&H17, dosIntEin, dosIntAus)
  IF dosIntAus.ax = -1 THEN
    IF dosIntAus.cx = 35 THEN
      ' EWOULDBLOCK ignorieren (warten!)
    ELSEIF dosIntAus.cx = 6 THEN
      ' EBADF als vom Partner ausgelöstes close() behandeln
      PRINT "Verbindung vom Partner geschlossen"
      Drinbleib% = 0
    ELSE
      PRINT "Fehler beim Lesen"
      PRINT "Code:"; dosIntAus.cx
      GOSUB Schliessen
      GOSUB Beenden
      END
    END IF
  ELSE
    FOR i% = 0 TO dosIntAus.ax - 1
      z$ = CHR$(PEEK(PufOffs% + 16 + i%))
      ' COLOR 5
      ' PRINT ASC(z$);
      ' COLOR 7
      PRINT z$;
    NEXT i%
  END IF

Wie im Artikel über Ereignisabfragen beschrieben, erfolgt hier zunächst einmal die Abfrage des Netzwerks, ob etwas empfangen wurde. Den Leseaufruf können Sie mit INPUT$() vergleichen mit einem entscheidenden Unterschied, dass die Anzahl der zu lesenden Zeichen nur ein Wunsch darstellt, so dass Sie immer auch mit weniger Zeichen rechnen müssen. Daher müssen Sie den Rückgabewert immer beachten. Dieses Verhalten ist übrigens bei jedem UNIX und Linux der Fall, sogar bei MS-DOS arbeitet ein normaler read()-Systemaufruf nach diesem Prinzip. Warum Microsoft bei INPUT$() stur die angegebene Anzahl Zeichen erwartet, so dass gerne Einlesen nach Dateiende-Fehler zustande kommen, ist auch mir ein Rätsel, denn meiner Meinung nach wäre dasselbe Verhalten wie UNIX bei INPUT$() wesentlich zweckmässiger. Falls übrigens keine Zeichen vorliegen, erhalten Sie eine Fehlermeldung EWOULDBLOCK, welche wir aber mit voller Absicht ignorieren! Die Daten werden dabei mit PEEK() aus dem Pufferbereich geholt, dabei sollten Sie beachten, dass diese erst bei PufOffs%+16 beginnen, da sich in den ersten 16 Bytes immer noch die rsockaddr-Struktur befindet.

  ' Tastatur prüfen
  t$ = INKEY$
  IF t$ = CHR$(29) THEN
    Drinbleib% = 0
  ELSEIF t$ <> "" THEN
    WHILE t$ <> ""
      dosIntEin.ax = &H900   ' SI_SENDTO
      dosIntEin.cx = LEN(t$) ' Anzahl Zeichen
      dosIntEin.dx = 1 ' Socket-ID
      FOR i% = 0 TO LEN(t$) - 1
        POKE PufOffs% + 16 + i%, ASC(MID$(t$, i% + 1, 1))
      NEXT i%
      CALL INTERRUPT(&H17, dosIntEin, dosIntAus)
      IF dosIntAus.ax < 0 THEN
        PRINT "Fehler beim Schreiben"
        PRINT "Code"; dosIntAus.cx
        GOSUB Schliessen
        GOSUB Beenden
        END
      END IF
      ' Verbleibende, noch nicht versandte Bytes
      t$ = MID$(t$, dosIntAus.ax + 1)
    WEND
  END IF
WEND

Bitte beachten Sie auch hier das UNIX/Linux-mässige Verhalten: Die Zahl der zu sendenden Bytes stellt auch hier nur ein Wunsch dar, so dass Sie auch wiederum selber nachsehen sollten, wie viele Bytes effektiv versendet wurden. In unserem Falle wiederholen wir einfach bei Bedarf die Schreiboperation solange, bis alles erfolgreich versendet werden konnte.

Als Benutzer können Sie auch hier die Telnet-Sitzung wie bei jedem Telnet-Client üblich mit Strg+] beenden.

' Beenden
GOSUB Schliessen
GOSUB Beenden
PRINT "Telnet-Sitzung beendet."
END

Analog normalen Dateien müssen Sie auch am Schluss jeweils korrekt und sauber schliessen.

Schliessen:
' Zum Schluss Socket schliessen
dosIntEin.ax = &HC00  ' SI_CLOSE
dosIntEin.dx = 1      ' Socket-ID
CALL INTERRUPT(&H17, dosIntEin, dosIntAus)
IF dosIntAus.ax <> 0 THEN
  PRINT "Fehler beim Schliessen"
  PRINT "Code:"; dosIntAus.cx
  GOSUB Beenden
  END
END IF
RETURN

Hier wird der Socket wiederum geschlossen, aber nur so, dass Sie ohne weiteres wieder neue Sockets erzeugen können.

Beenden:
' Netzwerk-Anwendung ganz beenden
dosIntEin.ax = &HD00  ' SI_SHUTDOWN
CALL INTERRUPT(&H17, dosIntEin, dosIntAus)
RETURN

Und hier wird das gesamte DOSISODE-Subsystem korrekt heruntergefahren. Dies sollten Sie immer am Schluss auch tun, und zwar dann, wenn alle Sockets mit SI_CLOSE geschlossen wurden.

Was könnte verbessert werden?

TCP/IP mit DOS ist zugegebenermassen recht umständlich im Zeitalter von Windows, wo Ihnen der Internet-Verbindungsassistent die gesamte Konfiguration erledigt. Daher zeige ich Ihnen noch hier die Grenzen auf und möchte Sie bei dieser Gelegenheit auch gleichzeitig aufrufen, konkrete Verbesserungsvorschläge zukommen lassen.

Wichtige Einschränkungen

DOSISODE ist zugegebenermassen schon recht alt, und wie mir der Autor in einer e-Mail mitgeteilt hat, wird es auch nicht mehr von ihm weitergepflegt. Falls Sie aber gute C-Programmierkenntnisse besitzen und Waterloo TCP ebenfalls gut kennen, könnten Sie diese Schnittstelle beliebig erweitern. Insbesondere fehlen in der aktuellen Version:

Von diesen Einschränkungen einmal abgesehen können Sie trotzdem schon recht tolle Projekte damit machen :-).

Aufruf an Sie: Verwendung von WINSOCK.DLL

In einer Newsgroup-Anfrage wurde ich auf INT 2Fh verwiesen, wo es anscheinend möglich sein sollte, direkt auf Windows-API-Funktionen zugreifen zu können. Falls es also Ihnen damit gelingen sollte, auf beliebige 32-Bit-Funktionen aus Windows-.DLL-Dateien zuzugreifen, wäre ich Ihnen sehr dankbar, wenn Sie sich sofort bei mir melden würden. Denn damit könnte TCP/IP mit QuickBASIC auch wirklich »volkstauglich« gemacht werden und Probleme wie beispielsweise zuerst ins DOS hinausgehen, wo Sie dann den Chat mit demjenigen Kollegen, mit welchem Sie Rasterbike spielen möchten, beenden müssen, wären dann hinfällig :-).


Wieder zurück zur Übersicht


© 2000 by Andreas Meile