Voraussetzungen

Die Voraussetzungen sind knapp umrissen: ein Betriebssystem, das Sockets unterstützt, eine Programmiersprache, die Sockets untertstützt (im weitesten Sinne also direkt Syscalls anpacken kann), sowie eine Menge Geduld am Anfang, wie beim Erarbeiten eines neuen Teilbereichs der Programmierung üblich ist.

Sockets sind nicht auf Netzwerk-Verbindungen beschränkt, sondern stellen eine allgemeine Schnittstelle für Interprozeß-Kommunikation dar. Auch für die Netzwerk-Programmierung ist man nicht zwangsläufig an TCP/IP gebunden, sondern kann auch NetBIOS oder IPX/SPX verwenden. Auf alle diese Feinheiten wird in diesem Tutorial nicht eingegangen, sondern es werden nur Sockets der Protokoll-Familie PF_INET berücksichtigt, und als Transportprotokolle wird TCP und UDP eingesetzt.

Eigentlich unterstützen alle modernen Betriebssysteme Sockets. Glücklicherweise hat sich die Socket-API von BSD so schön etabliert, daß die meisten dazu kompatibel sind. Mit Namen sind das die modernen UNIX-Systeme (FreeBSD, NetBSD, OpenBSD, Solaris, ..., Linux), die 32-Bit-Windowse (Windows 9x, ME, NT, 2000, XP) und andere. DOS und Windows 3.x kann man mit Socket-Implementierungen von Drittanbietern aufbohren, zu anderen Systemen kann ich hier nichts qualifiziertes sagen.

Die Sprache mit der man das ganze betreibt ist natürlich auch ein interessanter Aspekt. Ich persönlich bevorzuge in fast allen Lebenslagen C. Eigentlich braucht man nur eine Sprache, die einen an die eigentlichen Syscalls dran lässt, wie es die meisten Script-Sprachen wie Perl auch zulassen. In der Windows-Welt geht es (mit entsprechenden Modulen) mit Visual Basic und mit Delphi auch. Da die fundamentalen Datenstrukturen aber alle auf C zugeschnitten sind, kann ich für die anderen Sprachen nur Tipps geben, was das Verhalten der Sockets angeht, nicht aber wie man sie genau verwenden kann. Im Visual-Basic-Umfeld existiert jede Menge Code, im Umfeld zu Perl selbstverständlich auch.

Wer noch keinen Lieblingscompiler hat, sollte sich einmal den gcc (stand früher für GNU C Compiler, inzwischen bedeutet es GNU Compiler Collection) ansehen. Unter UNIX-Systemen ist er meist installiert (falls nicht empfiehlt es sich ohnehin den dort installierten zu verwenden), für Windows gibt es die MinGW-Umgebung, http://www.mingw.org/.

Eine These die ich in den ursprünglichen Socket-Tipps aufgestellt habe, und die sich bisher auch recht oft zutraf, möchte ich auch hier erwähnen: Jeder der diese Sache mit C++ angepackt hat, versuchte früher oder später eine Socket-Klasse zu schreiben, die alles viel einfacher macht. Erfolgsberichte haben mich noch keine erreicht, aber ich hätte mit dem Schweiß der Leute sicher sämtliche Waldbrände dieses Sommers löschen können :-))

Zur Geduld habe ich früher nichts gesagt. Das hatte auch ganz gut geklappt, aber hier möchte ich doch ein paar warme Worte loswerden. Am Anfang ist es immer frustrierend, wenn irgendwas nicht klappt. Das ist normal, und gerade am Anfang ist die Lernkurve im Allgemeinen so steil, daß die Tränen schnell wieder trocknen. Bevor irgendwas wirklich nicht funktioniert (trotzdem erst die Zähne dran ausbeißen, das ist ein gutes Training, nicht nur falls der Postbote wiederkommen sollte) und man die Flinte ins Korn wirft, jemanden fragen der sowas schonmal gemacht hat. Wenn alle Stricke reissen, kommt her und heult euch bei mir aus ;-)

Die Verwendung der Sockets mit C ist sehr einfach, weil die Socket-API genau wie alle anderen Syscalls auch direkt auf C zugeschnitten sind. Unter UNIX kann man sogar gleich drauf losprogrammieren, aber unter Windows muß man die Sockets vorher aufwärmen. Es gibt den API-Call WSAStartup(), der in der entsprechenden Dokumentation (googelt nach win32.hlp, falls ihr die noch nicht habt) auch erklärt ist. An dieser Stelle soll uns nur dieses Codestück interessieren:

/* initialize windows sockets */
{
    WSADATA wsa;
    if (WSAStartup(MAKEWORD(1, 1), &wsa))
    {
        printf("WSAStartup() failed, %lu\n", (unsigned long)GetLastError());
        return EXIT_FAILURE;
    }
}
    

Prinzipiell gibt es noch mehr Unterschiede (die Socket-Tipps zielen auf UNIX, daher werden im Folgenden die Unterschiede zu Windows so aufgeführt, als sei Windows das böse, böse Ding), z.B. sind Sockets nicht vom Datentyp int, sondern werden durch SOCKET dargestellt. Die Funktionen geben auch nicht -1 zurück, sondern SOCKET_ERROR bzw. INVALID_SOCKET. Diese sind auf "alle Bits auf 1" definiert, und SOCKET ist vom Typ unsigned, sodaß der Vergleich auf -1 tatsächlich hinhaut, aber das kann sich rein theoretisch bei der nächsten Auflage der Winsocks ändern.

Die winsock.h ersetzt die meisten UNIX-Header! Wenn irgendwelche Header tatsächlich nicht da sind, probiert es einfach ohne sie. Das gilt natürlich nicht für die Header der (ANSI-)Standard-Funktionen wie z.B. stdio.h und stdlib.h. Eine Liste der UNIX-spezifischen Header, die durch winsock.h ersetzt werden, gibt's hier.

Nichtaufgelöste Symbole? Unter Windows? Ihr braucht neben der winsock.h als Header (in dem ja nur die Deklarationen stecken) auch die wsock32.lib (in der dann die eigentlichen Funktionen stecken). Je nach Compiler/Linker muß das noch angegeben werden, in der IDE muß man etwas suchen, auf der Kommandozeile reicht es meistens die wsock32.lib (ggf. mit kompletten Pfad) anzuhängen. Die Dokumentation des Compilers bzw. Linkers weiß mehr. Unter UNIX habe ich das bisher nur auf einem System gesehen, der notwendige Parameter für den Compiler bzw. Linker war in der Manpage zu socket() vorbildlich dokumentiert.

Ein Wort zu errno, strerror und perror sollte auch nicht fehlen: diese feinen Funktionen bzw. die globale Fehlervariable errno tun nur unter UNIX was sie wirklich sollen, da hier die Sockets ganz normale Syscalls sind. Unter Windows hat sich die Philosophie durchgesetzt, alle fremden Module (auch wenn sie in jedem Windows drinstecken) gesondert zu behandeln, was Fehlercodes angeht. So bekommt man die Fehlercodes im Zusammenhang mit Sockets unter Windows mit WSAGetLastError(). Den Fehlercode kann man entweder mit FormatMessage() in einen Text verwandeln, oder einfach den numerischen Code nachschlagen (Google findet ganz schnell eine Liste, ansonsten stehen sie auch im passenden Header). Unter UNIX hat die Resolver-Bibliothek, die für z.B. gethostbyname() angeleiert wird, eine eigene Fehlervariable h_errno, die für Fehler gesetzt wird. In dem Fall existiert eine Manpage zu herror().

Apropos Header: unter Windows reicht es im Allgemeinen winsock.h einzubinden, aber unter UNIX hat man den Kram auf einige Header verteilt. Auf richtigen Systemen (genauer: Systemen mit vernünftigen C-Bibliotheken) müssen diese auch noch in der richtigen Reihenfolge sein. Die Manpage weiß das genau, ansonsten gilt die Faustregel, daß die allgemeinen zuerst eingebunden werden sollen (wie z.B. sys/types.h, weil hier viele Datentypen deklariert sind). Ich habe es (hoffe ich) bei den einzelnen Befehlen dazu geschrieben. Achtung: nur weil es unter Linux funktioniert muß es noch lange nicht woanders klappen - Linux verwendet die glibc, welchselbige bei der Reihenfolge der Header ein paar Augen zudrückt (und sie quer untereinander includiert), aber so ein Programm fällt dann unter FreeBSD möglicherweise auf den Bauch. Es gilt also wie immer bei portabler Software: wissen daß sie portabel ist, ist gut, aber es auch auszuprobieren ist noch besser.

Was die Portabilität unter UNIX angeht, sollte man einen Blick auf die Single UNIX Specification, Version 3 geworfen haben. Dort wird man regelmäßig zum Registrieren gezwungen, um an Details zu kommen, die es anderswo auch ohne Registrierung gibt: http://www.posix.com/posix.html.