Provided by: manpages-fr-dev_4.15.0-9_all bug

NOM

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

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 fils. 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) {
                   /* Gérer les erreurs */
               }

               if (got_SIGCHLD) {
                   got_SIGCHLD = 0;

                   /* Gérer les événements signalés ici; e.g., wait() pour
                      que tous les fils 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 <stdlib.h>
       #include <stdio.h>
       #include <unistd.h>
       #include <sys/select.h>
       #include <string.h>
       #include <signal.h>
       #include <sys/socket.h>
       #include <netinet/in.h>
       #include <arpa/inet.h>
       #include <errno.h>

       static int forward_port;

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

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

           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)
       {
           struct sockaddr_in addr;
           int cfd;

           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 fd1 = -1, fd2 = -1;
           char buf1[BUF_SIZE], buf2[BUF_SIZE];
           int buf1_avail = 0, buf1_written = 0;
           int buf2_avail = 0, buf2_written = 0;

           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 (;;) {
               int ready, nfds = 0;
               ssize_t nbytes;
               fd_set readfds, writefds, exceptfds;

               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)

COLOPHON

       Cette  page  fait partie de la publication 5.13 du projet man-pages Linux. Une description
       du projet et des instructions pour signaler des anomalies et la dernière version de  cette
       page peuvent être trouvées à l'adresse https://www.kernel.org/doc/man-pages/.

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  ⟨https://www.gnu.org/licenses/gpl-3.0.html⟩  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⟩.