Чтение онлайн

ЖАНРЫ

О чём не пишут в книгах по Delphi

Григорьев Антон Борисович

Шрифт:

2.2.7. Асинхронный режим, основанный на событиях

Асинхронный режим, основанный на событиях, появился во второй версии 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);

// ***** Описание на Delphi *****

function WSAWaitForMultipleEvents(cEvents: DWORD; lphEvents: PWSAEvent; fWaitAll: BOOL; dwTimeout: DWORD; fAlertable: BOOL): DWORD;

Дескрипторы событий, взведения которых ожидает нить, должны храниться в массиве, размер которого передаётся через параметр

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
.

Поделиться с друзьями: