Blog ENI : Toute la veille numérique !
🐠 -25€ dès 75€ 
+ 7 jours d'accès à la Bibliothèque Numérique ENI. Cliquez ici
Accès illimité 24h/24 à tous nos livres & vidéos ! 
Découvrez la Bibliothèque Numérique ENI. Cliquez ici

Protocoles réseau

Introduction

La programmation d’applications communiquant via le réseau est un sujet complexe, qui peut être divisé en plusieurs thèmes. Nous en traitons deux dans ce livre. La programmation réseau de bas niveau du chapitre précédent correspond aux couches trois et quatre du modèle OSI, dites couches de réseau et de transport. Dans ce modèle, présenté dans l’introduction du chapitre précédent, les couches supérieures dites de session, présentation et application permettent de définir des protocoles de communication entre applications, ce que nous abordons dans ce chapitre. Celui-ci s’appuie d’ailleurs sur le précédent, en particulier la recette permettant de créer un client TCP/IP et la fonction create_tcp_client() qu’elle contient, car c’est elle qui permet de se connecter aux serveurs distants.

Le schéma suivant montre comment s’empilent les protocoles applicatifs, en haut, sur les protocoles de bas niveau, le protocole IP étant lui-même au-dessus de protocoles non représentés ici.

images/14_modele_OSI.png

Protocoles applicatifs

Remarquez que le protocole FTP utilise à la fois les protocoles TCP et UDP. Et si PostgreSQL et MySQL utilisent directement TCP/IP, il est aussi possible d’insérer une couche intermédiaire, ODBC. Notez que dans ce schéma, lorsqu’il...

Faire suivre un port

Problème

Vous avez besoin d’un programme qui agisse en tant que serveur mandataire pour un port.

Solution

Créez un serveur capable de se connecter sur une machine distante. Toute connexion sur le serveur entraîne une connexion sur la machine distante. Puis toutes les données disponibles sur une socket doivent être renvoyées sur l’autre et réciproquement.

Discussion

Le principe d’un tel programme est de créer un client et un serveur TCP. Ces deux tâches sont assurées par les fonctions create_tcp_server() et create_tcp_client(). C’est à l’utilisateur d’effectuer les connexions entre ce programme mandataire et les deux autres, serveur et client souhaitant communiquer. Puis, dans la boucle principale, le programme attend des données sur chacune des sockets à l’aide de la fonction select() et écrit dans chaque socket tout ce qu’il lit sur l’autre. La boucle principale se trouve dans la fonction forwarder().

La boucle de la fonction main() pourrait aussi être qualifiée de boucle principale. Notre serveur mandataire attend une connexion sur son serveur et, lorsque celle-ci a lieu, le programme se duplique avec fork() afin que le processus fils puisse jouer le rôle de mandataire. Pendant ce temps, le processus père itère et attend une nouvelle connexion.


#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <unistd.h> 
#include <netdb.h> 
#include <sys/socket.h> 
#include...

Obtenir une page web d’un serveur HTTP ou HTTPS

Problème

Vous voulez communiquer avec un serveur HTTP ou HTTPS pour, par exemple, télécharger des pages web.

Solution

Utilisez la recette permettant de créer un client TCP du chapitre "Réseau", envoyer une requête HTTP et obtenir la page web depuis un serveur HTTP. Créez le client TCP SSL comme vu dans le chapitre "Réseau" lorsqu’il s’agit d’un serveur HTTPS.

Utilisez libcurl pour obtenir une page web depuis un serveur HTTP ou HTTPS en écrivant peu de lignes de code.

Discussion

Pour vous connecter à un serveur HTTP, utilisez create_tcp_client() (chapitre "Réseau") et indiquez le nom de la machine hébergeant le serveur web en premier argument, et un numéro de port en second argument, sachant que, par défaut, un serveur web écoute sur le port 80. L’exemple suivant interroge le serveur web pour lire la page web http://serveur/index.html avec la requête GET /index.html et afficher le résultat sur la sortie standard :


#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <unistd.h> 
#include <sys/socket.h> 
#include <sys/types.h> 
#include <netinet/in.h> 
#include <netdb.h> 
 
int 
create_tcp_client (const char *hostname, int port) 
{ 
/* Voir le code de la recette "Créer un client TCP/IP" (chapitre "Réseau"). */ 
} 
 
