Provided by: manpages-fr-dev_4.26.0-1_all bug

NOM

       select, pselect - Multiplexage d'entrées-sorties synchrones

BIBLIOTHÈQUE

       Bibliothèque C standard (libc, -lc)

SYNOPSIS

       Voir select(2)

DESCRIPTION

       Les  appels  système  select()  et  pselect())  sont  utilisés  pour  superviser  efficacement  plusieurs
       descripteurs de fichiers pour vérifier si l'un d'entre eux est ou devient « prêt » ; c'est-à-dire  savoir
       si  des entrées-sorties deviennent possibles ou si une « condition exceptionnelle » est survenue sur l'un
       des descripteurs.

       Cette page fournit des informations de contexte et des tutoriels sur l'utilisation de ces appels système.
       Pour des détails sur les paramètres et la sémantique de select() et de pselect(), voir select(2).

   Combinaison d'événements de signaux et de données
       ***pselect() est utile si vous attendez un signal ou qu'un/des descripteur(s) de fichier deviennent prêts
       pour des entrées-sorties. Les programmes qui reçoivent des signaux utilisent généralement le gestionnaire
       de signal uniquement pour lever un drapeau global. Le drapeau global indique que  l'événement  doit  être
       traité  dans  la  boucle  principale  du  programme.  Un  signal provoque l'arrêt de l'appel select() (ou
       pselect()) avec errno positionnée à EINTR. Ce comportement est essentiel afin que  les  signaux  puissent
       être traités dans la boucle principale du programme, sinon select() bloquerait indéfiniment.

       Ceci étant, la boucle principale implante quelque part une condition vérifiant le drapeau global, et l'on
       doit  donc  se demander : que se passe-t-il si un signal est levé après la condition mais avant l'appel à
       select() ? La réponse est que select() bloquerait indéfiniment, même  si  un  signal  était  en  fait  en
       attente.  Cette  "race  condition" est résolue par l'appel pselect(). Cet appel peut être utilisé afin de
       définir le masque des signaux qui sont censés n'être reçus que durant l'appel à pselect().  Par  exemple,
       supposons  que  l'événement en question est la fin d'un processus enfant. Avant le démarrage de la boucle
       principale, nous bloquerions SIGCHLD en utilisant  sigprocmask(2).  Notre  appel  pselect()  débloquerait
       SIGCHLD en utilisant le masque de signaux vide. Le programme ressemblerait à ceci :

       static volatile sig_atomic_t got_SIGCHLD = 0;

       static void
       child_sig_handler(int sig)
       {
           got_SIGCHLD = 1;
       }

       int
       main(int argc, char *argv[])
       {
           sigset_t sigmask, empty_mask;
           struct sigaction sa;
           fd_set readfds, writefds, exceptfds;
           int r;

           sigemptyset(&sigmask);
           sigaddset(&sigmask, SIGCHLD);
           if (sigprocmask(SIG_BLOCK, &sigmask, NULL) == -1) {
               perror("sigprocmask");
               exit(EXIT_FAILURE);
           }

           sa.sa_flags = 0;
           sa.sa_handler = child_sig_handler;
           sigemptyset(&sa.sa_mask);
           if (sigaction(SIGCHLD, &sa, NULL) == -1) {
               perror("sigaction");
               exit(EXIT_FAILURE);
           }

           sigemptyset(&empty_mask);

           for (;;) {          /* main loop */
               /* Initialiser readfds, writefds et exceptfds
                  avant l'appel à pselect(). (Code omis.) */

               r = pselect(nfds, &readfds, &writefds, &exceptfds,
                           NULL, &empty_mask);
               if (r == -1 && errno != EINTR) {
                   /* Handle error */
               }

               if (got_SIGCHLD) {
                   got_SIGCHLD = 0;

                   /* Gérer les événements signalés ici; e.g., wait() pour
                      que tous les enfants se terminent. (Code omis.) */
               }

               /* corps principal du programme */
           }
       }

   Pratique
       Quelle  est donc la finalité de select() ? Ne peut on pas simplement lire et écrire dans les descripteurs
       chaque fois qu'on le  souhaite ?  L'objet  de  select()  est  de  surveiller  de  multiples  descripteurs
       simultanément  et  d'endormir proprement le processus s'il n'y a pas d'activité. Les programmeurs UNIX se
       retrouvent souvent dans une situation dans laquelle ils doivent gérer des  entrées-sorties  provenant  de
       plus  d'un  descripteur  de  fichier et dans laquelle le flux de données est intermittent. Si vous deviez
       créer une séquence d'appels read(2) et write(2), vous vous retrouveriez potentiellement bloqué sur un  de
       vos appels attendant pour lire ou écrire des données à partir/vers un descripteur de fichier, alors qu'un
       autre  descripteur  de fichier est inutilisé bien qu'il soit prêt pour des entrées-sorties. select() gère
       efficacement cette situation.

   Règles de select
       De nombreuses  personnes  qui  essaient  d'utiliser  select()  obtiennent  un  comportement  difficile  à
       comprendre  et  produisent  des  résultats non portables ou des effets de bord. Par exemple, le programme
       ci-dessus est écrit avec précaution afin de ne bloquer nulle  part,  même  s'il  ne  positionne  pas  ses
       descripteurs  de  fichier  en  mode  non  bloquant.Il  est  facile  d'introduire des erreurs subtiles qui
       annuleraient l'avantage de l'utilisation de select(), aussi, voici  une  liste  de  points  essentiels  à
       contrôler lors de l'utilisation de select().

       1.  Vous devriez toujours essayer d'utiliser select() sans timeout. Votre programme ne devrait rien avoir
           à  faire  s'il  n'y  a pas de données disponibles. Le code dépendant de timeouts n'est en général pas
           portable et difficile à déboguer.

       2.  La valeur nfds doit être calculée correctement pour des  raisons  d'efficacité  comme  expliqué  plus
           haut.

       3.  Aucun  descripteur de fichier ne doit être ajouté à un quelconque ensemble si vous ne projetez pas de
           vérifier son état après un appel à select(), et de réagir de façon adéquate. Voir la règle suivante.

       4.  Après le retour de select(), tous les descripteurs de fichier dans tous les ensembles devraient  être
           testés pour savoir s'ils sont prêts.

       5.  Les fonctions read(2), recv(2), write(2) et send(2) ne lisent ou n'écrivent pas forcément la quantité
           totale  de  données spécifiée. Si elles lisent/écrivent la quantité totale, c'est parce que vous avez
           une faible charge de trafic et un flux rapide. Ce n'est pas toujours le cas. Vous  devriez  gérer  le
           cas où vos fonctions traitent seulement l'envoi ou la réception d'un unique octet.

       6.  Ne  lisez/n'écrivez  jamais  seulement quelques octets à la fois à moins que vous ne soyez absolument
           sûr de n'avoir qu'une faible quantité de données à traiter. Il est parfaitement inefficace de ne  pas
           lire/écrire  autant  de  données  que  vous pouvez en stocker à chaque fois. Les tampons de l'exemple
           ci-dessous font 1024 octets bien qu'ils aient facilement pu être rendus plus grands.

       7.  Les appels à read(2), recv(2), write(2), send(2) et select() peuvent échouer avec l'erreur  EINTR  et
           les  appels  à  read(2), recv(2), write(2), write(2) et send(2) peuvent échouer avec errno positionné
           sur EAGAIN (EWOULDBLOCK). Ces  résultats  doivent  être  correctement  gérés  (cela  n'est  pas  fait
           correctement  ci-dessus).  Si  votre  programme  n'est  pas  censé  recevoir de signal, alors, il est
           hautement  improbable  que  vous  obteniez  EINTR.  Si  votre  programme  n'a   pas   configuré   les
           entrées-sorties en mode non bloquant, vous n'obtiendrez pas de EAGAIN.

       8.  N'appelez jamais read(2), recv(2), write(2) ou send(2) avec un tampon de taille nulle.

       9.  Si  les  fonctions  read(2),  recv(2),  write(2) et send(2) échouent avec une erreur autre que celles
           indiquées en 7., ou si l'une des fonctions d'entrée renvoie 0, indiquant une fin de fichier, vous  ne
           devriez pas utiliser ce descripteur à nouveau pour un appel à select(). Dans l'exemple ci-dessous, le
           descripteur est immédiatement fermé et ensuite est positionné à -1 afin qu'il ne soit pas inclus dans
           un ensemble.

       10. La  valeur  de  timeout  doit être initialisée à chaque nouvel appel à select(), puisque des systèmes
           d'exploitation modifient la structure. Cependant, pselect() ne modifie pas sa structure de timeout.

       11. Comme select() modifie ses ensembles de descripteurs de fichiers, si l'appel est  effectué  dans  une
           boucle alors les ensembles doivent être réinitialisés avant chaque appel.

