Дескрипторы событий, взведения которых ожидает нить, должны храниться в массиве, размер которого передаётся через параметр
cEvents
, а указатель — через параметр
lphEvents
. Параметр
fWaitAll
определяет, что является условием окончания ожидания: если он равен
True
, ожидание завершается, когда все события из переданного массива оказываются во взведенном состоянии,
если
False
— когда оказывается взведенным хотя бы одно из них. Параметр
dwTimeout
определяет тайм-аут ожидания в миллисекундах. В WinSock 2 определена константа
WSA_INFINITE
(совпадающая по значению со стандартно константой
INFINITE
), которая задает бесконечное ожидание. Параметр
fAlertable
нужен при перекрытом вводе-выводе: мы рассмотрим его позже в разд. 2.2.9. Если перекрытый ввод-вывод не используется,
fAlertable
должен быть равен
False
.
Существует ограничение на число событий, которое можно ожидать с помощью данной функции. Максимальное число событий определяется константой
WSA_MAXIMUM_WAIT_EVENTS
, которая в данной реализации равна 64.
Результат, возвращаемый функцией, позволяет определить, по каким причинам закончилось ожидание. Если ожидалось взведение всех событий (
fWaitAll = True
), и оно произошло, функция возвращает
WSA_WAIT_EVENT_0
(0). Если ожидалось взведение хотя бы одного из событий, возвращается
WSA_WAIT_EVENT_0 + Index
, где
Index
— индекс взведенного события в массиве
lphEvents
(отсчет индексов начинается с нуля). Если ожидание завершилось по тайм-ауту, возвращается значение
WSA_WAIT_TIMEOUT
(258). И наконец, если произошла какая-либо ошибка, функция возвращает
WSA_WAIT_FAILED
(
$FFFFFFFF
).
Существует еще одно значение, которое может возвратить функция
WSAWaitForMultipleEvents
:
WAIT_IO_COMPLETION
(это константа из стандартной части Windows API, она объявлена в модуле
Windows
). Смысл этого результата и условия, при которых он может быть возвращен, мы рассмотрим в разд. 2.2.9.
Функции, которые мы рассматривали до сих пор, являются аналогами системных функций для стандартных событий. Теперь мы переходим к рассмотрению тех функций, которые отличают сокетные события от стандартных. Главная из них —
WSAEventSelect
, позволяющая привязать события, создаваемые с помощью
WSACreateEvent
, к тем событиям, которые происходят на сокете. Прототип этой функции приведен в листинге 2.59.
Листинг 2.59. Функция
WSAEventSelect
// ***** Описание на C++ *****
int WSAEventSelect(SOCKET s, WSAEVENT hEventObject, long lNetworkEvents);
// ***** описание на Delphi *****
function WSAEventSelect(S: TSocket; hEventObject: TWSAEvent; lNetworkEvents: LongInt): Integer;
Эта функция очень похожа на функцию
WSAAsyncSelect
, за исключением того, что события
FD_XXX
привязываются не к оконным сообщениям, а к сокетным событиям. Параметр
S
определяет сокет, события которого отслеживаются, параметр
hEventObject
— событие, которое должно взводиться при наступлении отслеживаемых событий,
lNetworkEvents
— комбинация констант
FD_XXX
, определяющая, с какими событиями на сокете связывается событие
hSocketEvent
.
Функция
WSAEventSelect
возвращает ноль, если операция прошла успешно, и
SOCKET_ERROR
при возникновении ошибки.
Событие, связанное с сокетом функцией
WSAEventSelect
, взводится при тех же условиях, при которых в очередь окна помещается сообщение при использовании
WSAAsyncSelect
. Так, например, функция
recv
взводит событие, если после ее вызова в буфере сокета еще остаются данные. Но, с другой стороны, функция
recv
не сбрасывает событие, если данных в буфере сокета нет. А поскольку сокетные события не сбрасываются автоматически функцией
WSAWaitForMultipleEvents
, программа всегда должна сбрасывать события сама. Так, при обработке
FD_READ
наиболее типична ситуация, когда сначала сбрасывается событие, а потом вызывается функция
recv
, которая при необходимости снова взводит событие. Здесь мы снова имеем проблему ложных срабатываний в тех случаях, когда данные извлекаются из буфера по частям с помощью нескольких вызовов
recv
, но в данном случае проблему решить легче: не нужно отменять регистрацию событий, достаточно просто сбросить событие непосредственно перед последним вызовом
recv
.
В принципе, события
FD_XXX
разных сокетов можно привязать к одному сокетному событию, но этой возможностью обычно не пользуются, т.к. в WinSock2 отсутствуют средства, позволяющие определить, событие на каком из сокетов привело к взведению сокетного события. Поэтому приходится для каждого сокета создавать отдельное событие.
Как и в случае с
WSAAsyncSelect
при вызове
WSAEventSelect
сокет переводится в неблокирующий режим. Повторный вызов
WSAEventSelect
для данного сокета отменяет результаты предыдущего вызова (т.е. невозможно связать разные события
FD_XXX
одного сокета с разными сокетными событиями). Сокет, созданный в результате вызова accept или
WSAAccept
наследует связь с сокетными событиями, установленную для слушающего сокета.
Существует весьма важное различие между использованием оконных сообщений и сокетных событий для оповещения о том, что происходит на сокете.
Предположим, с помощью функции
WSAAsyncSelect
события
FD_READ
,
FD_WRITE
и
FD_CONNECT
связаны с некоторым оконным сообщением. Пусть происходит событие
FD_CONNECT
. В очередь окна помещается соответствующее сообщение. Затем, до того, как предыдущее сообщение будет обработано, происходит
FD_WRITE
. В очередь окна помещается еще одно сообщение, которое информирует об этом. И наконец, при возникновении
FD_READ
в очередь будет помещено третье сообщение. Затем оконная процедура получит их по очереди и обработает.
Теперь рассмотрим ситуацию, когда те же события связаны с сокетным событием. Когда происходит
FD_CONNECT
, сокетное событие взводится. Теперь если
FD_WRITE
и
FD_READ
произойдут до того, как сокетное событие будет сброшено, оно уже не изменит своего состояния. Таким образом, программа, работающая с асинхронными сокетами, основанными на событиях, должна, во-первых, учитывать, что взведенное событие может означать несколько событий
FD_XXX
, а во-вторых, иметь возможность узнать, какие именно события произошли с момента последней проверки. Для получения этой информации предусмотрена функция
WSAEnumNetworkEvents
, прототип которой приведен в листинге 2.60.
Листинг 2.60. Функция
WSAEnumNetworkEvents
// ***** Описание на C++ *****
int WSAEnumNetworkEvents(SOCKET s, WSAEVENT hEventObject, LPWSANETWORKEVENTS lpNetworkEvents);
// ***** Описание на Delphi *****
function WSAEnumNetworkEvents(S: TSocket; hEventObject: TWSAEvent; var NetworkEvents: TWSANetworkEvents): Integer;
Функция
WSAEnumNetworkEvents
через параметр
NetworkEvents
возвращает информацию о том, какие события произошли на сокете S с момента последнего вызова этой функции для данного сокета (или с момента запуска программы, если функция вызывается в первый раз). Параметр
hEventObject
необязательный, он определяет сокетное событие, которое нужно сбросить. Использование этого параметра позволяет обойтись без явного вызова функции
WSAResetEvent
для сброса события. Как и большинство функций WinSock, функция
WSAEnumNetworkEvents
возвращает ноль в случае успеха и ненулевое значение при возникновении ошибки.
Запись
TWSANetworkEvents
содержит информацию о произошедших событиях об ошибках (листинг 2.61).