Ecrire des applications réseau sous Linux

Depuis la démocratisation des réseaux locaux et de l'Internet, les ordinateurs sont amenés à communiquer de plus en plus entre eux. Si le but initial n'était que d'échanger des fichiers ou d'accéder à des bases de données, ces communications touchent maintenant tous les domaines de l'informatique. L'Internet se prête aujourd'hui aussi bien aux applications multimédia, aux jeux, aux communications humaines, qu'à la télémaintenance ou au contrôle de processus à distance.

Comment cela fonctionne-t-il ? Comment écrire des applications communicantes ? Pourquoi et comment définir ses propres protocoles de communication ? Autant de questions auxquelles cette série d'articles essaie de répondre par la pratique.

Ce premier volet donne quelques connaissances de base sur le protocole TCP/IP, décrit l'algorithme général d'un client TCP, montre le fonctionnement de la commande telnet et se termine par la programmation d'un client mail "embryonnaire".

Quelques rappels sur TCP/IP

Le client/serveur

Un logiciel client/serveur se distingue d'un logiciel autonome par le fait qu'il met en communication au moins deux processus. Ces deux processus peuvent s'exécuter sur des machines différentes à partir du moment où celles-ci sont reliées par un réseau. Un de ces processus s'appelle le serveur et l'autre, le client. Un serveur est généralement capable de gérer plusieurs clients de façon concurrentielle.

Le "langage" permettant aux deux processus de communiquer est appelé un protocole. En pratique, plusieurs protocoles de différents niveaux entrent en jeu dans une communication. Chaque protocole joue un rôle bien particulier comme, par exemple, assurer l'acheminement de l'information vers les bonnes machines, contrôler et corriger les erreurs de transmission, transporter l'information utile.

L'ISO (International Standards Organization) a développé un modèle théorique de communication réseau à sept couches, l'OSI (Open Systems Interconnect Reference Model), dans le but de décrire la structure et le fonctionnement des communications entre machines.

La pile TCP/IP

On parle souvent de pile TCP/IP pour rappeler que TCP/IP est un ensemble de protocoles organisé en couches. TCP et IP sont en fait deux protocoles parmi les plus importants de cette pile.

Des tentatives ont été menées pour essayer de rapprocher le modèle TCP/IP du modèle OSI. La décomposition en couches de TCP/IP est malheureusement sujette à controverse et on peut trouver dans l'abondante littérature traitant du sujet des descriptions comprenant de trois à sept couches !

Sans entrer dans ce débat, on peut tout de même présenter un modèle simplifié à quatre couches que l'on retrouve assez fréquemment :

3 Couche réseau : Cette couche définit les protocoles permettant d'accéder au réseau physique. Cela comprend les gestionnaires des différentes cartes réseau, mais aussi des protocoles comme ARP, utilisé sur les réseaux Ethernet ou PPP, pour la communication par ligne série (modem).

3 Couche Internet : Le protocole IP (Internet Protocol) définit le système d'adressage de l'Internet (les fameuses adresses IP) ainsi que l'unité de transmission des données (le datagramme). Il sait acheminer les datagrammes d'une machine à une autre, directement, si les machines se trouvent sur le même réseau local ou par le biais de passerelles ou routeurs dans le cas contraire.

3 Couche transport : Les deux protocoles les plus importants de cette couche sont UDP (User Datagram Protocol) et TCP (Transmission Control Protocol). UDP est un protocole demandant peu de ressources, mais qui ne vérifie pas si le destinataire a correctement reçu les données. TCP est par contre un protocole fiable basé sur un mécanisme d'acquittements. Ces deux protocoles utilisent des numéros de port afin de déterminer à quels processus les données doivent être transmises.

3 Couche application : C'est la couche qui comprend le plus de protocoles et elle est destinée à fournir des services aux utilisateurs. Citons par exemple TELNET, qui permet de se connecter à distance sur une machine, FTP (File Transfer Protocol) pour le transfert de fichiers, HTTP (HyperText Transfer Protocol) pour la diffusion d'informations sur le Web, SMTP (Simple Mail Transfer Protocol) pour le transfert de courriers électroniques, NFS (Network File System) pour le partage de fichiers, mais aussi IRC (Internet Relay Chat), ICQ (I seek you), etc.

Des ports et des services

Si les adresses IP permettent de localiser de manière unique des machines sur un réseau local ou sur l'Internet, elles ne suffisent pas à déterminer quels processus sont mis en cause dans la communication. Prenons l'exemple d'une machine exécutant un serveur HTTP et un serveur FTP. Lorsque la pile TCP/IP reçoit des données, elle doit être capable de les délivrer soit au processus serveur HTTP, soit au processus serveur FTP. La couche transport associe à cet effet un numéro de port à chaque voie de communication. Une voie de communication ne peut être détenue que par un seul processus. Il en résulte que le couple (adresse IP, n° de port) permet de définir non seulement la machine mais aussi le processus destinataire des données.

Les processus serveurs correspondant aux services classiques utilisent généralement des numéros de port connus de l'ensemble de la communauté Internet. C'est ainsi que le port 80 correspond au service HTTP, le 21 au service FTP, le 25 à SMTP, etc. Ces numéros assignés sont décrits dans la RFC 1340 (Request for Comments). Le fichier /etc/services de toute installation Linux reprend un certain nombre de ces numéros.

L'interface Socket

TCP/IP décrit de façon interne le fonctionnement de ses protocoles (format des datagrammes IP, procédures de gestion, etc), mais ne définit pas d'interface de programmation. Introduite au début des années 1980 par l'université de Berckley, l'interface socket permet de doter tout système UNIX d'un ensemble de fonctions systèmes destinées aux communications TCP/IP. Le premier système proposant cette interface a été BSD UNIX 4.1. Elle fait aujourd'hui référence dans le monde UNIX.

UNIX possède depuis ses débuts un ensemble de fonctions qui permettent à un programme d'accéder aux entrées/sorties (open, read, write, close, ioctl, lseek). L'interface socket ajoute de nouveaux appels et de nouvelles structures de données afin de gérer les spécificités de TCP/IP.

Voici les fonctions clés de cette API :

socket : Crée la structure de données interne permettant la communication. Cette structure est communément appelée socket. Le programmeur choisit, entre autres, le protocole de transport à utiliser (TCP ou UDP).

connect : Etablit la connexion avec un processus serveur.

L'adresse IP et le numéro de port sont spécifiés.

write : Envoie des données sur une connexion TCP.

read : Reçoit des données sur une connexion TCP.

close : Ferme la connexion et détruit la structure de données associée.

bind : Associe un numéro de port à une socket. Les processus serveurs l'utilisent pour spécifier le port sur lequel ils attendent les connexions.

listen : Place la socket en mode passif, afin de lui permettre d'accepter des connexions. Seuls les serveurs utilisent cet appel.

accept : Attend une demande de connexion sur une socket passive et, lorsqu'elle arrive, crée une nouvelle socket active. Les serveurs attendent des connexions avec accept sur une socket passive, puis communiquent avec les clients par les sockets actives ainsi crées.

Cette interface est écrite en C et ce langage s'impose donc dans la programmation des applications client/serveur. Toutefois, de nombreux autres langages, et notamment des langages interprétés comme Perl ou Tcl/Tk, fournissent une API analogue, parfois même plus simple à mettre en œuvre.

Et Linux dans tout ça ?

TCP/IP est lié aux systèmes UNIX et UNIX-like, dont Linux, de manière très intime. Le fonctionnement de l'environnement fenêtré X-Window, par exemple, s'appuie largement sur TCP/IP. La majorité des distributions Linux installent une pile TCP/IP complète ainsi que de nombreux logiciels applicatifs clients et serveurs. Les nombreux langages de programmation gratuits de Linux permettent d'écrire rapidement des applications réseau en utilisant l'API socket pour accéder aux protocoles TCP/IP.

Toutes ces raisons font de Linux un système de choix pour le développement et l'utilisation de logiciels client/serveur.

La programmation des clients

Cette partie aborde le client/serveur par sa pente la plus douce : accéder à des serveurs existants depuis une application cliente développée par nos soins. Ceci nous permettra de mieux cerner le dialogue client/serveur et de se familiariser avec les commandes de base.

Algorithme général d‘un client TCP

Créer une socket (socket)

Connecter la socket au serveur (connect)

Tant qu'il reste des choses à faire :

Envoyer des requêtes au serveur (write)

Lire les réponses du serveur (read)

Traiter les réponses

Fin Tantque

Fermer la socket (close)

Telnet : Un client TCP universel

La commande telnet sous Linux est la partie cliente du service Telnet. Elle permet normalement à un utilisateur d'ouvrir une session texte sur une machine distante. Elle accepte deux paramètres : le premier est le nom de la machine sur laquelle on désire se connecter, le second, optionnel, est un numéro de port. Si on ne le précise pas, la commande telnet réalise une connexion sur le port 23 de la machine désirée. Le port 23 correspond au port par défaut du service Telnet.

Son algorithme est celui du client TCP décrit ci-dessus à ceci près : tout caractère entré au clavier est envoyé au serveur, tout caractère en provenance du serveur est affiché à l'écran. Ce fonctionnement relativement simple est une véritable mine d'or pour le programmeur d'applications serveur. En effet, il est possible, en utilisant un autre numéro de port, de communiquer directement, par le clavier, avec n'importe quel type de serveur TCP. Voici un exemple d'utilisation de telnet pour communiquer avec l'agent de transport du courrier (sendmail) :

Les lignes en gras sont celles entrées par l'utilisateur, celles commençant par un nombre sont envoyées par le serveur, les autres sont propres à telnet.

telnet localhost 25

Trying 127.0.0.1...

Connected to localhost.

Escape character is '^]'.

220 gollum.fg ESMTP Sendmail 8.8.0/8.8.2; Tue, 11 …

HELO toto

250 gollum.fg Hello root@localhost [127.0.0.1], pleased to meet you

MAIL FROM: moi

250 moi... Sender ok

RCPT TO: root

250 root... Recipient ok

DATA

354 Enter mail, end with "." on a line by itself

Salut la compagnie !

blabla...

.

250 QAA28517 Message accepted for delivery

QUIT

221 gollum.fg closing connection

Connection closed by foreign host.

A l'issue de cette liste de commandes, un mail sera effectivement envoyé à l'utilisateur root, sur le système local. La commande telnet et un zeste de protocole SMTP ont suffi pour cela. La commande mail fait la même chose, en déchargeant l'utilisateur de la connaissance du protocole.

Il est à noter que nombre de protocoles utilisent des chaînes de caractères ayant un sens (Hello, please to meet you, etc.), même si les humains que nous sommes n'en font généralement pas cas. La planète est couverte de machines s'envoyant chaque seconde des jolis mots et autres formules de politesse !

telnet permet donc d'appréhender un protocole existant ou de tester facilement toute application serveur TCP, et notamment celles que l'on développe.

Programmation d'un client SMTP

Ce premier programme est écrit en Tcl/Tk. Malgré sa syntaxe rébarbative, ce langage rend très simple la création d'une interface utilisateur sous X et propose un accès aux sockets facile à mettre en œuvre.

Ce programme communique avec le serveur SMTP à la manière de l'exemple précédent. Il ne gère pas les conditions d'erreurs. Les champs From, To et Subject définissent l'expéditeur, le destinataire et le sujet du mail. Le grand champ mémo représente le texte du mail. Le bouton " CX/FIN " permet de se connecter ou se déconnecter du serveur SMTP. Le bouton " Envoyer " permet de transmettre le mail au serveur SMTP.

#!/usr/bin/wish

set connected 0

# Variables devant contenir les valeurs a envoyer au serveur SMTP.

set tfrom "root"

set tto $tfrom

set tsujet "Client smtp"

# Valeur affichee sur la ligne de status (liee au label .status.status)

set status "Ready"

# Procedure de connexion au service smtp