VALEUR RENVOYÉE

       Voir select(2).

NOTES

       De  façon générale, tous les systèmes d'exploitation qui gèrent les sockets proposent également select().
       select() peut être utilisé pour résoudre de façon portable et efficace  de  nombreux  problèmes  que  des
       programmeurs  naïfs  essaient de résoudre avec des threads, des forks, des IPC, des signaux, des mémoires
       partagées et d'autres méthodes peu élégantes.

       L'appel système poll(2) a les mêmes fonctionnalités que select(), tout en étant légèrement plus  efficace
       quand  il  doit  surveiller  des  ensembles  de  descripteurs creux. Il est disponible sur la plupart des
       systèmes de nos jours, mais était historiquement moins portable que select().

       L'API epoll(7) spécifique à Linux fournit une interface plus efficace que select(2)  et  poll(2)  lorsque
       l'on surveille un grand nombre de descripteurs de fichier.

EXEMPLES

       Voici  un  exemple  qui  montre  mieux  l'utilité  réelle  de select(). Le code ci-dessous consiste en un
       programme de « TCP forwarding » qui redirige un port TCP vers un autre.

       #include <arpa/inet.h>
       #include <errno.h>
       #include <netinet/in.h>
       #include <signal.h>
       #include <stdio.h>
       #include <stdlib.h>
       #include <string.h>
       #include <sys/select.h>
       #include <sys/socket.h>
       #include <sys/types.h>
       #include <unistd.h>

       static int forward_port;

       #undef max
       #define max(x, y) ((x) > (y) ? (x) : (y))

       static int
       listen_socket(int listen_port)
       {
           int                 lfd;
           int                 yes;
           struct sockaddr_in  addr;

           lfd = socket(AF_INET, SOCK_STREAM, 0);
           if (lfd == -1) {
               perror("socket");
               return -1;
           }

           yes = 1;
           if (setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR,
                          &yes, sizeof(yes)) == -1)
           {
               perror("setsockopt");
               close(lfd);
               return -1;
           }

           memset(&addr, 0, sizeof(addr));
           addr.sin_port = htons(listen_port);
           addr.sin_family = AF_INET;
           if (bind(lfd, (struct sockaddr *) &addr, sizeof(addr)) == -1) {
               perror("bind");
               close(lfd);
               return -1;
           }

           printf("on accepte les connexions sur le port %d\n", listen_port);
           listen(lfd, 10);
           return lfd;
       }

       static int
       connect_socket(int connect_port, char *address)
       {
           int                 cfd;
           struct sockaddr_in  addr;

           cfd = socket(AF_INET, SOCK_STREAM, 0);
           if (cfd == -1) {
               perror("socket");
               return -1;
           }

           memset(&addr, 0, sizeof(addr));
           addr.sin_port = htons(connect_port);
           addr.sin_family = AF_INET;

           if (!inet_aton(address, (struct in_addr *) &addr.sin_addr.s_addr)) {
               fprintf(stderr, "inet_aton(): mauvais format d'adresse IP\n");
               close(cfd);
               return -1;
           }

           if (connect(cfd, (struct sockaddr *) &addr, sizeof(addr)) == -1) {
               perror("connect()");
               shutdown(cfd, SHUT_RDWR);
               close(cfd);
               return -1;
           }
           return cfd;
       }

       #define SHUT_FD1 do {                                \
                            if (fd1 >= 0) {                 \
                                shutdown(fd1, SHUT_RDWR);   \
                                close(fd1);                 \
                                fd1 = -1;                   \
                            }                               \
                        } while (0)

       #define SHUT_FD2 do {                                \
                            if (fd2 >= 0) {                 \
                                shutdown(fd2, SHUT_RDWR);   \
                                close(fd2);                 \
                                fd2 = -1;                   \
                            }                               \
                        } while (0)

       #define BUF_SIZE 1024

       int
       main(int argc, char *argv[])
       {
           int      h;
           int      ready, nfds;
           int      fd1 = -1, fd2 = -1;
           int      buf1_avail = 0, buf1_written = 0;
           int      buf2_avail = 0, buf2_written = 0;
           char     buf1[BUF_SIZE], buf2[BUF_SIZE];
           fd_set   readfds, writefds, exceptfds;
           ssize_t  nbytes;

           if (argc != 4) {
               fprintf(stderr, "Utilisation\n\tfwd <listen-port> "
                       "<forward-to-port> <forward-to-ip-address>\n");
               exit(EXIT_FAILURE);
           }

           signal(SIGPIPE, SIG_IGN);

           forward_port = atoi(argv[2]);

           h = listen_socket(atoi(argv[1]));
           if (h == -1)
               exit(EXIT_FAILURE);

           for (;;) {
               nfds = 0;

               FD_ZERO(&readfds);
               FD_ZERO(&writefds);
               FD_ZERO(&exceptfds);
               FD_SET(h, &readfds);
               nfds = max(nfds, h);

               if (fd1 > 0 && buf1_avail < BUF_SIZE)
                   FD_SET(fd1, &readfds);
                   /* Note: nfds est mis à jour ci-dessous, lorsque fd1
                     est ajouté à exceptfds. */
               if (fd2 > 0 && buf2_avail < BUF_SIZE)
                   FD_SET(fd2, &readfds);

               if (fd1 > 0 && buf2_avail - buf2_written > 0)
                   FD_SET(fd1, &writefds);
               if (fd2 > 0 && buf1_avail - buf1_written > 0)
                   FD_SET(fd2, &writefds);

               if (fd1 > 0) {
                   FD_SET(fd1, &exceptfds);
                   nfds = max(nfds, fd1);
               }
               if (fd2 > 0) {
                   FD_SET(fd2, &exceptfds);
                   nfds = max(nfds, fd2);
               }

               ready = select(nfds + 1, &readfds, &writefds, &exceptfds, NULL);

               if (ready == -1 && errno == EINTR)
                   continue;

               if (ready == -1) {
                   perror("select()");
                   exit(EXIT_FAILURE);
               }

               if (FD_ISSET(h, &readfds)) {
                   socklen_t addrlen;
                   struct sockaddr_in client_addr;
                   int fd;

                   addrlen = sizeof(client_addr);
                   memset(&client_addr, 0, addrlen);
                   fd = accept(h, (struct sockaddr *) &client_addr, &addrlen);
                   if (fd == -1) {
                       perror("accept()");
                   } else {
                       SHUT_FD1;
                       SHUT_FD2;
                       buf1_avail = buf1_written = 0;
                       buf2_avail = buf2_written = 0;
                       fd1 = fd;
                       fd2 = connect_socket(forward_port, argv[3]);
                       if (fd2 == -1)
                           SHUT_FD1;
                       else
                           printf("connexion depuis %s\n",
                                  inet_ntoa(client_addr.sin_addr));

                       /* Passer les événements des anciens descripteurs de
                          fichier fermés. */

                       continue;
                   }
               }

               /* NB : lecture des données hors bande avant les lectures normales */

               if (fd1 > 0 && FD_ISSET(fd1, &exceptfds)) {
                   char c;

                   nbytes = recv(fd1, &c, 1, MSG_OOB);
                   if (nbytes < 1)
                       SHUT_FD1;
                   else
                       send(fd2, &c, 1, MSG_OOB);
               }
               if (fd2 > 0 && FD_ISSET(fd2, &exceptfds)) {
                   char c;

                   nbytes = recv(fd2, &c, 1, MSG_OOB);
                   if (nbytes < 1)
                       SHUT_FD2;
                   else
                       send(fd1, &c, 1, MSG_OOB);
               }
               if (fd1 > 0 && FD_ISSET(fd1, &readfds)) {
                   nbytes = read(fd1, buf1 + buf1_avail,
                                 BUF_SIZE - buf1_avail);
                   if (nbytes < 1)
                       SHUT_FD1;
                   else
                       buf1_avail += nbytes;
               }
               if (fd2 > 0 && FD_ISSET(fd2, &readfds)) {
                   nbytes = read(fd2, buf2 + buf2_avail,
                                 BUF_SIZE - buf2_avail);
                   if (nbytes < 1)
                       SHUT_FD2;
                   else
                       buf2_avail += nbytes;
               }
               if (fd1 > 0 && FD_ISSET(fd1, &writefds) && buf2_avail > 0) {
                   nbytes = write(fd1, buf2 + buf2_written,
                                  buf2_avail - buf2_written);
                   if (nbytes < 1)
                       SHUT_FD1;
                   else
                       buf2_written += nbytes;
               }
               if (fd2 > 0 && FD_ISSET(fd2, &writefds) && buf1_avail > 0) {
                   nbytes = write(fd2, buf1 + buf1_written,
                                  buf1_avail - buf1_written);
                   if (nbytes < 1)
                       SHUT_FD2;
                   else
                       buf1_written += nbytes;
               }

               /* Vérifier si l'écriture de données a rattrapé la lecture de données */

               if (buf1_written == buf1_avail)
                   buf1_written = buf1_avail = 0;
               if (buf2_written == buf2_avail)
                   buf2_written = buf2_avail = 0;

               /* une extrémité a fermé la connexion, continue
                  d'écrire vers l'autre extrémité jusqu'à ce
                  que ce soit vide */

               if (fd1 < 0 && buf1_avail - buf1_written == 0)
                   SHUT_FD2;
               if (fd2 < 0 && buf2_avail - buf2_written == 0)
                   SHUT_FD1;
           }
           exit(EXIT_SUCCESS);
       }

       Le programme ci-dessus redirige correctement la plupart des types de connexions TCP y compris les signaux
       de données hors bande OOB transmis par les serveurs telnet. Il gère  le  problème  épineux  des  flux  de
       données  bidirectionnels  simultanés.  Vous  pourriez  penser qu'il est plus efficace d'utiliser un appel
       fork(2) et de dédier une tâche à chaque flux. Cela devient alors plus délicat que vous ne l'imaginez. Une
       autre idée est de configurer les entrées-sorties comme non bloquantes en utilisant  fcntl(2).  Cela  pose
       également problème puisque ça vous force à utiliser des timeouts inefficaces.

       Le  programme  ne gère pas plus d'une connexion à la fois bien qu'il soit aisément extensible à une telle
       fonctionnalité en utilisant une liste chaînée de tampons — un pour chaque connexion. Pour  l'instant,  de
       nouvelles connexions provoquent l'abandon de la connexion courante.

