Programmation réseau avec Winsock

Lyonel Vincent
(vincentl@cc.ec-lyon.fr)

Résumé : Cet article présente l'utilisation d'une pile de protocoles réseau très répandue dans le monde Windows
Mots-clefs : windows;réseau;winsock;Internet
Techniques Pascal : objets, DLL
Matériel Utilisé : PC 486 DX2 66 - BPW 7.0 - Windows 95
Champ d'application : toute version de Windows (>3.0), tout langage capable de produire du code Windows, tout PC (même non connecté à un réseau)

1. Introduction

1.1. Généralités

Nous entendons de plus en plus parler d'autoroutes de l'information, de cybersurf ou bien d'autres niaiseries pondues par nos amis les médias. Ils veulent bien entendu parler d'Internet, le réseau de réseaux mondial. Cet article prend le problème par l'autre bout de la lorgnette en montrant exactement de quelle manière les données sont acheminées au travers du réseau jusqu'à une application Windows. Fort heureusement, vous n'aurez nul besoin d'être connecté à un quelconque réseau pour tester les programmes de cet article (bien entendu, si vous avez accès à un réseau, ceci ne gâche rien et vous pourrez faire les tests en grandeur réelle sur deux machines distinctes).

1.2. Qu'est-ce que Winsock ?

La spécification Winsock (Windows Sockets) définit une interface de programmation réseau pour Windows basée sur la notion de socket introduite dans la BSD par l'Université de Californie. Ces sockets sont utilisés pour la communication dans le monde Internet (TCP/IP).

Winsock inclut aussi bien les fonctions BSD standard que des extensions spécifiques à Windows (traitement événementiel de la communication). Cette spécification impose en outre que toutes les fonctions de manipulation de sockets soient groupées dans une unique DLL : WINSOCK.DLL.

Il existe plusieurs fournisseurs de bibliothèques Winsock; parmi lesquels on peut citer :

1.3. Comment se procurer Winsock ?

Les prestataires de services Internet fournissent souvent un Winsock avec leurs produits mais vous pouvez utiliser Trumpet Winsock [TRUMPET] qui se trouve sur la plupart des sites FTP. La version que nous utiliserons sera la version 1.1 de la spécification Winsock [WINSOCK] (il existe déjà la version 2.0 qui étend l'interface à d'autres protocoles dont IPX de NetWare et intègre quelques améliorations).

Pour ceux qui ont la "chance" d'utiliser Windows 95, il suffit d'installer les outils réseau pour disposer d'un Winsock intégré au système.

2. Les sockets BSD

2.1. Généralités

Comme nous l'avons déjà dit, la notion de socket a été introduite dans les distributions Berkeley. Son but est de fournir un modèle général de communication entre deux processus (qui peuvent être localisés sur la même machine).

