.\" -*- coding: UTF-8 -*- .\" This manpage is copyright (C) 2001 Paul Sheer. .\" .\" SPDX-License-Identifier: Linux-man-pages-copyleft .\" .\" very minor changes, aeb .\" .\" Modified 5 June 2002, Michael Kerrisk .\" 2006-05-13, mtk, removed much material that is redundant with select.2 .\" various other changes .\" 2008-01-26, mtk, substantial changes and rewrites .\" .\"******************************************************************* .\" .\" This file was generated with po4a. Translate the source file. .\" .\"******************************************************************* .TH SELECT_TUT 2 "3 mai 2023" "Pages du manuel de Linux 6.05.01" .SH NOM select, pselect \- Multiplexage d'entrées\-sorties synchrones .SH BIBLIOTHÈQUE Bibliothèque C standard (\fIlibc\fP, \fI\-lc\fP) .SH SYNOPSIS Voir \fBselect\fP(2) .SH DESCRIPTION Les appels système \fBselect\fP() et \fBpselect\fP()) 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. .PP .\" 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 \fBselect\fP() et de \fBpselect\fP(), voir \fBselect\fP(2). .SS "Combinaison d'événements de signaux et de données" ***\fBpselect\fP() 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 \fBselect\fP() (ou \fBpselect\fP()) avec \fIerrno\fP positionnée à \fBEINTR\fP. Ce comportement est essentiel afin que les signaux puissent être traités dans la boucle principale du programme, sinon \fBselect\fP() bloquerait indéfiniment. .PP 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 à \fBselect\fP()\ ? La réponse est que \fBselect\fP() bloquerait indéfiniment, même si un signal était en fait en attente. Cette "race condition" est résolue par l'appel \fBpselect\fP(). 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 à \fBpselect\fP(). 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 \fBSIGCHLD\fP en utilisant \fBsigprocmask\fP(2). Notre appel \fBpselect\fP() débloquerait \fBSIGCHLD\fP en utilisant le masque de signaux vide. Le programme ressemblerait à ceci\ : .PP .EX 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 */ } } .EE .SS Pratique Quelle est donc la finalité de \fBselect\fP()\ ? Ne peut on pas simplement lire et écrire dans les descripteurs chaque fois qu'on le souhaite\ ? L'objet de \fBselect\fP() 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 \fBread\fP(2) et \fBwrite\fP(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. \fBselect\fP() gère efficacement cette situation. .SS "Règles de select" De nombreuses personnes qui essaient d'utiliser \fBselect\fP() 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 \fBselect\fP(), aussi, voici une liste de points essentiels à contrôler lors de l'utilisation de \fBselect\fP(). .TP 4 1. Vous devriez toujours essayer d'utiliser \fBselect\fP() 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. .TP 2. La valeur \fInfds\fP doit être calculée correctement pour des raisons d'efficacité comme expliqué plus haut. .TP 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 à \fBselect\fP(), et de réagir de façon adéquate. Voir la règle suivante. .TP 4. Après le retour de \fBselect\fP(), tous les descripteurs de fichier dans tous les ensembles devraient être testés pour savoir s'ils sont prêts. .TP 5. Les fonctions \fBread\fP(2), \fBrecv\fP(2), \fBwrite\fP(2) et \fBsend\fP(2) ne lisent ou n'écrivent \fIpas\fP 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. .TP 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. .TP 7. .\" Nonetheless, you should still cope with these errors for completeness. Les appels à \fBread\fP(2), \fBrecv\fP(2), \fBwrite\fP(2), \fBsend\fP(2) et \fBselect\fP() peuvent échouer avec l'erreur \fBEINTR\fP et les appels à \fBread\fP(2), \fBrecv\fP(2), \fBwrite\fP(2), \fBwrite\fP(2) et \fBsend\fP(2) peuvent échouer avec \fIerrno\fP positionné sur \fBEAGAIN\fP (\fBEWOULDBLOCK\fP). 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 \fBEINTR\fP. Si votre programme n'a pas configuré les entrées\-sorties en mode non bloquant, vous n'obtiendrez pas de \fBEAGAIN\fP. .TP 8. N'appelez jamais \fBread\fP(2), \fBrecv\fP(2), \fBwrite\fP(2) ou \fBsend\fP(2) avec un tampon de taille nulle. .TP 9. Si les fonctions \fBread\fP(2), \fBrecv\fP(2), \fBwrite\fP(2) et \fBsend\fP(2) échouent avec une erreur autre que celles indiquées en \fB7.\fP, ou si l'une des fonctions d'entrée renvoie \fB0\fP, indiquant une fin de fichier, vous \fIne\fP devriez \fIpas\fP utiliser ce descripteur à nouveau pour un appel à \fBselect\fP(). Dans l'exemple ci\-dessous, le descripteur est immédiatement fermé et ensuite est positionné à \fB\-1\fP afin qu'il ne soit pas inclus dans un ensemble. .TP 10. La valeur de timeout doit être initialisée à chaque nouvel appel à \fBselect\fP(), puisque des systèmes d'exploitation modifient la structure. Cependant, \fBpselect\fP() ne modifie pas sa structure de timeout. .TP 11. .\" "I have heard" does not fill me with confidence, and doesn't .\" belong in a man page, so I've commented this point out. .\" .TP .\" 11. .\" I have heard that the Windows socket layer does not cope with OOB data .\" properly. .\" It also does not cope with .\" .BR select () .\" calls when no file descriptors are set at all. .\" Having no file descriptors set is a useful .\" way to sleep the process with subsecond precision by using the timeout. .\" (See further on.) Comme \fBselect\fP() 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. .SH "VALEUR RENVOYÉE" Voir \fBselect\fP(2). .SH NOTES De façon générale, tous les systèmes d'exploitation qui gèrent les sockets proposent également \fBselect\fP(). \fBselect\fP() 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. .PP L'appel système \fBpoll\fP(2) a les mêmes fonctionnalités que \fBselect\fP(), 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 \fBselect\fP(). .PP L'API \fBepoll\fP(7) spécifique à Linux fournit une interface plus efficace que \fBselect\fP(2) et \fBpoll\fP(2) lorsque l'on surveille un grand nombre de descripteurs de fichier. .SH EXEMPLES Voici un exemple qui montre mieux l'utilité réelle de \fBselect\fP(). Le code ci\-dessous consiste en un programme de «\ TCP forwarding\ » qui redirige un port TCP vers un autre. .PP .\" SRC BEGIN (select.c) .EX #include #include #include #include #include #include #include #include #include #include \& 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\en", 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\en"); 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 { \e if (fd1 >= 0) { \e shutdown(fd1, SHUT_RDWR); \e close(fd1); \e fd1 = \-1; \e } \e } while (0) \& #define SHUT_FD2 do { \e if (fd2 >= 0) { \e shutdown(fd2, SHUT_RDWR); \e close(fd2); \e fd2 = \-1; \e } \e } 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\en\etfwd " " \en"); 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\en", 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); } .EE .\" SRC END .PP 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 \fBtelnet\fP. 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 \fBfork\fP(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 \fBfcntl\fP(2). Cela pose également problème puisque ça vous force à utiliser des timeouts inefficaces. .PP 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 \[em] un pour chaque connexion. Pour l'instant, de nouvelles connexions provoquent l'abandon de la connexion courante. .SH "VOIR AUSSI" \fBaccept\fP(2), \fBconnect\fP(2), \fBpoll\fP(2), \fBread\fP(2), \fBrecv\fP(2), \fBselect\fP(2), \fBsend\fP(2), \fBsigprocmask\fP(2), \fBwrite\fP(2), \fBepoll\fP(7) .\" .SH AUTHORS .\" This man page was written by Paul Sheer. .PP .SH TRADUCTION La traduction française de cette page de manuel a été créée par Christophe Blaess , Stéphan Rafin , Thierry Vignaud , François Micaux, Alain Portal , Jean-Philippe Guérard , Jean-Luc Coulon (f5ibh) , Julien Cristau , Thomas Huriaux , Nicolas François , Florentin Duneau , Simon Paillard , Denis Barbier , David Prévot , Cédric Boutillier , Frédéric Hantrais et Jean-Philippe MENGUAL . .PP Cette traduction est une documentation libre ; veuillez vous reporter à la .UR https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3 .UE concernant les conditions de copie et de distribution. Il n'y a aucune RESPONSABILITÉ LÉGALE. .PP Si vous découvrez un bogue dans la traduction de cette page de manuel, veuillez envoyer un message à .MT debian-l10n-french@lists.debian.org .ME .