Асинхронный режим, основанный на событиях, появился во второй версии Windows Sockets. В его основе лежат события — специальные объекты, служащие для синхронизации работы нитей.
Существуют события, поддерживаемые на уровне системы. Они создаются с помощью функции
CreateEvent
. Каждое событие может находиться в сброшенном или взведенном состоянии. Нить с помощью функций
WaitForSingleObject
и
WaitForMultipleObjects
может дожидаться, пока одно или несколько событий не окажутся во взведенном состоянии. В режиме ожидания нить не требует процессорного времени. Другая нить может установить событие с помощью функции
SetEvent
, в результате чего первая нить выйдет из состояния ожидания и продолжит свою работу. Подробно о системных событиях и прочих объектах синхронизации написано
в [2].
Аналогичные объекты определены и в Windows Sockets. Сокетные события отличаются от стандартных системных событий прежде всего тем, что они могут быть связаны с событиями
FD_XXX
, происходящими на сокете, и взводиться при наступлении этих событий.
Так как сокетные события поддерживаются только в WinSock 2, модуль WinSock не содержит объявлений типов и функций, требуемых для их поддержки. Поэтому их придется объявлять самостоятельно. Прежде всего, должен быть объявлен тип дескриптора событий, который в MSDN называется
WSAEVENT
. В Delphi он может быть объявлен следующим образом:
PWSAEvent = ^TWSAEvent;
TWSAEvent = THandle;
Событие создается с помощью функции
WSACreateEvent
, прототип которой приведен в листинге 2.55.
Листинг 2.55. Функция
WSACreateEvent
// ***** Описание на C++ *****
WSAEVENT WSACreateEvent(void);
// ***** Описание на Delphi *****
function WSACreateEvent: TWSAEvent;
Событие, созданное этой функцией, находится в сброшенном состоянии, при ожидании автоматически не сбрасывается, не имеет имени и обладает стандартными атрибутами безопасности. В MSDN отмечено, что сокетное событие на самом деле является простым системным событием, и его можно создавать с помощью стандартной функции
CreateEvent
, управляя значениями всех перечисленных параметров.
Функция создает событие и возвращает его дескриптор. Если произошла ошибка, функция возвращает значение
WSA_INVALID_EVENT
(0). Для ручного взведения и сброса события предназначены функции
WSASetEvent
и
WSAResetEvent
соответственно, прототипы которых приведены в листинге 2.56.
Листинг 2.56. Функции для управления событиями
// ***** Описание на C++ *****
BOOL WSASetEvent(WSAEVENT hEvent);
BOOL WSAResetEvent(WSAEVENT hEvent);
// ***** Описание на Delphi *****
function WSASetEvent(hEvent: TWSAEvent): BOOL;
function WSAResetEvent(hEvent: TWSAEvent): BOOL;
Функции возвращают
True
, если операция прошла успешно, и
False
— в противном случае.
После завершения работы с событием оно уничтожается с помощью функции
WSACloseEvent
(листинг 2.57).
Листинг 2.57. Функция
WSACloseEvent
// ***** Описание на C++ *****
BOOL WSACloseEvent(WSAEVENT nEvent);
// ***** Описание на Delphi *****
function WSACloseEvent(hEvent: TWSAEvent): BOOL;
Функция уничтожает событие и освобождает связанные с ним ресурсы. Дескриптор, переданный в качестве параметра, становится недействительным. Для ожидания взведения событий служит функция
WSAWaitForMultiрleEvents
(листинг 2.58).
Листинг 2.58. Функция
WSAWaitForMultipleEvents
// ***** Описание на C++ *****
DWORD WSAWaitForMultipleEvents(DWORD cEvents, const WSAEVENT FAR *lphEvents, BOOL fWaitAll, WORD dwTimeout, BOOL fAlertable);
Дескрипторы событий, взведения которых ожидает нить, должны храниться в массиве, размер которого передаётся через параметр
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
, но в данном случае проблему решить легче: не нужно отменять регистрацию событий, достаточно просто сбросить событие непосредственно перед последним вызовом