La manière la plus simple d'appréhender les sockets consiste à établir une analogie avec les communications humaines qui permettent la communication dans les deux sens (et pas à sens unique comme la télévision). On peut distinguer deux types principaux de communication : le courrier et le téléphone. Ces modes de communication sont caractérisés tous deux par le fait que l'une des parties prend l'initiative d'établir la communication (ceci implique bien sûr que l'autre lui ait fourni un moyen de la joindre : adresse ou numéro de téléphone). Par contre, ils fonctionnent sur des principes radicalement différents :

On distingue donc clairement deux types de communication : le mode connecté (téléphone) et le mode non connecté (courrier). Nous allons nous intéresser dans cet article principalement au mode connecté qui est à la charge de TCP (alors qu'UDP se charge du mode non connecté).

Pour le mode connecté, l'analogie téléphonique semble donc s'imposer. Pour mener une communication téléphonique, il faut :

1. installer le téléphone - fonction socket

1.bis obtenir un numéro d'appel - fonction bind

2. obtenir un numéro à appeler - fonction gethostbyname

3. appeler le numéro du correspondant - fonction connect

3.bis répondre à un appel - fonction accept

4. parler - fonction send

5. écouter - fonction recv

6. raccrocher - fonction closesocket

Remarque : Les numéros marqués bis correspondent aux tâches à accomplir pour être appelé (côté serveur).

On constate donc que le téléphone, contrairement au courrier qui permet d'envoyer des messages à n'importe qui pour autant que l'on possède son adresse, suppose une connexion suivant des règles bien précises.

2.2. Parenthèse sur l'adressage des machines Internet

Maintenant que nous savons qu'il faut pouvoir localiser le destinataire par l'intermédiaire de ce que nous avons appelé une adresse, il reste à déterminer en quoi consiste cette adresse.

Dans le cas d'Internet, les adresses des machines sont des nombres à 32 bits (soit 232=4 294 967 296 machines adressables). Ces adresses sont délivrées par un organisme central, ce qui permet d'assurer que chacune correspond à une seule et unique machine dans le monde. Ces adresses sont généralement notées sous la forme n1.n2.n3.n4 . Pour permettre le découpage du réseau en sous-réseaux, les bits de poids forts sont utilisés comme suit :

Ainsi, à partir de la simple adresse 156.18.81.200, on peut déduire une foule de renseignements : Ces renseignements sont utilisés pour acheminer un datagramme ("paquet" de données) jusqu'à la machine destinataire. Tout l'acheminement au travers du réseau est pris en charge par IP qui constitue le protocole réseau d'Internet (figure 1); c'est pour cette raison que l'adresse d'une machine est appelée numéro IP de la machine.

Remarque : Il existe un numéro IP spécial (127.0.0.1) qui désigne la machine locale (localhost).


fig 1. L'empilement des protocoles Internet

Tout cet acheminement au travers des différents réseaux interconnectés est une opération que l'on appelle le routage. Ce routage se fait par l'intermédiaire de machines-passerelles (gateways) entre plusieurs réseaux : ces machines possèdent un numéro IP dans chaque réseau (ce qui montre bien que, s'il ne peut y avoir qu'une machine par numéro IP, il peut cependant y avoir plusieurs numéros IP par machine).


fig 2. Plusieurs réseaux interconnectés
(initialement constitué des réseaux 1,2 et 3, le réseau s'est vu ajouter 4 et 5)

Dans cet exemple-ci (figure 2) où les passerelles sont indiquées par des rectangles non hachurés, P{1,2,3} a trois numéros IP alors que P{3,4} et P{3,5} n'en ont que deux.

Maintenant que nous avons installé des passerelles entre nos réseaux, il reste à définir quelle "route" mène à quel réseau. Ceci se fait grâce aux tables de routage. Chaque machine du réseau possède une table de routage qui lui indique qui contacter pour sortir du sous-réseau qui la contient.

Voici par exemple une table de routage d'une machine du réseau 1 (A par exemple) :

réseaupasserelleinterface
réseau 1moi-même i1
défautP{1,2,3}i1

Voici par contre celle de P{1,2,3}

réseaupasserelleinterface
réseau 1moi-même i1
réseau 2moi-même i2
réseau 3moi-même i3
réseau 4P{3,4}i3
réseau 5P{3,5}i3
défautP{3,4}i3

On peut remarquer que la connexion des réseaux 4 et 5 se fait sans aucune modification de la configuration des machines des autres réseaux. Ceci est un grand point fort d'Internet : la connexion de nouvelles machines demande très peu d'efforts; de plus, chaque nouvelle connexion offre une route supplémentaire pour les datagrammes (lorsque son chemin privilégié est indisponible, le datagramme en emprunte automatiquement un autre).

Illustrons maintenant ceci en déterminant le chemin que prendra un datagramme pour aller de A vers B :

  1. A émet par i1 en direction de P{1,2,3}
  2. P{1,2,3} s'aperçoit que le message est destiné au réseau 4
  3. P{1,2,3} fait suivre le message à P{3,4} par i3
  4. P{3,4} envoie finalement par i2 les données à B
Cet adressage des machine par IP (couche réseau) est bien entendu doublé par un adressage des applications par TCP et UDP (couche transport) : le port. Chaque application prétendant recevoir des communications de l'extérieur doit se mettre à l'écoute sur un port libre de la machine.

Une adresse complète permettant l'acheminement des données donc contenir les informations suivantes :

Tout ceci est bien joli mais on utilise généralement des noms symboliques pour les machines, ce qui est sensiblement plus pratique : avouez que "darkstar" est plus explicite que 156.18.81.200. Pour simplifier encore les choses, on introduit la notion de domaine, sorte d'extension du nom de la machine, qui permet de regrouper les machines d'un même campus par exemple, les différents domaines étant séparés par ".". Le domaine indique aussi le pays :
domainepays
.chSuisse
.comorganisation commerciale
.deAllemagne
.eduEtats-Unis (université)
.frFrance
.govEtats-Unis (administration)
.itItalie
.ukRoyaume-Uni
.usEtats-Unis
.vaVatican (!)

Ainsi, par exemple, www.ec-lyon.fr désigne la machine www de l'Ecole Centrale de Lyon, en France. Pour chaque domaine, il existe une machine appelée serveur de noms (Domain Name Server) qui est en charge de transformer les noms symboliques en numéros IP (si le serveur local ne "connaît" pas le nom, il passe le relais à un serveur plus général et ainsi de suite).

2.3. Fonctions BSD (listings 1 & 2)

Remarque : Les listings fournis correspondent à une version "allégée" de Winsock et ne contiennent que les déclarations indispensables au fonctionnement des exemples cités dans cet article. Ce choix répond à deux objectifs : permettre une compréhension plus rapide au travers de listings plus concis et surtout, éviter d'encombrer le lecteur avec 40 pages de listing bien tassé.

Pour cette raison, ne soyez pas étonnés de ne pas trouver la déclaration de telle ou telle fonction ou constante non indispensable à la compréhension.

2.3.1. Fonctions de gestion des sockets

2.3.2. Fonctions d'utilité plus générale

3. Extensions spécifiques à Windows

Outre les fonctions BSD standard, Winsock intègre plusieurs extensions spécifiques à Windows. Ces extensions ont principalement pour objet de tirer parti de la nature orientée événements de Windows.

3.1. Fonctions non-bloquantes

A côté des fonctions classiques, on trouve des fonctions non-bloquantes :

fonction bloquanteéquivalent non-bloquant
gethostbyaddr WSAAsyncGetHostByAddr
gethostbyname WSAAsyncGetHostByName
getprotobyname WSAAsyncGetProtoByName
getprotobynumber WSAAsyncGetProtoByNumber
getservbyname WSAAsyncGetServByName
getservbyport WSAAsyncGetServByPort
selectWSAAsyncSelect

La seule fonction de ce groupe qui nous intéressera véritablement est WSAAsyncSelect qui permet d'être tenu au courant des événements affectant un socket particulier :

function WSAAsyncSelect(s:tSOCKET;HWindow:HWND;wMsg:u_int;lEvent:longint):integer;

paramètres :

s : socket à surveiller

HWindow : fenêtre qui recevra les messages

wMsg : message à envoyer

lEvent : masque d'événements obtenu par combinaison (+) des constantes suivantes

FD_READ des données normales sont arrivées
FD_WRITE le tampon de sortie est vide
FD_OOBdes données spéciales sont arrivées
FD_ACCEPT un appel de l'extérieur vient d'arriver
FD_CONNECT la connexion (par connect) est terminée
FD_CLOSE l'interlocuteur vient de "raccrocher"

format du message envoyé :

wparam : socket concerné

hiword(lparam) : code d'erreur

loword(lparam) : événement (combinaison des constantes ci-dessus)

3.2. Fonctions utilitaires

4. Exemple d'utilisation

Après cette description de Winsock, nous allons tenter de mettre en application nos fraîches connaissances au travers d'un exemple simple, programmé de façon naïve : un FINGER.

4.1. Le protocole FINGER

FINGER est un protocole qui permet d'obtenir des renseignements sur un utilisateur d'un système (figure 3) . Ce protocole est spécifié dans [RFC1288]. Il est habituellement basé sur le port TCP numéro 79.

La communication est très simple :

  1. connexion d'un client
  2. réception d'une requête du type nomutilisateur CRLF
  3. envoi de la réponse
  4. le client se déconnecte

Login name: pirmann             In real life: David Pirmann
Office: 016 Hill, x2443         Home phone: 989-8482
Directory: /dimacs/u1/pirmann   Shell: /bin/tcsh

Last login Sat Jun 23 10:47 on ttyp0 from romulus.rutgers.
No unread mail
Project:
Plan:
                      Work Schedule,Summer 1990
                Rutgers LCSR Operations, 908-932-2443

                        Monday      5pm - 12am
                        Tuesday     5pm - 12am
                        Wednesday   9am -  5pm
                        Thursday    9am -  5pm
                        Saturday    9am -  5pm

                           larf larf hoo hoo
fig 3. Exemple de résultat FINGER
connexion à : dimacs.rutgers.edu
requête : pirmann<CRLF>

4.2. Le serveur (listings 3 et 4)

Dans notre cas, nous ne traiterons pas véritablement les requêtes FINGER : nous nous contenterons de répondre systématiquement que l'utilisateur spécifié est inconnu.
Pour ce qui est du fonctionnement du serveur, on peut découper les choses en trois parties :

  1. démarrage
    On commence par initialiser la DLL (procédure StartUp) en recueillant quelques informations sur la version de Winsock qui sont affichées (procédure ShowWinsockInfo).
    On récupère le port sur lequel on doit se brancher (procédure FindFingerService).
    On crée un socket qui servira à l'attente d'une connexion (procédure CreateSocket).
    On attache ce socket au port FINGER (procédure BindToSocket). Remarquer l'adresse INADDR_ANY pour se mettre en écoute sur toutes les adresses IP de la machine.
  2. attente d'une connexion
    On se met en écoute sur le socket (procédure ListenToSocket). On active au passage la notification des événements (WSAAsyncSelect).
    Nous sommes maintenant prêts à accepter une connexion.

    fig 4. Le serveur FINGER en attente d'appel
  3. réponse à une demande de connexion
    La fenêtre reçoit un message USER_CONNECT, accepte la connexion (avec accept) et active la notification d'un autre message : USER_READ. On affiche au passage l'adresse du client.
    Lorsque l'on reçoit USER_READ, ceci signifie que le client nous envoie un nom d'utilisateur; nous répondons systématiquement que cet utilisateur est inconnu ("unknown user") puis le socket accepté est fermé (closesocket).

4.3. Le client (listings 3 et 5)

Le fonctionnement est à peu près identique, à ceci près que l'on n'active pas de notification d'événements (en effet, le client provoque lui-même tous les événements). On initialise, on demande le nom de l'hôte, on se connecte puis on demande le nom de l'utilisateur à visualiser et on affiche les données renvoyées en réponse.


fig 5. Une session FINGER
machine : darkstar
utilisateur : root

5. Tentative de structuration

Les deux exemples précédents ont une grande qualité que n'ont pas forcément tous les programmes : ils fonctionnent à peu près; seulement, ils ont aussi un gros défaut : ils sont très obscurs. De plus, ils sont le résultat d'une programmation naïve, sans aucune méthode. Imaginons maintenant que nous voulions écrire un serveur pour un autre protocole, tout est à refaire. Plutôt réfléchir avant de programmer.

Pour tenter d'y voir un peu plus clair et de mettre à profit notre connaissance des objets, nous allons faire un peu l'analyse du problème.

5.1. Le modèle OSI

L'ISO a proposé un modèle théorique (figure 6) qui permet d'appréhender les communications informatiques. Ce modèle, très bien ficelé, a cependant pour notre cas un gros défaut : ayant été conçu après TCP/IP, il ne correspond pas exactement à l'empilement de protocoles (figure 1) utilisé dans le monde Internet.

Nous allons cependant tenter de nous en inspirer.


fig 6. Le modèle OSI à sept couches

Voici la signification de chacune des couches, en commençant par la couche de plus bas niveau.

Physique (Physical Layer)

Cette couche concerne le transport des données sous leur forme la plus élémentaire: les bits. On peut regrouper dans cette couche les modems, les brochages...

Liaison de données (Data Link Layer)

Cette couche comporte trois fonctions:

regroupement des bits sous forme de trames séparées par des signes de reconnaissance.

détection et correction des erreurs (CRC - Cyclic Redundancy Codes...).

définition de règles de conversation (autorisation d'émettre, accusé de réception, rejet). cette dernière fonction peut faire l'objet d'une couche spécifique.

Réseau (Network Layer)

Permet l'acheminement des données d'un point à un autre (établissement & fermeture d'une connexion, envoi de données express). Cette couche concerne la communication entre machines. On peut considérer IP comme se rapportant à cette couche.

Transport (Transport Layer)

Cette couche permet la communication entre processus (en mode connecté ou non connecté). Il s'agit du niveau où l'on peut situer TCP et UDP.

Session (Session Layer)

Fournit un ensemble de services pour la synchronisation des communications (synchronisation & contrôle du dialogue, resynchronisation en cas d'erreur).

cette couche est la plus discutée quant à son utilité

Présentation (Presentation Layer)

Standardisation des formats d'échange des données structurées (ex. entiers, chaînes, structures).

Application (Application Layer)

Contient un certain nombre de services (messagerie, terminaux virtuels, transfert de fichiers...).

En résumé, on constate que Winsock recouvre toutes les couches de Physique à Transport. Quant aux protocoles comme FINGER ou HTTP (que nous verrons plus tard), on peut les situer au niveau Application. Les fonctions comme htons ou ntohs appartiennent, pour leur part, à la couche Présentation.

5.2. Le modèle objet (listings 6 & 7)

Puisque Winsock se charge des couches de Physique à Transport (ou Session), le modèle objet commencera à Présentation (en fait, il sera constitué quasi uniquement de la couche Application). On choisira un traitement événementiel de la communication.

Les couches successives seront construites en créant des descendants de l'objet tsocketobject.

5.2.1. Fonctionnalités de l'objet tsocketobject

On créera une instance de l'objet tsocketobject par socket utilisé. On dispose donc d'un dictionnaire sockets/objets.

Le traitement des données sera fait de manière événementielle.

Pour une réponse à une demande de connexion, on crée un nouvel objet serveur qui sera libre d'accepter ou non la communication.

5.2.2. Description détaillée de l'objet tsocketobject

5.3. Techniques Windows employées

5.4. Exemple d'utilisation : un serveur WEB (listing 9)

Nous allons maintenant pouvoir nous attaquer, grâce à la structuration effectuée, à un problème plus complexe : la réalisation d'un serveur WEB.

Tout d'abord, qu'est-ce que le WEB ?


fig 7. Netscape en fonctionnement

Le WEB fait partie de ce que les médias appellent pompeusement les autoroutes de l'information. Il s'agit globalement d'un gigantesque maillage hypertexte accessible par un programme spécifique : Mosaic (qui commence maintenant à être remplacé par Netscape [NETSCAPE]). Chacun de ces documents hypertexte peut contenir du texte, des images, du son ou bien des liens vers d'autres documents (figure 7). Notre but est d'écrire un serveur qui pourra s'intégrer dans ce maillage. Nous devons donc :

5.4.1. Le protocole HTTP/0.9

HTTP signifie HyperText Transfer Protocol et constitue, dans sa version 0.9, un modèle de simplicité qu'il est très facile de mettre en oeuvre (la version la plus utilisée actuellement est la 1.0, spécifiée dans [HTTP], qui consiste en une extension de HTTP/0.9 mais, étant plus complexe, elle compliquerait inutilement notre programme). Nous adapterons légèrement ce protocole pour supporter (très partiellement) les requêtes HTTP/1.0 .

Comme son nom ne l'indique peut-être pas forcément, ce protocole est utilisé par les clients WWW pour transférer les pages HTML ("mosaic"). L'interrogation d'une page se fait suivant le schéma suivant :

  1. connexion à un serveur WEB sur le port http (généralement, 80)
  2. envoi de la séquence GET nomdefichier
  3. réception du contenu du fichier en question
  4. fermeture de la connexion
L'interrogation d'une simple page HTML comportant quelques images se compose donc d'une série de connexions/déconnexions (une par image + une pour le texte de la page).

5.4.2. Utilisation de notre serveur

Le serveur reste en permanence en attente d'une connexion (dans le monde UNIX, on appelle un tel processus un démon, ce qui explique le "d" de httpd). Pour ne pas gêner le travail avec d'autres applications, la fenêtre principale est cachée et ne redevient visible que lorsque l'on lance une deuxième fois le serveur. Lorsque la fenêtre principale est de nouveau visible (figure 8), il suffit d'appuyer sur [esc] pour arrêter le serveur et quitter le programme. Attention : pour conserver sa simplicité au code, l'appui sur [alt]+[f4] n'est pas géré.


fig 8. Le serveur HTTP en fonctionnement

5.4.3. Configuration du serveur

Le seul fichier de configuration nécessaire est le fichier httpd.ini dont la syntaxe est très simple : dans la section [files], trois entrées règlent le fonctionnement du serveur. Ces entrées sont

Vous trouverez ci-dessous des exemples de fichiers.
[files]
root=d:\www
home=d:\www\home.htm
error=d:\www\error.htm

fig 9. Exemple de fichier httpd.ini

<HTML>

<TITLE>ERROR</TITLE>

<H1>ERROR : access denied</H1>

</HTML>

fig 10. Exemple de fichier error.htm

<HTML>

<TITLE>Home page</TITLE>

<H1>Bienvenue - Welcome - Bonvenon</H1>

</HTML>

fig 11. Exemple de fichier home.htm

6. Listings

Listing 1 : winsock.inc - version "allégée"

Listing 2 : winsock.pas - version "allégée"

Listing 3 : error.inc

Listing 4 : fingerd.pas

Listing 5 : finger.pas

Listing 6 : utils.pas

Listing 7 : sockets.pas

Listing 8 : finger.pas - version structurée

Listing 9 : httpd.pas

7. Conclusion

Nous avons abordé dans cet article une petite partie des possibilités de la programmation réseau à travers des exemples simples mais qui reflètent bien la puissance de l'architecture d'Internet.

La structure objet que nous avons développée est tout-à-fait générique et peut être réutilisée pour n'importe quel service, tant du côté client que du côté serveur et aussi bien pour TCP que pour UDP.

Bien entendu, notre serveur HTTP peut être grandement amélioré :

- "interface-utilisateur" plus évoluée

- traitement des requêtes HTTP/1.0

- suivi des consultations (statistiques : pages les + consultées, par qui, etc.)

- sécurité renforcée

8. Bibliographie

[HTML]Berners-Lee, T., Connolly, D. 1995 Hypertext Markup Language - 2.0, Internet-Draft
[HTTP]Berners-Lee, T., Fielding, R.T., Frystyk Nielsen, H. 1995 Hypertext Transfer Protocol HTTP/1.0, Internet-Draft
[RFC1288]Zimmerman, D. 1991 The Finger User Information Protocol, Request For Comments 1288
[SOCKETS]Jazouli, A.I., Radi, N.E., Zghal, T. 1994 L'interface des Sockets [32], ENSIMAG
[UNIX]Rifflet, J.M. 1995 La communication sous UNIX, Applications réparties Ediscience international.
[WINSOCK]Hall, M., Towfiq, M., Arnold, G., Treadwell, D. & Sanders, H. 1992 Windows Sockets Specification InfoMagic, Inc. of Princeton (disponible comme fichier d'aide Windows avec BC++ 4)

9. Où se procurer les fichiers cités dans cet article ?

fichierlocalisation (URL)
[HTML] (.ps et .txt)http://www.w3.org/
[HTTP] (.ps et .txt)http://www.w3.org/
[NETSCAPE] (.exe)ftp://cc05.cc.ec-lyon.fr/pub/pc/www/n16e11n.exe
[RFC1288] (.txt)ftp://ftp.ibp.fr/pub/rfc/rfc/rfc1288.txt
[SOCKETS] (.ps)ftp://ftp.imag.fr/pub/DOC.UNIX/SOCKETS/
[TRUMPET] (.zip)ftp://cc05.cc.ec-lyon.fr/pub/pc/drivers/winsock.zip


article paru dans Pascalissime (numéros d'Octobre 95 et Janvier 96)

Lyonel Vincent
e-mail : vincentl@cc.ec-lyon.fr