Если программа выполняет одновременно несколько операций перекрытого ввода-вывода, возникает вопрос, как при вызове процедуры завершения определить, какая из них завершилась. Для каждой такой операции должен быть создан уникальный экземпляр записи 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
if Err <> 0 then
begin
// Произошла ошибка. Соединение нужно устанавливать заново
closesocket(S);
Connected := False;
end;
else
begin
// Получены данные, обрабатываем
......
// Запускаем новую операцию перекрытого чтения
Flags := 0;
WSARecv(S, @BufPtr, 1, Cnt, Flags, OvPtr, GetData);
end;
end;
procedure ProcessConnection;
begin
// Устанавливаем начальное состояние - сокет не соединен
Connected := False;
// Задаем буфер
BufPtr.Buf := @RecvBuf;
BufPtr.Len := SizeOf(RecvBuf);
while True do
begin
if not Connected then
begin
Connected := True;
// Создаем и подключаем сокет
S := socket(AF_INET, SOCK_STREAM, 0);
connect(S, ...);
// Запускаем первую для данного сокета операцию чтения
Flags := 0;
WSARecv(S, @BufPtr, 1, Cnt, Flags, @Overlapped, GetData);
end;
// Позволяем системе выполнить процедуру завершения,
// если это необходимо
SleepEx(0, True);
// Выполняем какие-либо дополнительные действия
......
end;
end;
Основная процедура здесь — ProcessConnection
. Эта процедура в бесконечном цикле устанавливает соединение, если оно не установлено, дает системе выполнить процедуру завершения, если это требуется, и выполняет какие-либо иные действия, не связанные с получением данных от сокета. Процедура завершения GetData
получает и обрабатывает данные, а если произошла ошибка, закрывает сокет и сбрасывает флаг Connected
, что служит для процедуры ProcessConnection
сигналом о необходимости установить соединение заново.
Из этого примера хорошо видны достоинства и недостатки процедур заверения. Получение и обработка данных выносится в отдельную процедуру, и с одной стороны, позволяет разгрузить основную процедуру, но, с другой стороны, заставляет прибегнуть к глобальным переменным для буфера и сокета.
Читать дальше
Конец ознакомительного отрывка
Купить книгу