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

ЖАНРЫ

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

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

Шрифт:

В качестве примера реализации перекрытого ввода-вывода рассмотрим, ситуацию, когда программа начинает операцию чтения данных из сокета, время от времени проверяя статус операции (листинг 2.71). События в этом примере не используются, проверка осуществляется с помощью функции

WSAGetOverlappedResult
.

Листинг 2.71. Перекрытый ввод-вывод с использованием функции
WSAGetOverlappedResult

var

 S: TSocket;

 Overlapped: TWSAOverlapped;

 BufPtr: TWSABuf;

 RecvBuf: array[1..100] of Char;

 Cnt, Flags: Cardinal;

begin

 // Инициализация WinSock, создание сокета S, привязка его к адресу

 ......

 //
Подготовка структуры, задавшей буфер

 BufPtr.Buf := @RBuf;

 BufPtr.Len := SizeOf(RBuf);

 // Подготовка структуры TWSAOverlapped

 // Поля Internal, InternalHigh, Offset, OffsetHigh программа

 // не устанавливает

 Overlapped.hEvent := 0;

 Flags := 0;

 // Начало операции перекрытого получения данных

 WSARecv(S, @BufPtr, 1, Cnt, Flags, @Overlapped, nil);

 while True do

 begin

if WSAGetOverlappedResult(S, @Overlapped, Cnt, False, Flags) then

begin

// Данные получены, находятся в RecvBuf, обрабатываем

......

// Выходим из цикла Break;

 end

 else if WSAGetLastError <> WSA_IO_INCOMPLETE then

 begin

// Произошла ошибка, анализируем ее

......

// Выходим из цикла

Break;

 end

 else

 begin

// Операция чтения не завершена

// Занимаемся другими действиями

 end;

end;

Теперь перейдем к рассмотрению перекрытого ввода-вывода на основе процедур завершения. Для этого при вызове функции

WSARecv
нужно задать указатель на процедуру завершения, описанную в программе. Процедура завершения должна иметь прототип, приведенный в листинге 2.72.

Листинг 2.72. Прототип процедуры завершения

// ***** Описание на C++ *****

void CALLBACK CompletionROUTINE(DWORD dwError, DWORD cbTransferred, LPWSAOVERLAPPED lpOverlapped, DWORD dwFlags);

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

TWSAOverlappedCompletionRoutine =

 procedure(dwError: DWORD; cbTransferred: DWORD; lpOverlapped: PWSAOverlapped; dwFlags: DWORD); stdcall;

При использовании процедур завершения в функцию

WSARecv
также нужно передавать указатель на запись
TWSAOverlapped
через параметр
lpOverlapped
, но значение поля
hEvent
этой структуры игнорируется. Вместо взведения события при завершении операции будет вызвана процедура, указанная в качестве параметра функции
WSARecv
. Указатель на структуру, заданный при вызове
WSARecv
, передается в процедуру завершения через параметр
lpOverlapped
. Смысл остальных параметров очевиден:
dwError
— это код ошибки (или ноль, если операция завершена успешно),
cbTransferred
— число полученных байтов (само полученное сообщение копируется в буферы, указанные при вызове функции
WSARecv
), a
dwFlags
— флаги.

Процедура завершения всегда выполняется в той нити, которая инициировала начало операции перекрытого ввода-вывода. Но система не может прерывать нить для выполнения процедуры завершения в любой удобный ей момент — нить должна перейти в состояние ожидания. В это состояние ее можно перевести, например, с помощью функции

SleepEx
, имеющей следующий прототип:

function SleepEx(dwMilliseconds: DWORD; bAlertable: BOOL); DWORD;

Функция