proc CX {} {

global s

global status

global connected

# Ouverture de la socket cliente (connexion au serveur)

set s [ socket localhost smtp ]

# On la configure sans buffer d'E/S et pour envoyer

# des \r\n (crlf) au lieu de \n (lf)

fconfigure $s -buffering none -translation crlf

# On lit le message envoye par le serveur smtp

# (version du logiciel de mail)

gets $s status

# On envoie la commande HELO suivie d'un domaine

# puis on attend le message d'acquittement du serveur

puts $s "HELO Yoopee"

gets $s stat

# On ne prend que les 50 premiers caracteres

set status [ string range $stat 0 50 ]

set connected 1

.status.envoi configure -state normal

}

# Procedure de fermeture de la connexion

proc FIN {} {

global s

global status

global connected

# On envoie QUIT puis on attend la reponse du serveur

puts $s "QUIT"

gets $s status

# On ferme la socket

close $s

set connected 0

.status.envoi configure -state disabled

}

# Procedure cliente SMTP

proc SMTP {} {

global tfrom

global tto

global tsujet

global status

global s

# Recuperation du texte du mail

set tdata [ .bas.data get 1.0 end ]

# Envoi du mail par le protocole smtp

# Le serveur envoie des chaines d'acquittement

# pour les commandes MAIL, RCPT, DATA et "."

puts $s "MAIL FROM: $tfrom"

gets $s status

puts $s "RCPT TO: $tto"

gets $s status

puts $s "DATA"

gets $s status

puts $s "Subject: $tsujet"

puts $s "$tdata"

puts $s "."

gets $s status

}

# Procedure qui gere l'appui sur le bouton CX/FIN

proc CXFIN {} {

global connected

if { $connected } {

FIN

} else {

CX

}

}

# Description des controles de l'interface

frame .haut

frame .bas

frame .status

frame .haut.gauche

frame .haut.droite

label .haut.gauche.lfrom -text "From:"

label .haut.gauche.lto -text "To:"

label .haut.gauche.lsubject -text "Subject:"

entry .haut.droite.from -width 64 -textvariable tfrom

entry .haut.droite.to -width 64 -textvariable tto

entry .haut.droite.subject -width 64 -textvariable tsujet

text .bas.data -width 72

label .status.status -textvariable status

button .status.envoi -text "Envoyer" -underline 0 -state disabled -command { SMTP }

button .status.quit -text "CX/FIN" -underline 0 -command { CXFIN }

# Mise en place des controles de l'interface

pack .haut.gauche.lfrom .haut.gauche.lto .haut.gauche.lsubject -anchor e

pack .haut.droite.from .haut.droite.to .haut.droite.subject -anchor w

pack .bas.data

pack .haut.gauche .haut.droite -side left

pack .status.envoi -side right

pack .status.quit -side right

pack .status.status -side left

pack .haut .bas -anchor w

pack .status -fill x

L'instruction set s [ socket localhost smtp ] crée une socket de communication vers la machine " localhost " sur le port " smtp " et affecte son descripteur à la variable s. On peut remplacer localhost par un nom de machine quelconque accessible par le réseau. smtp est le nom du service correspondant au port 25. Il est évidemment nécessaire qu'un serveur SMTP s'exécute sur le port 25 de la machine à laquelle on s'adresse.

Les instructions puts $s envoient des chaînes de caractères au serveur SMTP par la socket s. Réciproquement, les instructions gets $s status attendent des chaînes de caractères en provenance du serveur SMTP et les placent dans la variable status. Le label .status.status est lié à cette variable, il affiche donc le dernier message reçu du serveur.

En résumé

L'écriture d'un client est chose relativement aisée. Dans le cadre d'une application professionnelle, il faudrait ajouter du code à notre exemple afin qu'il gère les erreurs de protocoles et lui adjoindre des fonctionnalités évoluées. Le lecteur pourra néanmoins s'appuyer sur le code présenté afin de réaliser des clients pour d'autres protocoles, comme par exemple ftp ou irc.

Le prochain article décrira plusieurs algorithmes serveurs. Les exemples en langage C aborderont des points essentiels du développement client/serveur comme, par exemple, la gestion des connexions simultanées.

 

Alain BASTY

Alain.Basty@accesinter.com

 


© Copyright 2000 Diamond Editions/Linux magazine France. - Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1or any later version published by the Free Software Foundation; A copy of the license is included in the section entitled "GNU Free Documentation License".