Библиотека сайта rus-linux.net
Цилюрик О.И. Linux-инструменты для Windows-программистов | ||
Назад | Библиотеки API POSIX | Вперед |
Мультиплексирование ввода-вывода
Один из самых старых API:
int select( int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
struct timeval *timeout );
И более поздний эквивалент:
int pselect( int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
const struct timespec *timeout, sigset_t *sigmask );
Различия:
- select()
использует тайм-аут в виде struct
timeval
(с секундами и микросекундами), а pselect()
использует struct timespec
(с секундами и наносекундами);
- select()
может обновить параметр timeout
,
чтобы сообщить, сколько времени осталось. Функция pselect()
не изменяет этот параметр ;
- select()
не содержит параметра sigmask
,
и ведет себя как pselect()
с параметром sigmask
, равным NULL
.
Если этот параметр pselect()
не равен NULL
, то pselect()
сначала замещает текущую маску сигналов на ту, на которую указывает sigmask
,
затем выполняет select()
, и восстанавливает исходную маску сигналов.
Параметр тайм-аута может задаваться несколькими способами:
- NULL, что означает ожидать вечно;
- ожидать инициированное структурой значение времени;
- не ожидать вообще (программный опрос, pooling), когда структура инициализируется значением {0, 0}.
Функции возвращают значение больше нуля — число готовых к операции дескрипторов, ноль — в случае истечения тайм-аута, и отрицательное значение при ошибке.
Вводится понятие набора дескрипторов, и макросы для работы с набором дескрипторов:
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 );
С готовностью дескрипторов чтения и записи readfds, writefds
—
относительно ясно интуитивно. Очень
важно, что вариантом срабатывания исключительной ситуации exceptfds
на дескрипторе сетевых сокетов — является получение
внеполосовых данных TCP, что очень широко используется в реализациях
(конечных автоматов) сетевых протоколов (например SIP, VoIP
сигнализаций PRI, SS7 — на линиях E1/T1, ...).
Примечание: Большинство UNIX систем имеют
определение численной константы FD_SETSIZE
,
но её численное значение сильно зависит от констант периода
компиляции совместимости с стандартами (такими, например, как
__USE_XOPEN2K
, ...).
Ещё один вариант мультиплексирования ввода-вывода
вывода — функция poll()
.
Представление набора дескрипторов заменено на массив структур вида:
struct pollfd { int fd; /* файловый дескриптор */ short events; /* запрошенные события */ short revents; /* возвращенные события */ };
- где: fd
— открытый файловый дескриптор, events
— набор битовых флагов запрошенных событий для этого дескриптора, revents
— набор битовых флагов возвращенные событий для этого дескриптора (из числа запрошенных,
или POLLERR, POLLHUP, POLLNVAL
). Часть возможных битов, описаны в
<sys/poll.h>
:
#define POLLIN 0x0001 /* Можно читать данные */ #define POLLPRI 0x0002 /* Есть срочные данные */ #define POLLOUT 0x0004 /* Запись не будет блокирована */ #define POLLERR 0x0008 /* Произошла ошибка */ #define POLLHUP 0x0010 /* Разрыв соединения */ #define POLLNVAL 0x0020 /* Неверный запрос: fd не открыт */
Ещё некоторая часть описаны в <asm/poll.h>
:
POLLRDNORM, POLLRDBAND, POLLWRNORM, POLLWRBAND
и POLLMSG
.
Сам вызов оперирует с массивом таких структур, по одному элементу на каждый интересующий дескриптор:
#include <sys/poll.h>
int poll( struct pollfd *ufds, unsigned int nfds, int timeout );
- где: ufds
- сам массив структур, nfds
- его размерность, timeout
- тайм-аут в миллисекундах (ожидание при положительном значении,
немедленный возврат при нулевом, бесконечное ожидание при значении,
заданном специальной константой INFTIM
,
которая определена просто как отрицательное значение).
Пример того, как используются (и работают) вызовы
select()
и poll()
-
позаимствованы из [3] (архив ufd.tgz
),
оригиналы кодов У. Стивенса несколько изменены (оригиналы относятся к
1998 г. и проверялись на совершенно других UNIX того периода).
Примеры достаточно объёмные (это полные версии программ TCP клиентов
и серверов), поэтому ниже показаны только фрагменты примеров,
непосредственно относящиеся к вызовам select()
и poll()
, а также примеры того, что реально эти примеры выполняются и как это
происходит (вызовы функций в коде показаны как у У. Стивенса —
с большой буквы, вызов этот — это полный аналог
соответсвующего вызова API, но обрамлённый выводом сообщения о роде
ошибки, если она возникнет):
tcpservselect01.c (TCP ретранслирующий сервер на select()
):
... int nready, client[ FD_SETSIZE ]; fd_set rset, allset; socklen_t clilen; struct sockaddr_in cliaddr, servaddr; ... listenfd = Socket( AF_INET, SOCK_STREAM, 0 ); bzero( &servaddr, sizeof(servaddr) ); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); Bind( listenfd, (SA*)&servaddr, sizeof(servaddr) ); Listen( listenfd, LISTENQ ); maxfd = listenfd; /* initialize */ maxi = -1; /* index into client[] array */ for( i = 0; i < FD_SETSIZE; i++ ) client[i] = -1; /* -1 indicates available entry */ FD_ZERO( &allset ); FD_SET( listenfd, &allset ); for ( ; ; ) { rset = allset; /* structure assignment */ nready = Select( maxfd + 1, &rset, NULL, NULL, NULL ); if( FD_ISSET( listenfd, &rset ) ) { /* new client connection */ connfd = Accept(listenfd, (SA *) &cliaddr, &clilen); ...
tcpservpoll01.c (TCP ретранслирующий сервер на poll()
):
... struct pollfd client[ OPEN_MAX ]; struct sockaddr_in cliaddr, servaddr; ... listenfd = Socket( AF_INET, SOCK_STREAM, 0 ); bzero( &servaddr, sizeof(servaddr) ); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl( INADDR_ANY ); servaddr.sin_port = htons( SERV_PORT ); Bind( listenfd, (SA*)&servaddr, sizeof(servaddr) ); Listen( listenfd, LISTENQ ); client[0].fd = listenfd; client[0].events = POLLRDNORM; for( i = 1; i < OPEN_MAX; i++ ) client[i].fd = -1; /* -1 indicates available entry */ maxi = 0; /* max index into client[] array */ for ( ; ; ) { nready = Poll( client, maxi + 1, INFTIM ); if( client[0].revents & POLLRDNORM ) { /* new client connection */ for( i = 1; i < OPEN_MAX; i++ ) if( client[i].fd < 0 ) { client[i].fd = connfd; /* save descriptor */ break; } ... client[i].events = POLLRDNORM; if( i > maxi ) maxi = i; ...
Как выполнять эти примеры и на что обратить внимание? Запускаем выбранный нами сервер (позже мы остановим его по Ctrl+C), все сервера этого архива прослушивают фиксированный порт 9877, и являются для клиента ретрансляторами данных, получаемых на этот порт:
$ ./tcpservselect01 ... ^C
или
$ ./tcpservpoll01 ... ^C
В том, что сервер прослушивает порт и готов к работе, убеждаемся, например, так:
$ netstat -a | grep :9877
tcp 0 0 *:9877 *:* LISTEN
К серверу подключаемся клиентом (из того же архива примеров), и вводим строки, которые будут передаваться на сервер и ретранслироваться обратно:
$ ./tcpcli01 127.0.0.1 1 строка 1 строка 2 строка 2 строка последняя последняя ^C
Указание IP адреса сервера (не имени!) в качестве параметра запуска клиента — обязательно. Клиентов может быть много — сервера параллельные. Во время выполнения клиента можно увидеть состояние сокетов — клиентского и серверных, прослушивающего и присоединённого (клиент не закрывает соединение после обслуживания каждого запроса, как, например, сервер HTTP):
$ netstat -a | grep :9877 tcp 0 0 *:9877 *:* LISTEN tcp 0 0 localhost:46783 localhost:9877 ESTABLISHED tcp 0 0 localhost:9877 localhost:46783 ESTABLISHED
Предыдущий раздел: | Оглавление | Следующий раздел: |
Расширенные операции ввода-вывода | Ввод-вывод управляемый сигналом |