Так как многие функции библиотеки сокетов блокируют вызвавшую их нить, если соответствующая операция не может быть выполнена немедленно, часто бывает полезно заранее знать, готов ли сокет к немедленному (без блокирования) выполнению той или иной операции. Основным средством определения этого в библиотеке сокетов служит функция select
:
function select(nfds: Integer; readfds, writefds, exceptfds: PFDSet; timeout: PTimeVal): LongInt;
Первый параметр этой функции оставлен только для совместимости со старыми версиями библиотеки сокетов: в существующих версиях он игнорируется. Три следующих параметра содержат указатели на множества сокетов (эти множества описываются типом TFDSet
), состояние которых должно проверяться. В данном случае понятие множества не имеет ничего общего с типом множество в Delphi. В оригинальной версии библиотеки сокетов, написанной на C, определены макросы, позволяющие очищать такие множества, добавлять и удалять сокеты и определять, входит ли тот или иной сокет в множество. В модуле WinSock эти макросы заменены одноименными процедурами и функциями (листинг 2.22).
TFDSet
// Удаляет сокет Socket из множества FDSet.
procedure FD_CLR(Socket: TSocket; var FDSet: TFDSet);
// Определяет, входит ли сокет Socket в множество FDSet.
function FD_ISSET(Socket: TSocket; var FDSet: TFDSet): Boolean;
// Добавляет сокет Socket в множество FDSet.
procedure FD_SET(Socket: TSocket; var FDSet: TFDSet);
// Инициализирует множество FDSet.
procedure FD_ZERO(var FDSet: TFDSet);
При создании переменной типа TFDSet
в той области памяти, которую она занимает, могут находиться произвольные данные, являющиеся, по сути дела, "мусором". Из-за этого мусора функции FD_CLR
, FD_ISSET
, и FD_SET
не смогут работать корректно. Процедура FD_ZERO
очищает мусор, создавая пустое множество. Вызов остальных функций FD_XXX
до вызова FD_ZERO
приведёт к непредсказуемым результатам.
Мы намеренно не приводим здесь описание внутренней структуры типа TFDSet
. С помощью функций FD_XXX
можно выполнить все необходимые операции с множеством, не зная этой структуры. Отметим, что в Windows и в Unix внутреннее устройство этого типа существенно различается, но благодаря использованию этих функций код остается переносимым.
В Windows максимальное количество сокетов, которое может содержать в себе множество TFDSet
, определяется значением константы FD_SETSIZE
. По умолчанию ее значение равно 64. В C/C++ отсутствует раздельная компиляция модулей в том смысле, в котором она существует в Delphi, поэтому модуль в этих языках может поменять значение константы FD_SETSIZE
перед включением заголовочного файла библиотеки сокетов, и это изменение приведёт к изменению внутренней структуры типа TFDSet
(точнее, типа FDSet
— в C/C++ он называется так). К счастью, в Delphi модули надежно защищены от подобного влияния друг на друга, поэтому как бы мы ни переопределяли константу FD_SETSIZE
в своем модуле, на модуле WinSock это никак не отразится. В Delphi приходится прибегать к другому способу изменения количества сокетов в множестве: для этого следует определить свой тип, эквивалентный по структуре TFDSet
, но резервирующий иное количество памяти для хранения сокетов (структуру TFDSet
можно узнать из исходного кода модуля WinSock). В функцию select
можно передавать указатели на структуры нового типа, необходимо только приведение типов указателей. А вот существующие функции FD_XXX
, к сожалению, не смогут работать с новой структурой, потому что компилятор требует строгого соответствия типов для параметров-переменных. Но, опять же, при необходимости очень легко создать аналоги этих функций для своей структуры.
На первый взгляд может показаться, что Delphi в данном случае хуже, чем C/C++. Но достаточно хотя бы раз столкнуться с ошибкой, вызванной взаимным влиянием макроопределений в модулях C/C++, чтобы понять, что уж лучше написать несколько лишних строк кода, лишь бы никогда больше не иметь таких проблем.
Последний параметр функции select
содержит указатель на структуру TTimeVal
, которая описывается следующим образом: