Zusammenfassung

  • Windows Sockets 1.1 gab Windows-Programmierern eine an Berkeley angelehnte TCP/IP-Schnittstelle, konnte Berkeleys blockierendes Verhalten aber nicht unverändert in Windows mit 16 Bit übernehmen.
  • Ein blockierter Winsock-Aufruf musste weiterhin Windows-Nachrichten verteilen. So blieb der kooperative Desktop ansprechbar; zugleich galten strenge Grenzen für verschachtelte Aufrufe, Reentranz und die Lebensdauer von Puffern.

Ein Socket-Aufruf sollte das Netzwerk verbergen

Ein Socket gibt einem Programm eine stabile Möglichkeit, Daten zu senden und zu empfangen, ohne es an den Anbieter des darunterliegenden Netzwerkstacks zu binden. Genau diesen Tausch wollten Windows-Entwickler. Sie wollten auch auf einem vertrauten Modell aufbauen: Berkeley-Sockets waren unter Unix bereits das maßgebliche TCP/IP-Programmiermodell.

Das Windows-Sockets-Projekt begann am 10. Oktober 1991 bei einer Birds-of-a-Feather-Sitzung auf der Interop ’91 in San José. Die Spezifikation 1.1 hält fest, was folgte: ein Ausschuss, E-Mail-Debatten, ein Entwurf, der stark auf Vorschlägen von JSB und NetManage beruhte, ein von Microsoft im Dezember ausgerichtetes Treffen und eine Arbeitsgruppe, die das Dokument weiterentwickelte. Die Schnittstelle entstand in einer Gruppe; sie war kein TCP/IP-Protokoll, das ein einzelner Anbieter vorschrieb.

Version 1.0 wurde veröffentlicht, damit Anbieter von Netzwerksoftware und Anwendungsentwickler Implementierungen und Programme auf Basis eines gemeinsamen Vertrags beginnen konnten. Version 1.1 vom 20. Januar 1993 brachte Klarstellungen und kleinere Korrekturen ein, die auf den Erfahrungen mehrerer Firmen mit Version 1.0 beruhten. Die Reihenfolge zählt: Bevor die nächste Fassung sprachlich feststand, war die Spezifikation bereits mit laufendem Code abgeglichen worden.

Die Spezifikation übernahm das Berkeley-Socket-Modell, damit Unix-Programmierer sich zurechtfanden und Quellcode leichter portiert werden konnte. Sie definierte die Internet-Protokollfamilie mit TCP-Stream-Sockets und UDP-Datagramm-Sockets. Sie behauptete nicht, dass Version 1.1 jedes denkbare Netzwerk standardisierte. Diese Grenze war nützlich: Entwickler bekamen eine vertraute Schnittstelle, aber kein Versprechen, dass sich jedes System darunter gleich verhalten würde.

Dann traf recv auf einen Desktop mit 16 Bit

Schwierig wird es, wenn ein Programm wartet. Ein blockierendes recv kehrt erst zurück, wenn Daten eintreffen oder der Vorgang anderweitig abgeschlossen ist. Auf einem präemptiv geplanten System kann das Betriebssystem währenddessen andere Arbeit ausführen. Windows Sockets 1.1 konnte diese Art der Planung unter Windows mit 16 Bit nicht voraussetzen.

Microsofts Dokumentation beschreibt den Ausweg ungewöhnlich genau. Konnte ein blockierender Vorgang nicht sofort abgeschlossen werden, startete der Dienstanbieter ihn und ging in eine Schleife: Windows-Nachrichten verteilen, prüfen, ob der Socket-Aufruf beendet war, wiederholen. So konnte die nachrichtengetriebene Anwendung ihre Fenster weiter bedienen, während der Netzwerkzugriff noch ausstand.

Dadurch wurde ein Programm mit 16 Bit nicht präemptiv. Die Nachrichtenverteilung von Windows wurde in einen Aufruf eingeschoben, den Entwickler sonst vielleicht als bloßes Warten behandelt hätten. Nachrichten, die währenddessen eintrafen, konnten die Anwendung zu einem weiteren Winsock-Aufruf veranlassen. Version 1.1 untersagte diese Verschachtelung: Eine Aufgabe durfte nur einen ausstehenden Winsock-Aufruf haben. Ein weiterer verschachtelter Aufruf scheiterte mit WSAEINPROGRESS.

Die API bot zwei kleine Auswege. WSAIsBlocking zeigte an, ob ein Aufruf gerade blockierte. WSACancelBlockingCall konnte ihn abbrechen. Komplexere Anwendungen, etwa mit mehreren Dokumentfenstern, konnten WSASetBlockingHook installieren und die standardmäßige Nachrichtenverteilung ersetzen. Damit erhielt die Anwendung mehr Kontrolle – und musste die Nachrichten selbst korrekt behandeln.

Die Schnittstelle verschob Arbeit, sie beseitigte sie nicht

Nichtblockierende und asynchrone Aufrufe vermieden das Reentranzproblem des blockierenden Hooks. Dafür entstand eine andere Pflicht. Hielt ein Vorgang einen Zeiger auf einen Puffer oder ein anderes Speicherobjekt, musste die Anwendung dieses Objekt bis zum Abschluss gültig halten. Die Netzbibliothek konnte diese Lebensdauer nicht für sie festlegen.

Das ist die kleine, aber entscheidende Lücke zwischen einer vertrauten API und einem portablen Programm. Gleiche Funktionsnamen können die Einstiegskosten eines Ports senken. Sie löschen weder das Planungsmodell des Hostsystems noch seine Nachrichtenschleife oder die Speicherlebensdauer aus. Microsoft dokumentierte später Headerdateien, die das Portieren von BSD-Quellcode erleichtern sollten. Die älteren Winsock.h-Deklarationen blieben für bestehende Anwendungen erhalten, nachdem Winsock 2 Winsock2.h eingeführt hatte. Kompatibilität selbst wurde zu Software, die weitergetragen werden musste.

Version 1.1 zog ihre Grenze bei der Schnittstelle und den darin unterstützten TCP/IP-Sockettypen. Anbieter lieferten Implementierungen; Anwendungsentwickler entschieden, wie gewartet, abgebrochen, Nachrichten verteilt und Speicher geschützt wurde. Die gemeinsame Spezifikation machte es leichter, diese Teile zu verbinden. Sie machte ihre Pflichten nicht austauschbar.

Die bemerkenswerte technische Entscheidung war nicht, dass Windows Berkeley kopierte. Die Arbeitsgruppe behielt genug von Berkeleys Programmierwortschatz bei, um Portierungen attraktiv zu machen, und dokumentierte zugleich, wo Windows nicht Unix spielen konnte. Die Nachrichtenschleife war der Kassenbon: Eine offene Schnittstelle senkte einen Kostenpunkt und zeigte genau, wo der nächste anfiel.

Quellen