Client / serveur UDP

L'objectif de ce TP est d'écrire un client et un serveur simples fonctionnant en mode non connecté (UDP). Les exercices proposés sont détaillés après un résumé des fonctions à utiliser.

Résumé des fonctions

Fonctions système

Les fonctions utiles sont données ci-dessous.

Socket

Comme toutes les sockets, une socket UDP est créée par la fonction socket(2) qu'on utilisera de la manière suivante:

    udp_socket = socket(AF_INET, SOCK_DGRAM, 0);

Voir la page du manuel udp(7) pour plus de détails.

Le client

Hormis la création de la socket, il n'y a rien à faire pour le client, si ce n'est d'initialiser un objet de type struct sockaddr_in avec l'adresse et le numéro de port du serveur pour pouvoir l'utiliser ensuite lors de l'envoi de messages.

Se référer au TP4 pour l'initialisation d'un objet de type struct sockaddr_in.

Le serveur

Comme en TCP (cf. TP4), la socket doit être liée à l'adresse et au numéro de port souhaité avec la fonction bind(2). Le serveur peut ensuite se mettre immédiatement en attente de messages.

Envoi et réception de messages

Envoi

Pour envoyer un message UDP, on utilisera la fonction:

    ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                   const struct sockaddr *dest_addr, socklen_t addrlen);

  • sockfd est la socket à utiliser
  • buf est un pointeur vers les données à envoyer
  • len est la taille des données à envoyer
  • flags permet de spécifier des options (en pratique, on utilisera la valeur 0)
  • dest_addr est l'adresse de destination (en réalité un pointeur vers un objet de type struct sockaddr_in)
  • addrlen est la taille de la structure pointée par dest_addr

La valeur de retour est le nombre d'octets effectivement envoyés, ou -1 en cas d'erreur.

Réception

Pour recevoir un message UDP, on utilisera la fonction suivante. L'appel de la fonction est bloquant jusqu'à la réception d'un message (ou erreur).

    ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                     struct sockaddr *src_addr, socklen_t *addrlen);

  • sockfd est la socket à utiliser
  • buf est l'adresse où écrire les données reçues
  • len est la taille maximale des données à recevoir
  • flags permet de spécifier des options (en pratique, on utilisera la valeur 0)
  • src_addr, si différent de NULL, sera renseigné par l'adresse et le port source du message (adresse du client)
  • addrlen donne la taille de la structure pointée par src_addr

La valeur de retour est le nombre d'octets reçus, ou -1 en cas d'erreur.

Les paramètres src_addr et addrlen peuvent être réutilisés ensuite, par exemple pour envoyer un message en retour avec sendto().

Remarque

Il n'est pas utile, en UDP, d'envoyer la taille du message.  Les messages ne peuvent pas être découpés, ils doivent obligatoirement être reçus en une seule fois.  En pratique, il suffit de fourninr un tampon (buffer) assez grand à la fonction recvfrom().

Pour la définition de « assez grand », on peut remarquer qu'un message UDP est encapsulé dans un paquetI IP, et que la taille maximale d'un paquet IP est de 65535 octets. Un message UDP sera donc toujours plus petit que 64Ko.

Exercices

L'exercice est d'écrire un client et un serveur communicant par UDP. Dans la version finale, le client envoie un message au serveur, qui retourne une réponse. Chacune des partie affiche les messages reçus sur sa sortie standard.

Pour tester les codes, on pourra utiliser soit l'implémentation de référence fournie, soit l'outil netcat (nc) avec l'option « -u » pour fonctionner en mode UDP.

Le client

Dans une première version, le client doit lire les données à envoyer sur son entrée standard, et les envoyer en un seul message UDP au serveur.  L'adresse du serveur et le numéro de port à utiliser doivent pouvoir être donnés sur la ligne de commande.

Le client sera ensuite complété pour attendre une réponse du serveur. Cette réponse est affichée sur la sortie standard dès sa réception.

Modifier enfin le client pour qu'il se termine si la réponse du serveur n'arrive pas après une certaine durée prédéterminée (une ou deux secondes par exemple). Indication : installer un gestionnaire pour le signal SIGALRM, et utiliser la fonction alarm().

Le serveur

Dans une première version, le serveur doit attendre des messages UDP sur un numéro de port donné. Le numéro de port doit pouvoir être donné sur la ligne de commande. Pour chaque message reçu, le serveur doit afficher son contenu sur sa sortie standard. Dès qu'il a traité un message, le serveur se met en attente d'un message suivant. Compléter l'affichage du message avec les informations du client (adresse et numéro de port source).

Le serveur sera ensuite complété pour envoyer une réponse au client. La réponse est envoyée au client sur le numéro de port qu'il a utilisé pour le message initial. Le contenu de la réponse peut par exemple être une phrase de la forme "lu un message de XXX octets" où XXX est la taille du message reçu.

Questions subsidiaires

Quelle est la taille du plus petit message qu'il est possible de transmettre ?

Y a-t-il une limite maximale à la taille des messages ? Si oui, comment peut-on la déterminer ? Quelle est alors cette limite ?