Буферы, в которые нужно поместить данные, передаются функции
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
, оказываются принятые данные. Но знания одного только факта, что запрос выполнен, недостаточно, чтобы этими данными воспользоваться, потому что, во-первых, неизвестен размер этих данных, а во-вторых, неизвестно, успешно ли завершена операция перекрытого ввода-вывода. Для получения недостающей информации служит функция
function WSAGetOverlappedResult(S: TSocket; lpOverlapped: PWSAOverlapped; var cbTransfer: DWORD; fWait: BOOL; var Flags: DWORD): BOOL;
Параметры
S
и
lpOverlapped
функции
WSAGetOverlappedResult
определяют coкет и операцию перекрытого ввода-вывода, информацию о которой требуется получить. Их значения должны совпадать со значениями соответствующих параметров, переданных функции
WSARecv
. Через параметр
cbTransfer
возвращается число полученных байтов, а через параметр
Flags
— флаги (напомним, что в случае TCP и UDP флаги не модифицируются, и выходное значение параметра
Flags
будет равно входному значению параметра
Flags
функции
WSARecv
).
Допускается вызов функции
WSAGetOverlappedResult
до того, как операция перекрытого ввода-вывода будет завершена. В этом случае поведение функции зависит от параметра
fWait
. Если он равен
True
, функция переводит нить в состояние ожидания до тех пор, пока операция не будет завершена. Если он равен
False
, функция завершается немедленно с ошибкой
WSA_IO_INCOMPLETE
(996).
Функция
WSAGetOverlappedResult
возвращает
True
, если операция перекрытого ввода-вывода успешно завершена, и
False
, если произошли какие-то ошибки. Ошибка может возникнуть в одном из трех случаев:
1. Операция перекрытого ввода-вывода еще не завершена, а параметр
fWait
равен
False
.
2. Операция перекрытого ввода-вывода завершилась с ошибкой (например, из-за разрыва связи).
3. Параметры, переданные функции
WSAGetOverlappedResult
, имеют некорректные значения.
Точную причину, по которой функция вернула
False
, можно установить стандартным образом — по коду ошибки, возвращаемому функцией
WSAGetLastError
.
В принципе, программа может вообще не использовать события для отслеживания завершения операции ввода-вывода, а вызывать вместо этого время от времени функцию
WSAGetOverlappedResult
в удобные для себя моменты. Тогда при вызове функции
WSARecv
можно указать нулевое значение события
hEvent
. Но следует иметь в виду, что при вызове функции
WSAGetOverlappedResult
с параметром
fWait
, равным
True
, указанное событие служит для ожидания завершения операции, и если событие не задано, возникнет ошибка. Таким образом, если событие не используется, функция
WSAGetOverlappedResult
не может вызываться в режиме ожидания.
Отдельно рассмотрим ситуацию, когда на момент вызова функции
WSARecv
с ненулевым параметром
lpOverlapped
во входном буфере сокета есть данные. В этом случае функция отработает так же, как и в неперекрытом режиме, т.е. изменит значения параметров
NumberOfBytesRecvd
и
Flags
и вернет ноль, свидетельствующий об успешном выполнении функции. Но при этом событие будет взведено, а в структуру
lpOverlapped
будет внесена вся необходимая информация. Благодаря этому последующие вызовы функций
WSAWaitForMultipleEvents
и
WSAGetOverlappedResult
будут выполняться корректно, т.е. таким образом, как если бы функция
WSARecv
завершилась с ошибкой
WSA_IO_PENDING
, и сразу после этого в буфер сокета поступили данные. Это позволяет выполнить обработку результатов операций перекрытого ввода-вывода с помощью одного и того же кода независимо от того, были ли в буфере сокета данные на момент начала операции или нет.
Новая операция перекрытого ввода-вывода может быть начата до того, как закончится предыдущая. Это удобно при работе с несколькими сокетами: можно выполнять операции с ними параллельно в фоновом режиме, получая уведомления о завершении каждой из операций.
В MSDN не написано явно, что будет, если вызвать для сокета функцию
WSARecv
повторно, до того как будет завершена предыдущая операция перекрытого чтения (но запрета на такие действия тоже нет). Эксперименты показывают, что в этом случае операции перекрытого чтения встают в очередь, т.е. первый полученный сокетом пакет приводит к завершению операции, начатой первой, второй пакет — к завершению операции, начатой второй, и т.д. Но поскольку это явно не документировано, лучше не полагаться на то, что такой порядок будет всегда соблюдаться.