При использовании перекрытого ввода-вывода операция, которая не может быть выполнена немедленно, формально завершается ошибкой — в этом заключается сходство перекрытого ввода-вывода и неблокирующего режима. Однако, в отличие от неблокирующего режима, при перекрытом вводе-выводе библиотека сокетов начинает выполнять операцию в фоновом режиме, после ее завершения начавшая операцию программа получает уведомление об успешно выполненной операции или о возникшей при ее выполнении фатальной ошибке. Несколько операций ввода-вывода могут одновременно выполняться в фоновом режиме, как бы перекрывая работу инициировавшей их нити и друг друга. Именно поэтому данная модель получила название модели перекрытого ввода-вывода.
Перекрытый ввод-вывод существовал и в спецификации WinSock 1, но реализовывался только
для линии NT. Специальных функций для перекрытого ввода-вывода в WinSock 1 не было, требовались функции
ReadFile
и
WriteFile
, в которые вместо дескриптора файла подставлялся дескриптор сокета. В WinSock 2 появилась полноценная поддержка перекрытого ввода-вывода для всех версий Windows, а в спецификацию добавились новые функции для его реализации, избавившие от необходимости использования функций файлового ввода-вывода. Здесь мы будем рассматривать перекрытый ввод-вывод только в спецификации WinSock 2, т.к. старый вариант из-за своих ограничений уже не имеет практического смысла.
Существуют два варианта уведомления о завершении операции перекрытого ввода-вывода: через событие и через процедуру завершения. Кроме того, программа может не дожидаться уведомления, а проверять состояние запроса перекрытого ввода-вывода с помощью функции
WSAGetOverlappedResult
(ее мы рассмотрим позже).
Чтобы сокет мог использоваться в операциях перекрытого ввода-вывода, при его создании должен быть установлен флаг
WSA_FLAG_OVERLAPPED
(функция
socket
неявно устанавливает этот флаг). Для выполнения операций перекрытого ввода-вывода сокет не нужно переводить в какой-либо особый режим, достаточно обычные функции
function WSARecv(S: TSocket; lpBuffers: PWSABuf; dwBufferCount: DWORD; var NumberOfBytesRecvd: DWORD; var Flags: DWORD; lpOverlapped: PWSAOverlapped; lpCompletionRoutine: TWSAOverlappedCompletionRoutine): Integer;
Перекрытым вводом-выводом управляют два последних параметра функции, но
WSARecv
обладает и другими дополнительными по сравнению с функцией
recv
возможностями, не связанными с перекрытым вводом-выводом. Если оба этих параметра равны
nil
, или сокет создан без указания флага
WSA_FLAG_OVERLAPPED
, функция работает в обычном блокирующем или неблокирующем режиме, который установлен для сокета. При этом ее поведение отличается от поведения функции
recv
только тремя незначительными аспектами: во-первых, вместо одного буфера ей можно передать несколько, заполняемых последовательно. Во-вторых, флаги передаются ей не как значение, а как параметр-переменная, и при некоторых условиях функция
WSARecv
может их изменять (при использовании TCP и UDP флаги никогда не меняются, поэтому мы не будем рассматривать здесь эту возможность). В-третьих, при успешном завершении функция
WSARecv
возвращает ноль, а не число прочитанных байтов (последнее возвращается через параметр
lpNumberOfBytesRecvd
).
Буферы, в которые нужно поместить данные, передаются функции
WSARecv
через параметр
lpBuffers
. Он содержит указатель на начало массива структур
TWSABuf
, а параметр
dwBufferCount
— число элементов
в этом массиве. Ранее мы знакомились со структурой
TWSABuf
(см. листинг 2.39): она содержит указатель на начало буфера и его размер. Соответственно, массив таких структур определяет набор буферов. При чтении данных заполнение буферов начинается с первого буфера в массиве
lpBuffers
, затем, если в нем не хватает места, заполняется второй буфер и т.д. Функция не переходит к следующему буферу, пока не заполнит предыдущий до последнего байта. Таким образом, данные, получаемые с помощью функции
WSARecv
, могут быть помещены в несколько несвязных областей памяти, что иногда бывает удобно, если принимаемые сообщения имеют строго определенный формат с фиксированными размерами компонентов пакета: в этом случае можно каждый компонент поместить в свой независимый буфер.
Теперь переходим непосредственно к рассмотрению перекрытого ввода-вывода на основе событий. Для реализации этого режима при вызове функции
WSARecv
параметр
lpCompletionRoutine
должен быть равен
nil
, а через параметр
lpOverlapped
передается указатель на запись
TWSAOverlapped
, которая определена следующим образом (листинг 2.69).
Листинг 2 69. Тип
TWSAOverlapped
//***** Описание на C++ *****
struct _WSAOVERLAPPED {
DWORD Internal;
DWORD InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
WSAEVENT hEvent;
} WSAOVERLAPPED, *LPWSAOVEPLAPPED;
// ***** Описание на Delphi *****
PWSAOverlapped = ^TWSAOverlapped;
TWSAOverlapped = packed record
Internal, InternalHigh, Offer, OffsetHigh: DWORD;
hEvent: TWSAEvent;
end;
Поля
Internal
,
InternalHigh
,
Offset
и
OffsetHigh
предназначены для внутреннего использования системой, программа не должна выполнять никаких действий с ними. Поле
hEvent
задает событие, которое будет взведено при завершении операции перекрытого ввода-вывода. Если на момент вызова функции
WSARecv
данные в буфере сокета отсутствуют, она вернет значение
SOCKET_ERROR
, а функция
WSAGetLastError
—
WSA_IO_PENDING
(997). Это значит, что операция начала выполняться в фоновом режиме. В этом случае функция
WSARecv
не изменяет значения параметров
NumberOfBytesRecvd
и
Flag
. Поля структуры
TWSAOverlapped
при этом также модифицируются, и эта структура должна быть сохранена программой в неприкосновенности до окончания операции перекрытого ввода-вывода. После окончания операции будет взведено событие, указанное в поле
hEvent
параметра
lpOverlapped
. При необходимости программа может дождаться этого взведения с помощью функции
WSAWaitForMultipleEvents
.
Как только запрос будет выполнен, в буферах, переданных через параметр
lpBuffers
, оказываются принятые данные. Но знания одного только факта, что запрос выполнен, недостаточно, чтобы этими данными воспользоваться, потому что, во-первых, неизвестен размер этих данных, а во-вторых, неизвестно, успешно ли завершена операция перекрытого ввода-вывода. Для получения недостающей информации служит функция