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)
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).
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 :
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.
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 :
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.
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 :



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

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).

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éseau | passerelle | interface |
| réseau 1 | moi-même | i1 |
|---|---|---|
| défaut | P{1,2,3} | i1 |
Voici par contre celle de P{1,2,3}
| réseau | passerelle | interface |
| réseau 1 | moi-même | i1 |
|---|---|---|
| réseau 2 | moi-même | i2 |
| réseau 3 | moi-même | i3 |
| réseau 4 | P{3,4} | i3 |
| réseau 5 | P{3,5} | i3 |
| défaut | P{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 :
Une adresse complète permettant l'acheminement des données donc contenir les informations suivantes :
| domaine | pays |
|---|---|
| .ch | Suisse |
| .com | organisation commerciale |
| .de | Allemagne |
| .edu | Etats-Unis (université) |
| .fr | France |
| .gov | Etats-Unis (administration) |
| .it | Italie |
| .uk | Royaume-Uni |
| .us | Etats-Unis |
| .va | Vatican (!) |
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).
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.
function socket (af, struct, protocol : integer) : tSOCKET;
UNIX permet de choisir entre
| PF_UNIX | local |
| PF_INET | Internet |
| PF_NS | XEROX NS |
| PF_OSI | monde OSI |
| PF_IMPLINK | Internet imp |
| PF_SNA | SNA/IBM |
| ... |
| SOCK_DGRAM | non connecté |
| SOCK_STREAM | connecté |
| IPPROTO_UDP | UDP |
| IPPROTO_TCP | TCP |
| 0 | protocole par défaut :
TCP si SOCK_STREAM UDP si SOCK_DGRAM |
function closesocket (s : tSOCKET) : integer;
paramètre :
s : identifie le socket à fermer
retour : SOCKET_ERROR en cas d'erreur, 0 sinon.
function recv (s : tSOCKET; buf : PChar; len, flags : integer) : integer;
paramètres :
| MSG_PEEK | ne retire pas les données de la queue de réception |
| MSG_OOB | lit les données urgentes (Out Of Band) |
| 0 | lecture normale |
Remarque : Si aucun octet n'est disponible à la lecture, cette fonction bloque le processus jusqu'à ce que des données parviennent au socket.
function send (s : tSOCKET; buf : PChar; len, flags : integer) : integer;
paramètres :
| MSG_DONTROUTE | les données ne routeront pas |
| MSG_OOB | envoie des données urgentes (Out Of Band) |
| 0 | données normales |
function bind (s : tSOCKET; addr : sockaddr; namelen : integer) : integer;
paramètres :
Remarques :
function listen (s : tSOCKET; backlog: integer) : integer;
paramètres :
function accept(s: tSOCKET;addr: PSockaddr; addrlen : PInteger): tSOCKET;
paramètres :
function gethostbyname (name : PChar) : PHostEnt;
paramètre :
name : nom de machine
retour : nil en cas d'erreur, pointeur sur la structure suivante
hostent = record
h_name : nom de la machine
h_aliases : pointeur sur un tableau de surnoms (nil=fin)
h_addrtype : type d'adresse
h_length : taille d'une adresse (4 car PF_INET)
h_addr_list : pointeur sur un tableau de pointeurs d'adresses
(nil=fin)
end;
Remarques :
function getservbyname (name, proto : PChar) : PServEnt;
paramètre :
name : nom du service
proto : protocole à utiliser (nil pour utiliser le protocole par défaut)
retour : nil en cas d'erreur, pointeur sur la structure suivante
servent = record
s_name : nom du service
s_aliases : pointeur sur un tableau de synonymes (nil=fin)
s_port : port du service
s_proto : nom du protocole
end;
function inet_ntoa (inaddr : in_addr) : PChar;
function inet_addr (cp : PChar) : u_long;
Puisque le réseau peut être utilisé par des machines complètement différentes, les données qui y transitent doivent être standardisées. C'est le cas en particulier des adresses (numéro IP + port) qui doivent être transmises sous un format unique. Cette tâche est confiée aux fonctions suivantes qui doivent être systématiquement utilisées pour accéder aux structures hostent et servent.
function ntohs (netshort : u_short) : u_short;
function htons (hostshort : u_short) : u_short;
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.
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 |
| select | WSAAsyncSelect |
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_OOB | des 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)
function WSAStartup (wVersionRequired:word;lpWSData:LPWSADATA ):integer;nécessaire pour chaque application avant d'utiliser les fonctions de la DLL
paramètres :
wVersionRequired : version de Winsock attendue (010116 dans notre cas)
lpWSData : pointe sur une structure de type WSAData qui recevra les informations suivantes
| champ | signification |
|---|---|
| wVersion | version de Winsock supportée |
| wHighVersion | plus haute version de Winsock supportée |
| szDescription | description de la DLL |
| szSystemStatus | statut de la DLL |
| iMaxSockets | nombre maximal de sockets par application |
| iMaxUdpDg | taille maximale d'un datagramme UDP |
| lpVendorInfo | informations spécifiques au fournisseur |
retour : 0 si tout s'est bien passé, 0 sinon.
function WSACleanup : integer;
à appeler avant de terminer l'application.
retour : 0 si tout s'est bien passé, 0 sinon.
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.
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 :
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
|
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 :

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.

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.
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.

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.
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.
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.
contient l'identificateur du socket ou INVALID_SOCKET.
contient l'état du socket :
| s_idle | le socket ne fait rien de particulier |
| s_error | le socket est en erreur |
| s_closed | le socket n'est pas connecté |
| s_connecting | le socket est en cours de connexion |
| s_connected | le socket est connecté à un serveur |
| s_accepting | le socket est en cours de réponse |
contient le code d'erreur si le socket est dans l'état s_error.
construit l'objet sans créer de socket (utiliser ensuite assign pour associer l'objet à un socket déjà créé).
construit l'objet et crée un socket associé du type spécifié (SOCK_STREAM ou SOCK_DGRAM).
destructeur de l'objet : ferme le socket associé puis détruit l'objet.
associe l'objet à un socket déjà créé.
ferme le socket associé.
méthode appelée à chaque événement.
active la notification d'événements (même paramètre que WSAAsyncSelect)
mise en place de la notification.
arrêt de la notification.
libération du processeur pour un autre processus.
transforme un couple nom/port en adresse IP/port. Renvoie true si tout s'est bien passé.
lance une connexion vers la machine précisée sur le port spécifié.
place le socket en attente d'une connexion sur un service donné.
crée une instance d'objet serveur.
autorise la connexion d'une machine.
fonction POSIX de lecture.
fonction POSIX d'écriture.
liste des connexions acceptées.
Afin de disposer d'un identificateur de message unique (qui ne puisse pas être confondu avec WM_PAINT par exemple), on dispose de deux méthodes :
- déclarer une constante du type WM_USER+1,2,3 ...
- enregistrer dynamiquement le message avec RegisterWindowMessage
La deuxième méthode est bien entendu la meilleure.
Lors de la première utilisation, Windows crée un nouvel identificateur puis renvoie celui-ci pour tous les appels suivants. Ainsi, lors d'une session Windows, toutes les applications désirant traiter le message 'socket notification' utiliseront la même valeur numérique.
Nous utilisons ici directement l'API Windows et pas la bibliothèque objet Borland. En effet, peut-être avez-vous déjà réfléchi à la méthode à utiliser pour pouvoir créer une fenêtre principale cachée sans créer une instance de TApplication; ceci n'a rien d'évident, c'est même particulièrement inélégant. Pour cette raison, tout est fait ici en mode natif.
- on enregistre une nouvelle classe locale à l'application avec RegisterClass
- on crée la fenêtre avec CreateWindow
- on détruit la fenêtre avec DestroyWindow
- on supprime la classe locale avec UnregisterClass
Remarquez aussi la fonction de classe (WindowProc) définie comme export.
Ceci est nécessaire pour éviter de bloquer les autres applications : Windows 16bits n'étant pas préemptif, il faut forcer périodiquement l'activation des autres tâches.
Remarquez l'utilisation de GetMessage qui laisse plus de temps que PeekMessage.
procedure tsocketobject.idle;
var msg:tmsg;
begin
if peekmessage(msg,0,0,0,pm_noremove) then
if getmessage(msg,0,0,0) then
begin
translatemessage(msg);
dispatchmessage(msg);
end;
end;
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 ?

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 :
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 :
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é.

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
[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
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
| [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) |
| fichier | localisation (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 |
Lyonel Vincent
e-mail : vincentl@cc.ec-lyon.fr