Наши партнеры








Книги по Linux (с отзывами читателей)

Библиотека сайта rus-linux.net

На главную -> MyLDP -> Электронные книги по ОС Linux
Цилюрик О.И. 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 

Предыдущий раздел: Оглавление Следующий раздел:
Расширенные операции ввода-вывода   Ввод-вывод управляемый сигналом