Библиотека сайта 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
| Предыдущий раздел: | Оглавление | Следующий раздел: |
| Расширенные операции ввода-вывода | Ввод-вывод управляемый сигналом |
