UNIX: разработка сетевых приложений
Три средних аргумента,
,readsetиwriteset, определяют дескрипторы, которые ядро должно проверить на возможность чтения и записи и на наличие исключений (exceptions). В настоящее время поддерживается только два исключения:exceptset1. На сокет приходят внеполосные данные. Более подробно мы опишем этот случай в главе 24.
2. Присутствие информации об управлении состоянием (control status information), которая должна быть считана с управляющего (master side) псевдотерминала, помещенного в режим пакетной обработки. Псевдотерминалы в данном томе не рассматриваются.
Проблема в том, как задать одно или несколько значений дескрипторов для каждого из трех аргументов. Функция
использует наборы дескрипторов, обычно это массив целых чисел, где каждый бит в каждом целом числе соответствует дескриптору. Например, при использовании 32-разрядных целых чисел первый элемент массива (целое число) соответствует дескрипторам от 0 до 31, второй элемент — дескрипторам от 32 до 63, и т.д. Детали реализации не влияют на приложение и скрыты в типе данныхselectи следующих четырех макросах:fd_setvoid FD_ZERO(fd_set *<i>fdset</i>); /* сбрасываем все биты в <i>fdset</i> */void FD_SET(int <i>fd</i>, fd_set *<i>fdset</i>); /* устанавливаем бит для <i>fd</i> в <i>fdset</i> */void FD_CLR(int <i>fd</i>, fd_set *<i>fdset</i>); /* сбрасываем бит для <i>fd</i> в <i>fdset</i> */int FD_ISSET(int <i>fd</i>, fd_set *<i>fdset</i>); /* установлен ли бит для <i>fd</i> в <i>fdset</i>? */Мы размещаем в памяти набор дескрипторов типа
, с помощью этих макросов устанавливаем и проверяем биты в наборе, а также можем присвоить его (как значение) другому набору дескрипторов с помощью оператора присваивания языка С.fd_setПРИМЕЧАНИЕОписываемый нами массив целых чисел, использующий по одному биту для каждого дескриптора, — это только один из возможных способов реализации функции select. Тем не менее является обычной практикой ссылаться на отдельные дескрипторы в наборе дескрипторов как на биты, например так: «установить бит для прослушиваемого дескриптора в наборе для чтения».
В разделе 6.10 мы увидим, что функция poll использует совершенно другое представление: массив структур переменной длины, по одной структуре для каждого дескриптора.
Например, чтобы определить переменную типа
и затем установить биты для дескрипторов 1, 4 и 5, мы пишем:fd_setfd_set rset;FD_ZERO(&rset); /* инициализируем набор все биты сброшены */FD_SET(1, &rset); /* устанавливаем бит для fd 1 */FD_SET(4, &rset); /* устанавливаем бит для fd 4 */FD_SET(5, &rset); /* устанавливаем бит для fd 5 */Важно инициализировать набор, так как если набор будет создан в виде автоматической переменной и не проинициализировав, результат может оказаться непредсказуемым.
Любой из трех средних аргументов функции
—select,readsetилиwriteset— может быть задан как пустой указатель, если нас не интересует определяемое им условие. На самом деле, если все три указателя пустые, мы просто получаем таймер большей точности, чем обычная функция Unixexceptset(позволяющая задавать время с точностью до секунды). Функцияsleepобеспечивает аналогичную функциональность. На рис. С.9 и С.10 [110] показана функцияpoll, реализованная с помощью функцийsleep_usиselect, которая позволяет устанавливать время ожидания с точностью до микросекунд.pollАргумент
задает число проверяемых дескрипторов. Его значение на единицу больше максимального номера проверяемого дескриптора (поэтому мы назвали егоmaxfdp1). Проверяются дескрипторы 0, 1, 2 и далее доmaxfdp1- 1 включительно.maxfdp1Константа
, определяемая при подключении заголовочного файлаFD_SETSIZE, является максимальным числом дескрипторов для типа данных<sys/select.h>. Ее значение часто равно 1024, но такое количество дескрипторов используется очень немногими программами. Аргументfd_setзаставляет нас вычислять наибольший интересующий нас дескриптор и затем сообщать ядру его значение. Например, в предыдущем коде, который включает дескрипторы 1, 4 и 5, значение аргументаmaxfdp1равно 6. Причина, по которой это 6, а не 5, в том, что мы задаем количество дескрипторов, а не наибольшее значение, а нумерация дескрипторов начинается с нуля.maxfdp1ПРИМЕЧАНИЕЗачем нужно было включать этот аргумент и вычислять его значение? Причина в том, что он повышает эффективность работы ядра. Хотя каждый набор типа fd_set может содержать множество дескрипторов (обычно до 1024), реальное количество дескрипторов, используемое типичным процессом, значительно меньше. Эффективность возрастает за счет того, что не копируются ненужные части набора дескрипторов между ядром и процессом и не требуется проверять биты, которые всегда являются нулевыми (см. раздел 16.13 [128]).
Функция
изменяет наборы дескрипторов, на которые указывают аргументыselect,readsetиwriteset. Эти три аргумента являются аргументами типа «значение-результат». Когда мы вызываем функцию, мы указываем интересующие нас дескрипторы, а по ее завершении результат показывает нам, какие дескрипторы готовы. Проверить определенный дескриптор из структурыexceptsetпосле завершения вызова можно с помощью макросаfd_set. Для дескриптора, не готового для чтения или записи, соответствующий бит в наборе дескрипторов будет сброшен. Поэтому мы устанавливаем все интересующие нас биты во всех наборах дескрипторов каждый раз, когда вызываем функциюFD_ISSET.selectПРИМЕЧАНИЕДве наиболее общих ошибки программирования при использовании функции select — это забыть добавить единицу к наибольшему номеру дескриптора и забыть, что наборы дескрипторов имеют тип «значение-результат». Вторая ошибка приводит к тому, что функция select вызывается с нулевым битом в наборе дескрипторов, когда мы думаем, что он установлен в единицу.
Возвращаемое этой функцией значение указывает общее число готовых дескрипторов во всех наборах дескрипторов. Если значение таймера истекает до того, как какой-нибудь из дескрипторов оказывается готов, возвращается нулевое значение. Возвращаемое значение -1 указывает на ошибку (которая может произойти, если, например, выполнение функции прервано перехваченным сигналом).