Man page - select_tut(2)

Packages contains this manual

Available languages:

en fr ja ru

Manual

SELECT_TUT

NOM
BIBLIOTHÈQUE
SYNOPSIS
DESCRIPTION
Combinaison d’évĂ©nements de signaux et de donnĂ©es
Pratique
RĂšgles de select
VALEUR RENVOYÉE
NOTES
EXEMPLES
VOIR AUSSI
TRADUCTION

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 .