select()
: attente de données Il est assez courant de devoir attendre des données en provenance
de plusieurs sources. On utilise pour cela la fonction select()
qui
permet de surveiller plusieurs descripteurs simultanément.
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int n, fd_set *readfds,
fd_set *writefds,
fd_set *exceptfds,
struct timeval *timeout);
FD_CLR(int fd, fd_set *set);
FD_ISSET(int fd, fd_set *set);
FD_SET(int fd, fd_set *set);
FD_ZERO(fd_set *set);
Cette fonction attend que des données soient prêtes à être lues sur un
des descripteurs de l'ensemble readfs
, ou que l'un des
descripteurs de writefds
soit prêt à recevoir des écritures,
que des exceptions se produisent (exceptfds
), ou encore que
le temps d'attente timeout
soit épuisé.
Lorsque
select()
se termine, readfds
, writefds
et
exceptfds
contiennent les descripteurs qui ont changé d'état.
select()
retourne le nombre de descripteurs qui ont changé
d'état, ou -1 en cas de problème.
L'entier n
doit être
supérieur (strictement) au plus grand des descripteurs contenus dans
les 3 ensembles (c'est en fait le nombre de bits significatifs du
masque binaire qui représente les ensembles). On peut utiliser la
constante FD_SETSIZE
.
Les pointeurs sur les ensembles (ou
le délai) peuvent être NULL
, ils représentent alors des
ensembles vides (ou une absence de limite de temps).
Les macros FD_CLR, FD_ISSET, FD_SET, FD_ZERO
permettent de
manipuler les ensembles de descripteurs.
Exemple :
1 /* mix.c */
2 /*
3 affiche les données qui proviennent de 2 fifos
4 usage: mix f1 f2
5 */
6 #include <sys/time.h>
7 #include <sys/types.h>
8 #include <sys/stat.h>
9 #include <unistd.h>
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <fcntl.h>
13 #define TAILLE_TAMPON 128
14 /*************************************************
15 Mixe les données en provenance de deux
16 descripteurs
17 **************************************************/
18 void mixer(int fd1, int fd2, int sortie)
19 {
20 fd_set ouverts; /* les descripteurs ouverts */
21 int nbouverts;
22 FD_ZERO(&ouverts);
23 FD_SET(fd1, &ouverts);
24 FD_SET(fd2, &ouverts);
25 nbouverts = 2;
26 while (nbouverts > 0) { /* tant qu'il reste des
27 descripteurs ouverts.... */
28 fd_set prets;
29 char tampon[TAILLE_TAMPON];
30 /* on attend qu'un descripteur soit prêt ... */
31 prets = ouverts;
32 if (select(FD_SETSIZE, &prets, NULL, NULL, NULL) < 0) {
33 perror("select");
34 exit(EXIT_FAILURE);
35 }
36 if (FD_ISSET(fd1, &prets)) { /* si fd1 est prêt... */
37 int n = read(fd1, tampon, TAILLE_TAMPON);
38 if (n >= 0) {
39 /* on copie ce qu'on a lu */
40 write(sortie, tampon, n);
41 } else {
42 /* fin de fd1 : on l'enlève */
43 close(fd1);
44 nbouverts--;
45 FD_CLR(fd1, &ouverts);
46 }
47 };
48 if (FD_ISSET(fd2, &prets)) { /* si fd2 est prêt... */
49 int n = read(fd2, tampon, TAILLE_TAMPON);
50 if (n >= 0) {
51 /* on copie ce qu'on a lu */
52 write(sortie, tampon, n);
53 } else {
54 /* fin de fd2 : on l'enlève */
55 close(fd2);
56 nbouverts--;
57 FD_CLR(fd2, &ouverts);
58 }
59 };
60 };
61 }
62 int main(int argc, char *argv[])
63 {
64 int fd1, fd2;
65 if (argc != 3) {
66 fprintf(stderr, "Usage : %s f1 f2\n", argv[0]);
67 exit(EXIT_FAILURE);
68 };
69 fd1 = open(argv[1], O_RDONLY);
70 if (fd1 == -1) {
71 fprintf(stderr, "Ouverture %s refusée\n", argv[1]);
72 exit(EXIT_FAILURE);
73 };
74 fd2 = open(argv[2], O_RDONLY);
75 if (fd2 == -1) {
76 fprintf(stderr, "Ouverture %s refusée\n", argv[2]);
77 exit(EXIT_FAILURE);
78 };
79 mixer(fd1, fd2, 1);
80 exit(EXIT_SUCCESS);
81 }
L'exemple suivant montre comment utiliser la limite de temps dans le cas (fréquent) d'attente sur un seul descripteur.
Une fonction utilitaire :
1 /*
2 SelectFifo/AttenteDonnees.c
3 attente de données provenant d'un descripteur ouvert,
4 avec délai maximum
5 */
6 #include <sys/time.h>
7 #include <sys/types.h>
8 #include <sys/stat.h>
9 #include <unistd.h>
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <fcntl.h>
13 /*
14 fonction AttenteDonnees
15 paramètres:
16 - un descripteur ouvert
17 - une durée d'attente en millisecondes
18 rôle: attend que des données arrivent sur le descripteur pendant
19 un certain temps.
20 retourne:
21 1 si des données sont arrivées
22 0 si le délai est dépassé
23 -1 en cas d'erreur. Voir variable errno.
24 */
25 int AttenteDonnees(int fd, int millisecondes)
26 {
27 fd_set set;
28 struct timeval delai;
29 FD_ZERO(&set);
30 FD_SET(fd, &set);
31 delai.tv_sec = millisecondes / 1000;
32 delai.tv_usec = (millisecondes % 1000) * 1000;
33 return select(FD_SETSIZE, &set, NULL, NULL, &delai);
34 }
Le programme principal :
1 /*
2 SelectFifo/lecteur.c
3 Exemple de lecture avec délai (timeout).
4 M. Billaud, Septembre 2002
5 Ce programme attend des lignes de texte provenant d'une fifo, et les
6 affiche.
7 En attendant de recevoir les lignes, il affiche une petite étoile
8 tournante (par affichage successif des symboles - \ | et /).
9 Exemple d'utilisation:
10 - créer une fifo : mkfifo /tmp/mafifo
11 - dans une fenêtre, lancer : lecteur /tmp/mafifo
12 le programme se met en attente
13 - dans une autre fenêtre, faire
14 cat > /tmp/mafifo
15 puis taper quelques lignes de texte.
16 */
17 #include <sys/time.h>
18 #include <sys/types.h>
19 #include <sys/stat.h>
20 #include <unistd.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <fcntl.h>
24 #define TAILLE_TAMPON 100
25 #define DELAI 500 /* en millisecondes */
26 extern int AttenteDonnees(int, int);
27 int main(int argc, char *argv[])
28 {
29 int fd;
30 char symbole[] = "-\\|-";
31 int n = 0;
32 int numero_ligne = 1;
33 if (argc != 2) {
34 fprintf(stderr, "Usage: %s fifo\n", argv[0]);
35 exit(EXIT_FAILURE);
36 }
37 printf("> Ouverture fifo %s ...\n", argv[1]);
38 fd = open(argv[1], O_RDONLY);
39 if (fd == -1) {
40 fprintf(stderr, "Ouverture refusée\n");
41 exit(EXIT_FAILURE);
42 };
43 printf("Ok\n");
44 for (;;) {
45 int r;
46 r = AttenteDonnees(fd, DELAI);
47 if (r == 0) {
48 /* délai dépassé, on affiche un caractère suivi par backspace */
49 printf("%c\b", symbole[n++ % 4]);
50 fflush(stdout);
51 } else {
52 /* on a reçu quelque chose */
53 char buffer[TAILLE_TAMPON];
54 int nb;
55 nb = read(fd, buffer, TAILLE_TAMPON - 1);
56 if (nb <= 0)
57 break;
58 buffer[nb] = '\0';
59 printf("%4d %s", numero_ligne++, buffer);
60 }
61 }
62 close(fd);
63 exit(EXIT_SUCCESS);
64 }