Résumé

  • Windows Sockets 1.1 a donné aux programmeurs Windows une interface TCP/IP inspirée de Berkeley, sans pouvoir reprendre telle quelle la sémantique des appels bloquants de Berkeley dans Windows 16 bits.
  • Un appel Winsock bloqué devait continuer à distribuer les messages Windows. Le bureau coopératif restait actif, au prix de limites strictes sur les appels imbriqués, la réentrance et la durée de vie des tampons mémoire.

Une interface devait cacher le réseau

Un socket offre au programme une façon stable d’envoyer et de recevoir des données sans le lier à l’éditeur de la pile réseau sous-jacente. Les développeurs Windows voulaient ce compromis. Ils souhaitaient aussi partir d’un modèle connu : les sockets de Berkeley étaient déjà la référence de programmation TCP/IP sous Unix.

Le projet Windows Sockets a commencé lors d’une session Birds of a Feather à Interop ’91, à San José, le 10 octobre 1991. La spécification 1.1 retrace la suite : un comité, des débats par courrier électronique, un premier projet largement nourri par les propositions de JSB et NetManage, une réunion organisée par Microsoft en décembre, puis un groupe de travail qui a poursuivi la rédaction. L’interface est née d’un travail collectif ; ce n’était pas un protocole TCP/IP dicté par un seul fournisseur.

La version 1.0 a été publiée pour que les éditeurs de logiciels réseau et les développeurs d’applications puissent commencer à bâtir leurs implémentations et leurs programmes sur un contrat commun. La version 1.1, datée du 20 janvier 1993, a apporté des éclaircissements et des corrections mineures à partir de l’expérience des entreprises qui avaient implémenté la version 1.0. La séquence compte : avant de stabiliser le texte suivant, le groupe l’avait déjà confronté au code.

La spécification reprenait le modèle des sockets de Berkeley afin qu’il soit familier aux programmeurs Unix et facilite le portage du code source. Elle définissait la suite Internet, avec les sockets de flux TCP et les sockets datagrammes UDP. Elle ne prétendait pas normaliser tous les réseaux possibles dans sa version 1.1. Cette limite était utile : les développeurs disposaient d’une interface familière, sans promesse que tous les systèmes placés dessous se comporteraient de la même façon.

Pendant l’attente, Windows devait traiter les fenêtres

La difficulté apparaît quand le programme attend. Un recv bloquant ne rend pas la main avant l’arrivée des données ou la résolution de l’opération. Sur un système à préemption, le système d’exploitation peut planifier d’autres tâches pendant cette attente. Windows Sockets 1.1 ne pouvait pas supposer ce mode de planification sous Windows 16 bits.

La documentation de Microsoft décrit une parade peu ordinaire. Si une opération bloquante ne pouvait pas se terminer immédiatement, le fournisseur de service la lançait puis entrait dans une boucle : distribuer les messages Windows, vérifier si l’appel socket était terminé, puis recommencer. Cette boucle permettait à l’application pilotée par les messages de continuer à traiter ceux de ses fenêtres pendant la lecture réseau.

Cette conception ne transformait pas un programme 16 bits en processus préemptif. Elle insérait le traitement des messages Windows dans un appel que les programmeurs auraient pu considérer comme une simple attente. Les messages reçus pendant cette attente pouvaient amener l’application à lancer un autre appel Winsock. La version 1.1 interdisait cet emboîtement : une tâche ne pouvait avoir qu’un seul appel Winsock en cours. Un appel imbriqué supplémentaire échouait avec WSAEINPROGRESS.

L’API prévoyait deux petites commandes de sortie. WSAIsBlocking permettait de vérifier si un appel était en cours. WSACancelBlockingCall pouvait l’annuler. Les applications plus complexes, notamment celles qui géraient plusieurs fenêtres de document, pouvaient installer WSASetBlockingHook et remplacer la routine par défaut de distribution des messages. L’application gagnait la main, mais devait alors assurer elle-même le bon traitement des messages.

L’interface a déplacé le travail, elle ne l’a pas effacé

Les appels non bloquants et asynchrones évitaient le problème de réentrance créé par le crochet de blocage. Ils imposaient une autre obligation. Si une opération conservait un pointeur vers un tampon ou un autre objet mémoire, l’application devait garder cet objet valide jusqu’à la fin de l’opération. La bibliothèque réseau ne pouvait pas décider de cette durée de vie à sa place.

Voilà l’écart, petit mais décisif, entre une API familière et un programme portable. Des noms de fonctions semblables peuvent réduire le coût initial d’un portage. Ils n’effacent ni le modèle de planification de l’hôte, ni sa boucle de messages, ni la durée de vie de la mémoire. Microsoft a ensuite documenté les en-têtes fournis pour faciliter le portage du code BSD ; les anciennes déclarations Winsock.h ont été conservées pour les applications existantes lorsque Winsock 2 a introduit Winsock2.h. La compatibilité est elle-même devenue un logiciel à maintenir.

La version 1.1 fixait sa limite à l’interface et aux types de socket TCP/IP pris en charge. Les fournisseurs fournissaient les implémentations ; les développeurs choisissaient comment attendre, annuler, distribuer les messages et protéger la mémoire. La spécification commune facilitait le raccordement de ces éléments. Elle ne rendait pas leurs responsabilités interchangeables.

Le choix technique marquant n’est pas que Windows ait copié Berkeley. Le groupe de travail a préservé assez du vocabulaire de programmation de Berkeley pour rendre le portage attrayant, puis a documenté les endroits où Windows ne pouvait pas se faire passer pour Unix. La boucle de messages était le reçu : l’interface ouverte réduisait un coût et montrait précisément où le suivant tombait.

Sources