4. Programme serveur UDP : listener

4.1. Utilisation des sockets avec le serveur UDP

Au niveau du serveur ou listener, l'objectif est aussi d'ouvrir une nouvelle prise réseau ou socket ; ce qui revient à nouveau à ouvrir un canal de communication réseau avec la fonction socket(). On retrouve la même séquence que dans le programme client précédent avec l'utilisation de la fonction getaddrinfo(). À la différence du programme précédent, on oriente l'ouverture de la prise réseau sur les interfaces réseau locales au système.

hints.ai_family = AF_INET;       // IPv4
hints.ai_socktype = SOCK_DGRAM;  // UDP
hints.ai_flags = AI_PASSIVE;     // Toutes les adresses disponibles

if ((status = getaddrinfo(NULL, listenPort, &hints, &servinfo)) != 0) {
  fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(status));
  return 1;
  }

L'appel à la fonction socket() ne présente aucune différence avec le programme client puisque tout le paramétrage à déjà été effectué. Les informations utiles sont contenues dans les champs de l'enregistrement pointé par servinfo.

if ((listenSocket = socket(servinfo->ai_family, servinfo->ai_socktype,
                           servinfo->ai_protocol)) == -1) {
  perror("socket:");
  exit(EXIT_FAILURE);
  }

Une fois la prise réseau en place et en état d'écoute, le programme attend les datagrammes provenant des clients.

memset(msg, 0, sizeof msg);
if (recvfrom(listenSocket, msg, sizeof msg, 0,
             (struct sockaddr *) &clientAddress,
             &clientAddressLength) == -1) {
  perror("recvfrom:");
  close(listenSocket);
  exit(EXIT_FAILURE);
  }

Les paramètres msg et sizeof msg définissent la chaîne de caractères dans laquelle le datagramme reçu est stocké ainsi que le nombre maximum de caractères qui peuvent être stockés.

Enfin, les émissions de datagramme du serveur vers le client utilisent exactement les mêmes appels à la fonction sendto que les émissions du client vers le serveur.

4.2. Code source complet

Code du programme udp-listener.c :

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#define MAX_PORT 5
#define PORT_ARRAY_SIZE (MAX_PORT+1)
#define MAX_MSG 80
#define MSG_ARRAY_SIZE (MAX_MSG+1)
// Utilisation d'une constante x dans la définition
// du format de saisie
#define str(x) # x
#define xstr(x) str(x)

int main()
{
  int listenSocket, status, i;
  unsigned short int msgLength;
  struct addrinfo hints, *servinfo;
  struct sockaddr_in clientAddress;
  socklen_t clientAddressLength = sizeof clientAddress;
  char msg[MSG_ARRAY_SIZE], listenPort[PORT_ARRAY_SIZE];

  memset(listenPort, 0, sizeof listenPort);  // Mise à zéro du tampon
  puts("Entrez le numéro de port utilisé en écoute (entre 1500 et 65000) : ");
  scanf("%"xstr(MAX_PORT)"s", listenPort);

  memset(&hints, 0, sizeof hints);
  hints.ai_family = AF_INET;       // IPv4
  hints.ai_socktype = SOCK_DGRAM;  // UDP
  hints.ai_flags = AI_PASSIVE;     // Toutes les adresses disponibles

  if ((status = getaddrinfo(NULL, listenPort, &hints, &servinfo)) != 0) {
    fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(status));
    exit(EXIT_FAILURE);
  }

  if ((listenSocket = socket(servinfo->ai_family, servinfo->ai_socktype,
                             servinfo->ai_protocol)) == -1) {
    perror("socket:");
    exit(EXIT_FAILURE);
  }

  if (bind(listenSocket, servinfo->ai_addr, servinfo->ai_addrlen) == -1) {
    close(listenSocket);
    perror("bind:");
    exit(EXIT_FAILURE);
  }

  // Libération de la mémoire occupée par les enregistrements
  freeaddrinfo(servinfo);

  printf("Attente de requête sur le port %s\n", listenPort);

  while (1) {

    // Mise à zéro du tampon de façon à connaître le délimiteur
    // de fin de chaîne.
    memset(msg, 0, sizeof msg);
    if (recvfrom(listenSocket, msg, sizeof msg, 0,
                 (struct sockaddr *) &clientAddress,
                 &clientAddressLength) == -1) {
      perror("recvfrom:");
      close(listenSocket);
      exit(EXIT_FAILURE);
    }

    msgLength = strlen(msg);
    if (msgLength > 0) {
      // Affichage de l'adresse IP du client.
      printf(">>  depuis %s", inet_ntoa(clientAddress.sin_addr));

      // Affichage du numéro de port du client.
      printf(":%hu\n", ntohs(clientAddress.sin_port));

      // Affichage de la ligne reçue
      printf("  Message reçu : %s\n", msg);

      // Conversion de cette ligne en majuscules.
      for (i = 0; i < msgLength; i++)
        msg[i] = toupper(msg[i]);

      // Renvoi de la ligne convertie au client.
      if (sendto(listenSocket, msg, msgLength, 0,
                 (struct sockaddr *) &clientAddress,
                 clientAddressLength) == -1) {
        perror("sendto:");
        close(listenSocket);
        exit(EXIT_FAILURE);
      }
    }
  }
}