SleepEx
является частью стандартного API системы и импортируется модулем Windows. Она переводит нить в состояние ожидания. Параметр
dwMilliseconds
задает время ожидания в миллисекундах (или значение
INFINITE
для бесконечного ожидания). Параметр bAlertable указывает, допустимо ли прерывание состояния ожидания для выполнения процедуры завершения. Если
bAlertable
равен
False
, функция
SleepEx
ведет себя так же как функция
Sleep
, т.е. просто приостанавливает работу нити на заданное время. Если
bAlertable
равен
True
, нить может быть выведена системой из состояния ожидания раньше, чем истечет заданное время, если возникнет необходимость выполнить процедуру завершения. О причине завершения ожидания программа может судить по результату, возвращаемому функцией
SleepEx
: ноль в случае завершения по тайм-ауту и
WAIT_IO_COMPLETION
в случае завершения из-за выполнения процедуры завершения (в последнем случае сначала выполняется процедура завершения, а потом только происходит возврат из функции
SleepEx
). Если завершились несколько операций перекрытого ввода-вывода, в результате выполнения
SleepEx
будут вызваны процедуры завершения для всех этих операций.

Существует также возможность ожидать выполнения процедуры завершения одновременно с ожиданием взведения событий с помощью функции

WSAWaitForMultipleEvents
. Напомним, что у этой функции также есть параметр
fAlertable
. Если задать его равным
True
, то при необходимости выполнения процедуры завершения функция
WSAWaitForMultipleEvents
, подобно функции
SleepEx
, выполняет эту процедуру и возвращает
WAIT_IO_COMPLETION
.

Если программа выполняет одновременно несколько операций перекрытого ввода-вывода, возникает вопрос, как при вызове процедуры завершения определить, какая из них завершилась. Для каждой такой операции должен быть создан уникальный экземпляр записи

TWSAOverlapped
. Процедура завершения получает указатель на тот экземпляр, который использовался для начала завершившейся операции. Можно сравнил, указатель с теми, которые были заданы при запуске операций перекрытого ввода-вывода, и определить, какая из них завершилась. Это не всегда бывает удобно из-за необходимости где-то хранить список указателей, заданных при начале операций перекрытого ввода-вывода. Существуют еще два варианта решения этой проблемы. Первый заключается в создании своей процедуры завершения для каждой из выполняющихся параллельно операций. Этот способ приводит к получению громоздкого кода и может быть неудобен, если число одновременно выполняющихся операций заранее неизвестно. Он целесообразен только при одновременном выполнении разнородных операций, требующих разных алгоритмов при обработке их завершения. Другой вариант предлагается в MSDN. Так как при работе через процедуры завершения значение поля
hEvent
структуры
TWSAOverlapped
игнорируется системой, программа может записать туда любое 32-битное значение и с его помощью определить, какая из операций завершена. В строго типизированном языке, каким является Delphi, подобное смещение типа дескриптора и целого выглядит весьма непривлекательно, но, к сожалению, это лучшее из того, что нам предлагают разработчики WinSock API.

Механизм процедур завершения допускает определение статуса операции с с помощью функции

WSAGetOverlappedResult
, но ее параметр
fWait
обязательно должен быть равен
False
, потому что события, необходимые для выполнения ожидания, не взводятся, и попытка дождаться окончания операции может привести к блокировке работы нити.

В процедуре завершения допускается вызывать функции, начинающие новую операцию перекрытого ввода-вывода, в том числе и такую же операцию, которая только что завершена. Эта возможность используется в примере, приведенном в листинге 2.73. Пример иллюстрирует работу клиента, который подключается к серверу и получает от него данные в режиме перекрытого ввода-вывода, выполняя параллельно какие-то другие действия.

Листинг 2.73. Перекрытый ввод-вывод с использованием процедуры завершения

var

 S: TSocket;

 Overlapped: TWSAOverlapped;

 BufPtr: TWSABuf;

 RecvBuf: array[1..100] of Char;

 Cnt, Flags: Cardinal;

 Connected: Boolean;

procedure GetData(Err, Cnt:DWORD; OvPtr: PWSAOverlapped; Flags: DWORD): stdcall;

begin

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