VOIR AUSSI

       accept(2), connect(2), poll(2), read(2), recv(2), select(2), send(2), sigprocmask(2), write(2), epoll(7)

TRADUCTION

       La   traduction   française   de   cette   page   de   manuel   a   été   créée   par  Christophe  Blaess
       <https://www.blaess.fr/christophe/>,   Stéphan   Rafin   <stephan.rafin@laposte.net>,   Thierry   Vignaud
       <tvignaud@mandriva.com>,  François  Micaux,  Alain Portal <aportal@univ-montp2.fr>, Jean-Philippe Guérard
       <fevrier@tigreraye.org>,   Jean-Luc   Coulon   (f5ibh)   <jean-luc.coulon@wanadoo.fr>,   Julien   Cristau
       <jcristau@debian.org>,      Thomas      Huriaux      <thomas.huriaux@gmail.com>,     Nicolas     François
       <nicolas.francois@centraliens.net>,    Florentin    Duneau    <fduneau@gmail.com>,     Simon     Paillard
       <simon.paillard@resel.enst-bretagne.fr>,     Denis    Barbier    <barbier@debian.org>,    David    Prévot
       <david@tilapin.org>,    Cédric    Boutillier     <cedric.boutillier@gmail.com>,     Frédéric     Hantrais
       <fhantrais@gmail.com> et Jean-Philippe MENGUAL <jpmengual@debian.org>

       Cette  traduction  est  une  documentation libre ; veuillez vous reporter à la GNU General Public License
       version 3 concernant les conditions de copie et de distribution. Il n'y a aucune RESPONSABILITÉ LÉGALE.

       Si vous découvrez un bogue dans la traduction de cette page de manuel,  veuillez  envoyer  un  message  à
       debian-l10n-french@lists.debian.org.

Pages du manuel de Linux 6.9.1                    15 juin 2024                                     SELECT_TUT(2)