select_tut | Début | Suivant | Sommaire | Préc.page.lue | Accueil |
NOM | Début | Précédent | Suivant | Sommaire | Préc.page.lue | Accueil |
SYNOPSIS | Début | Précédent | Suivant | Sommaire | Préc.page.lue | Accueil |
/* Selon POSIX.1-2001 */
#include <sys/select.h> /* Selon les normes précédentes */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h> int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *utimeout); void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set); #include <sys/select.h> int pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timespec *ntimeout, const sigset_t *sigmask);
Exigences de macros de test de fonctionalités pour la glibc (voir feature_test_macros(7)) :
pselect() : _POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600
DESCRIPTION | Début | Précédent | Suivant | Sommaire | Préc.page.lue | Accueil |
Pour résumer, select() surveille simplement de multiples descripteurs de fichiers, et constitue l'appel Unix standard pour réaliser cette tâche.
Les tableaux de descripteurs de fichier sont appelés ensembles de descripteurs de fichiers. Chaque ensemble est de type fd_set, et son contenu peut être modifié avec les macros FD_CLR(), FD_ISSET(), FD_SET()et FD_ZERO(). On commence généralement par utiliser FD_ZERO() sur un ensemble venant d'être créé. Ensuite, les descripteurs de fichiers individuels qui vous intéressent peuvent être ajoutés un à un à l'aide de FD_SET(). select() modifie le contenu de ces ensembles selon les règles ci-dessous. Après un appel à select(), vous pouvez vérifier si votre descripteur de fichier est toujours présent dans l'ensemble à l'aide de la macro FD_ISSET(). FD_ISSET() renvoie zéro si le descripteur de fichier est absent et une valeur non nulle sinon. FD_CLR() retire un descripteur de fichier de l'ensemble.
Arguments | Début | Précédent | Suivant | Sommaire | Préc.page.lue | Accueil |
struct timeval { long tv_sec; /* secondes */ long tv_usec; /* microsecondes */ };
struct timespec { long tv_sec; /* secondes */ long tv_nsec; /* nanosecondes */ };
Combinaison d'événements de signaux et de données | Début | Précédent | Suivant | Sommaire | Préc.page.lue | Accueil |
int child_events = 0; void child_sig_handler(int x) { child_events++; signal(SIGCHLD, child_sig_handler); } int main(int argc, char **argv) { sigset_t sigmask, orig_sigmask; sigemptyset(&sigmask); sigaddset(&sigmask, SIGCHLD); sigprocmask(SIG_BLOCK, &sigmask, &orig_sigmask); signal(SIGCHLD, child_sig_handler); for (;;) { /* main loop */ for (; child_events > 0; child_events--) { /* do event work here */ } r = pselect(n, &rd, &wr, &er, 0, &orig_sigmask); /* corps principal du programme */ } }
Pratique | Début | Précédent | Suivant | Sommaire | Préc.page.lue | Accueil |
Un exemple simple de l'utilisation de select() peut être trouvé dans la page de manuel select().
Règles de select | Début | Précédent | Suivant | Sommaire | Préc.page.lue | Accueil |
Émulation de usleep | Début | Précédent | Suivant | Sommaire | Préc.page.lue | Accueil |
struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 200000; /* 0.2 secondes */ select(0, NULL, NULL, NULL, &tv);
Le fonctionnement n'est cependant garanti que sur les systèmes Unix.
VALEUR RENVOYÉE | Début | Précédent | Suivant | Sommaire | Préc.page.lue | Accueil |
En cas de timeout échu, la valeur de retour sera zéro. Les descripteurs de fichiers devraient tous être vides (mais peuvent ne pas l'être sur certains systèmes).
Une valeur de retour égale à -1 indique une erreur, errno est alors remplie de façon adéquate. En cas d'erreur, le contenu des ensembles renvoyés et de la structure timeout sont indéfinis et ne devraient pas être exploités. pselect() ne modifie cependant jamais ntimeout.
NOTES | Début | Précédent | Suivant | Sommaire | Préc.page.lue | Accueil |
L'appel système poll(2) a les mêmes fonctionnalités que select(), et est quelque peu plus efficace lors de la surveillance d'ensembles de descripteurs de fichiers parsemés. Il est aujourd'hui largement disponible mais était considéré historiquement comme moins portable que select().
L'API epoll(7), spécifique à Linux, fournit une interface plus efficace que select(2) et poll(2) pour la surveillance d'un grand nombre de descripteurs de fichiers.
EXEMPLE | Début | Précédent | Suivant | Sommaire | Préc.page.lue | Accueil |
#include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <sys/time.h> #include <sys/types.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 a; int s; int yes; if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror ("socket"); return -1; } yes = 1; if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &yes, sizeof (yes)) < 0) { perror("setsockopt"); close(s); return -1; } memset(&a, 0, sizeof (a)); a.sin_port = htons(listen_port); a.sin_family = AF_INET; if (bind(s, (struct sockaddr *) &a, sizeof (a)) < 0) { perror("bind"); close(s); return -1; } printf("accepting connections on port %d\n", listen_port); listen(s, 10); return s; } static int connect_socket(int connect_port, char *address) { struct sockaddr_in a; int s; if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("socket"); close(s); return -1; } memset(&a, 0, sizeof (a)); a.sin_port = htons(connect_port); a.sin_family = AF_INET; if (!inet_aton(address, (struct in_addr *) &a.sin_addr.s_addr)) { perror("bad IP address format"); close(s); return -1; } if (connect(s, (struct sockaddr *) &a, sizeof (a)) < 0) { perror("connect()"); shutdown(s, SHUT_RDWR); close(s); return -1; } return s; } #define SHUT_FD1 { \ if (fd1 >= 0) { \ shutdown(fd1, SHUT_RDWR); \ close(fd1); \ fd1 = -1; \ } \ } #define SHUT_FD2 { \ if (fd2 >= 0) { \ shutdown(fd2, SHUT_RDWR); \ close(fd2); \ fd2 = -1; \ } \ } #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, buf1_written; int buf2_avail, buf2_written; 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 < 0) exit(EXIT_FAILURE); for (;;) { int r, n = 0; fd_set rd, wr, er; FD_ZERO(&rd); FD_ZERO(&wr); FD_ZERO(&er); FD_SET(h, &rd); n = max (n, h); if (fd1 > 0 && buf1_avail < BUF_SIZE) { FD_SET(fd1, &rd); n = max(n, fd1); } if (fd2 > 0 && buf2_avail < BUF_SIZE) { FD_SET(fd2, &rd); n = max(n, fd2); } if (fd1 > 0 && buf2_avail - buf2_written > 0) { FD_SET(fd1, &wr); n = max(n, fd1); } if (fd2 > 0 && buf1_avail - buf1_written > 0) { FD_SET(fd2, &wr); n = max(n, fd2); } if (fd1 > 0) { FD_SET(fd1, &er); n = max(n, fd1); } if (fd2 > 0) { FD_SET(fd2, &er); n = max(n, fd2); } r = select(n + 1, &rd, &wr, &er, NULL); if (r == -1 && errno == EINTR) continue; if (r < 0) { perror("select()"); exit(EXIT_FAILURE); } if (FD_ISSET(h, &rd)) { unsigned int l; struct sockaddr_in client_address; memset(&client_address, 0, l = sizeof(client_address)); r = accept(h, (struct sockaddr *) &client_address, &l); if (r < 0) { perror("accept()"); } else { SHUT_FD1; SHUT_FD2; buf1_avail = buf1_written = 0; buf2_avail = buf2_written = 0; fd1 = r; fd2 = connect_socket(forward_port, argv[3]); if (fd2 < 0) { SHUT_FD1; } else printf("connexion de %s\n", inet_ntoa (client_address.sin_addr)); } } /* NB : lecture des données hors bande avant les lectures normales */ if (fd1 > 0) if (FD_ISSET(fd1, &er)) { char c; errno = 0; r = recv(fd1, &c, 1, MSG_OOB); if (r < 1) { SHUT_FD1; } else send(fd2, &c, 1, MSG_OOB); } if (fd2 > 0) if (FD_ISSET(fd2, &er)) { char c; errno = 0; r = recv(fd2, &c, 1, MSG_OOB); if (r < 1) { SHUT_FD1; } else send(fd1, &c, 1, MSG_OOB); } if (fd1 > 0) if (FD_ISSET(fd1, &rd)) { r = read(fd1, buf1 + buf1_avail, BUF_SIZE - buf1_avail); if (r < 1) { SHUT_FD1; } else buf1_avail += r; } if (fd2 > 0) if (FD_ISSET(fd2, &rd)) { r = read(fd2, buf2 + buf2_avail, BUF_SIZE - buf2_avail); if (r < 1) { SHUT_FD2; } else buf2_avail += r; } if (fd1 > 0) if (FD_ISSET(fd1, &wr)) { r = write (fd1, buf2 + buf2_written, buf2_avail - buf2_written); if (r < 1) { SHUT_FD1; } else buf2_written += r; } if (fd2 > 0) if (FD_ISSET(fd2, &wr)) { r = write (fd2, buf1 + buf1_written, buf1_avail - buf1_written); if (r < 1) { SHUT_FD2; } else buf1_written += r; } /* Vérifie si l'écriture de données a provoqué 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 E/S comme non bloquantes en utilisant un appel ioctl(2). Cela pose également problème parce que vous finissez par avoir 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 chainée de tampons - un pour chaque connexion. Pour l'instant, de nouvelles connexions provoquent l'abandon de la connexion courante.
VOIR AUSSI | Début | Précédent | Suivant | Sommaire | Préc.page.lue | Accueil |
TRADUCTION | Début | Précédent | Suivant | Sommaire | Préc.page.lue | Accueil |
Ce document est une traduction réalisée par Stéphan Rafin <stephan DOT rafin AT laposte DOT net> le 16 juin 2002 et révisée le 8 janvier 2008.
L'équipe de traduction a fait le maximum pour réaliser une adaptation française de qualité. La version anglaise la plus à jour de ce document est toujours consultable via la commande : « LANG=C man 2 select_tut ». N'hésitez pas à signaler à l'auteur ou au traducteur, selon le cas, toute erreur dans cette page de manuel.
Sommaire | Début | Suivant | Sommaire | Préc.page.lue | Accueil |