void 
print_URL (const char *host, int port, const char *file) 
{ 
  int socket_id; 
  char str[1024]; 
  int l; 
 
  socket_id = create_tcp_client (host, port); 
  if (socket_id == -1) 
    { 
      fprintf (stderr, "Impossible de créer le client\n"); 
      exit (EXIT_FAILURE); 
    } 
/* Envoi de la requête "GET <url>\n" */ 
  write (socket_id, "GET ", sizeof ("GET ") - 1); 
  write (socket_id, file, strlen (file)); 
  write (socket_id, "\n", sizeof ("\n") - 1); 
  l = sizeof (str) - 1; 
  while (sizeof (str) - 1 == l) 
    { 
/* Lecture d'un espace tampon jusqu'à ce qu'il n'y ait plus rien à lire. */ 
      l = read (socket_id, str, sizeof (str) - 1); ...

Télécharger et transférer des fichiers avec le protocole FTP

Problème

Vous souhaitez télécharger ou transférer un fichier grâce au protocole FTP.

Solution

Utilisez les fonctionnalités de transfert via FTP de la bibliothèque libcurl.

Discussion

Télécharger un fichier

Le téléchargement d’un fichier via FTP est très proche de celui d’une page web comme décrit dans la recette précédente lorsque nous utilisons la bibliothèque libcurl. Les programmes suivants se compilent normalement, en ajoutant -lcurl sur la ligne de commande de l’éditeur de liens :


gcc -Wall -c programme.c
gcc programme.o -lcurl -o programme
 

La fonction suivante recherche aussi bien une page web qu’un fichier sur un site FTP et l’affiche sur la sortie standard :


void 
print_URL (const char *url) 
{ 
  CURL *curl; 
  CURLcode res; 
 
  /* Initialisations */ 
  curl = curl_easy_init (); 
  if (curl) 
    { 
      /* Paramètres pour cURL */ 
 
      /* Fichier à demander */ 
      curl_easy_setopt (curl, CURLOPT_URL, url); 
 
      /* Recherche le fichier. */ 
      res = curl_easy_perform (curl); 
 
      /* Nettoyage des variables de cURL; retour. */ 
      curl_easy_cleanup (curl); 
 
      if (CURLE_OK != res) 
        { 
          fprintf (stderr, "Une erreur cURL est apparue (res=%d)\n", 
                   res); 
          return; 
        } 
    } 
}
 

Vous pouvez donc l’appeler print_URL("ftp://ftp.gnu.org/README") pour télécharger le fichier README sur le site ftp.gnu.org.

Pour placer le résultat dans un fichier, au lieu de l’afficher sur la sortie standard, par exemple /tmp/README, utilisez curl_easy_setopt() pour spécifier une fonction de rappel qui écrive dans un fichier. C’est ce qu’effectue...

Créer un serveur HTTP

Problème

Vous voulez implémenter un serveur HTTP dans votre programme. Cela vous permettrait de créer un serveur web, ou de pouvoir vous connecter à distance à votre programme...

Solution

Utilisez la bibliothèque libmicrohttpd pour créer rapidement un serveur HTTP.

Discussion

La bibliothèque libmicrohttpd permet d’implémenter facilement un serveur HTTP basique. Vous pouvez l’installer comme vous avez l’habitude d’installer des bibliothèques, ou la télécharger depuis http://www.gnu.org/software/libmicrohttpd/.

Libmicrohttpd propose une fonction MHD_start_daemon() qui effectue tout le travail à votre place. Voici son prototype :


_MHD_EXTERN struct MHD_Daemon *  
MHD_start_daemon_va (unsigned int flags,  
                     uint16_t port,  
                     MHD_AcceptPolicyCallback apc, void *apc_cls,  
                     MHD_AccessHandlerCallback dh, void *dh_cls,  
                     va_list ap); 
 

Le premier argument, flags, permet de choisir entre plusieurs types d’implémentation du serveur HTTP, en particulier l’utilisation de select() (MHD_USE_SELECT_INTERNALLY) ou d’un thread par connexion (MHD_USE_THREAD_PER_CONNECTION). Il est possible d’indiquer des options supplémentaires, comme par exemple le support SSL ou IPv6, mais le sujet étant vaste, nous vous invitons à vous documenter dans le manuel de libmicrohttpd. Le deuxième argument sert à indiquer le numéro de port. Rappelons que les ports inférieurs à 1024 sont réservés à l’administrateur. Mieux vaut utiliser les habituels ports 8080 ou 8888, au moins pour commencer.

Les arguments 3 et 4, apc et apc_cls, sont une fonction de rappel et un pointeur qui lui est transmis. Vous les utilisez si vous souhaitez effectuer un contrôle des clients à autoriser ou non. Le filtrage s’effectue selon l’adresse IP du client, automatiquement fournie à votre fonction de rappel. Si vous pensez autoriser tout le monde, n’indiquez pas de fonction de rappel et mettez NULL.

Les arguments...

Envoyer un courrier électronique

Problème

Vous souhaitez vous connecter directement à un serveur SMTP pour envoyer un courrier électronique.

Solution

Connectez-vous à un serveur SMTP avec un client TCP et échangez en respectant le protocole SMTP (Simple Mail Transfer Protocol).

Discussion

Pour vous connecter à un serveur SMTP, utilisez create_tcp_client() comme précédemment et indiquez le nom de la machine hébergeant le serveur web en premier argument, et un numéro de port en second argument, sachant que, par défaut, un serveur SMTP écoute sur le port 25.

Le protocole SMTP est basé sur des échanges par lignes de texte, délimitées par les deux caractères consécutifs de retour chariot et de saut de ligne, à coder "\r\n" en C. De plus, tous les messages envoyés par le serveur SMTP commencent par un code correspondant à la signification du message. Cela autorise donc le serveur à compléter par un message qui n’est pas forcément explicite ou qui peut même être humoristique. Nous ne pouvons donc pas compter sur autre chose que le code pour interpréter la signification de ce que renvoie le serveur SMTP. La liste des codes et leur signification se trouve décrite dans la RFC 2821 qui concerne le protocole.

Un échange de courrier électronique s’effectue comme le montre l’exemple suivant. Nous avons ajouté un S: pour les lignes retournées par le serveur et un E: pour celles que l’expéditeur envoie.

S:220 serveur ESMTP Postfix 
E:HELO localdomain  
S:250 serveur 
E:MAIL FROM:<expediteur@expediteur.fr>  
S:250 Ok 
E:RCPT TO:<destinataire@destination.fr>  
S:250 Ok 
E:DATA  
S:354 End data with <CR><LF>.<CR><LF> 
E:Subject: Un test 
E:Un test de plusieurs lignes 
E:avec un 
E:.. 
E:point 
E:.  
S:250 Ok: queued as 349E618270 
E:QUIT 
S:221 Bye 

Chaque ligne commençant par un numéro vient du serveur et le numéro est le code indiquant la signification de la réponse. Le code 220 nous informe que le service est prêt. Le code 250 que nous retrouvons souvent quand tout va pour le mieux signifie que l’action a été...

Récupérer un message électronique sur un serveur POP3

Problème

Vous souhaitez collecter le courrier électronique en vous connectant à un serveur POP3.

Solution

Utilisez la recette "Créer un client TCP/IP" du chapitre "Réseau" et communiquer avec le serveur POP3, habituellement via le port 110, selon le protocole défini par la RFC 1939. Obtenez le nombre de messages avec la commande STAT, puis chaque message avec RETR n, et de façon optionnelle, supprimez les messages avec DELE n.

Discussion

Le protocole POP3 est au format texte et fonctionne sur le schéma suivant : une commande est d’abord envoyée au serveur, qui répond une ligne commençant par la lettre + suivi de l’état, qui est de préférence OK, et d’un texte éventuel. Puis viennent, si la commande le nécessite, des données sous forme de lignes consécutives, dont les derniers caractères sont \r\n.\r\n. Le programme suivant va donc demander le nombre de messages disponibles sur le serveur, et, pour chaque message, lire son contenu grâce à la commande RETR n. Nous choisissons de les stocker dans un fichier en respectant le format maildir (voir la recette "Rechercher et lire un courrier dans une boîte au format maildir" à ce sujet).

Voici une connexion sur un serveur POP3 avec telnet pour illustrer une connexion permettant de collecter les messages électroniques. Nous avons ajouté en début de ligne un E: pour noter les lignes que nous envoyons au serveur, et un S: pour les réponses du serveur, sachant que les quatre premières sont celles de la connexion et sont propres à telnet.


$ telnet pop.serveur.fr 110 
Trying 123.45.32.111... 
Connected to pop.serveur.fr. 
Escape character is 'ˆ]'.  
S:+OK <12345.1122334455@pop.serveur.fr> 
E:USER utilisateur 
S:+OK  
E:PASS ***********  
S:+OK  
E:LIST 
S:+OK  
S:1 1461 
S:2 1457 
S:. 
E:STAT 
S:+OK 2 2918 
E:RETR 1  
S:+OK 1461 octets 
S:Return-Path: <utilisateur@serveur.fr> 
... (nous ne gardons ici que les première et dernière lignes 
              du contenu du message)  
S:Dernière ligne du message 
S:. 
E:QUIT  
S:+OK...

Récupérer un message électronique sur un serveur IMAP

Problème

Vous souhaitez collecter le courrier électronique en vous connectant à un serveur IMAP.

Solution

Créez un client TCP en utilisant la recette "Créer un client TCP/IP" (chapitre "Réseau") et connectez-vous au serveur IMAP sur le port 143. Communiquez avec ce serveur en respectant le protocole IMAP4rev1 défini dans la RFC 2060. Obtenez le nombre de messages avec la commande SELECT inbox, puis chaque message avec FETCH n (RFC822.HEADER RFC822.BODY).

Discussion

Avant de rentrer dans le vif du sujet, nous allons redéfinir le problème. En effet, contrairement au protocole POP3, qui est par ailleurs utilisé dans de nombreux logiciels de messagerie pour collecter les messages sur un serveur distant afin de les consulter en local, le protocole IMAP permet une gestion fine des messages. Il est par exemple possible d’extraire le sujet, la date ou l’expéditeur de chaque message. Nous pouvons ainsi choisir de ne collecter que certains courriers, voire, comme le font la plupart des logiciels de messagerie avec interface web (les webmails), déléguer la gestion de la boîte aux lettres au serveur IMAP. Récupérer le courrier électronique sur un serveur IMAP de la même manière que sur un serveur POP3 ne présente donc aucun intérêt. Considérez l’utilisation d’un serveur POP3 et référez-vous à la recette précédente si tel est votre problème.

Notre problème va consister, avec un serveur IMAP, à non pas collecter le courrier électronique, mais à ne télécharger qu’un seul message, celui qui nous intéresse. Pour l’identifier, nous verrons comment le retrouver en affichant la liste des messages, c’est-à-dire leur identifiant, des drapeaux (si le message a été lu, si vous y avez répondu...), leur expéditeur, leur date de réception et leur sujet.

Le protocole IMAP, défini dans la RFC 2060, est un protocole de communication orienté texte, comme les protocoles SMTP et POP3 des recettes précédentes. Cependant, il se distingue par deux différences majeures de ceux-ci. Chaque ligne de commande doit débuter par un identifiant...

Rechercher et lire un courrier dans une boîte au format maildir

Problème

Vous voulez lire votre boîte aux lettres qui est au format maildir et en extraire des messages électroniques.

Solution

Parcourez les répertoires de votre boîte aux lettres et lisez les fichiers (sauf ceux qui commencent par un point) : il contiennent chacun un message. Ceux-ci respectent la définition de la RFC 2822.

Discussion

Le format maildir, comme le format mbox, permet de stocker un message électronique dans un fichier. Mais contrairement au format mbox, chaque message est contenu dans un fichier et chaque fichier ne contient qu’un seul message. Cette particularité résout un problème du format mbox en n’obligeant plus le verrouillage du fichier dans lequel un message doit être écrit. En effet, lorsqu’un message est écrit, un utilisateur peut en même temps supprimer un autre message, ce qui pose un problème d’accès concurrent au système de fichiers. De plus, le verrouillage d’une boîte mbox s’effectuant en verrouillant le fichier, cela est inefficace lorsque le fichier est accessible via NFS, ce protocole ne supportant pas le verrouillage des fichiers.

Le format maildir, avec un fichier par message, n’a pas ce problème car un nouveau message peut être déposé dans un nouveau fichier dans le répertoire new/ pendant qu’un , contenu dans un fichier dans le répertoire cur/, est supprimé.

Les fichiers d’une boîte maildir sont contenus dans trois répertoires, cur/, new/ et tmp/. Ce dernier est utilisé lors du dépôt d’un message. Celui-ci est écrit dans un nouveau fichier avec un nom unique, avant d’être déplacé dans le répertoire new/. Il est donc normal d’avoir un répertoire tmp/ vide la plupart du temps. De plus, nous voyons ici que les messages non ouverts se trouvent dans le répertoire new/. Dès qu’un message est ouvert (en général pour être lu), il est déplacé dans le répertoire cur/. Avoir un fichier dans le répertoire cur/ ne signifie pas que le message a été lu, mais seulement qu’il a été ouvert.

Un logiciel de messagerie ouvre généralement...

Rechercher et lire un courrier dans une boîte au format mbox

Problème

Vous voulez lire votre boîte aux lettres qui est au format mbox et en extraire des courriers électroniques.

Solution

Lisez le fichier texte qui constitue votre boîte aux lettres. Chaque courrier commence par une ligne From (’F’, ’r’, ’o’, ’m’ et un caractère d’espacement) suivie d’une adresse électronique et d’une date. Chaque courrier se termine par une ligne vide. Les lignes intermédiaires constituent le contenu du message, au format défini par la RFC 2822 (et RFC 822 auparavant). Extrayez les lignes intermédiaires qui constituent chaque message.

Discussion

Le format mbox est un ancien format de stockage des courriers électroniques qui place les messages les uns à la suite des autres, avec un en-tête commençant par From et une fin de message constituée par une ligne vide. Lorsque le corps du message contient une ligne commençant par From, elle est remplacée par >From. Si une ligne commence par >From, un second caractère > est ajouté en début de ligne. Et ainsi de suite. Par exemple, si nous lisons >>>From dans le fichier, cela signifie que le message contient >>From.

Voici un exemple qui lit un fichier mbox et affiche, pour chaque message, l’expéditeur, la date d’envoi et le sujet :


#include <stdio.h> ...