Вторая активация процедуры может прервать блокирующий вызов с помощью функции WSACancelBlockingСаll. При этом первая активация получит ошибку WSAECANCELLED.
Программа может устанавливать свою процедуру, которая будет вызываться во время выполнения блокирующей операции. Для этого предусмотрены функции WSASetBlockingHook и WSAUnhookBlockingHook.
Данная модель неудобна, поэтому разработчики WinSock 1 рекомендуют модель асинхронных сокетов, более приспособленную к особенностям Windows.
В 32-разрядных версиях WinSock такая модель работы поддерживается в полном объеме, за исключением того, что по умолчанию при блокирующем вызове не вызывается никакая функция. Поэтому если не вызове не вызывается никакая функция. Поэтому если не использовать WSASetBlockingHook, то в 32-разрядном приложении невозможно получить ситуацию, когда операция с сокетом не будет выполнена из-за того, что в этот момент уже выполняется другая операция, и второй активации оконной процедуры из-за блокирующего вызова тоже не будет создано. Отметим, что разные нити могут одновременно выполнять блокирующие операции с сокетами, и это не приведет к появлению ошибки WSAEINPROGRESS.
Все перечисленные функции формально исключены из спецификации WinSock 2, хотя фактически они присутствуют в библиотеке WS2_32.dll и при необходимости могут быть задействованы (это, правда, осложняется тем, что в новых версиях MSDN отсутствует их описание). Тем не менее причин ориентироваться на эту неудобную модель в 32-разрядных версиях Windows, видимо, нет. Описание этих функций мы здесь привели только для того, чтобы упоминания об ошибках WSAEINPROGRESS и WSAECANCELLED, которые иногда встречаются в MSDN, не смущали вас.
2.2.3. Информация о протоколе
Ранее мы уже видели, что передача данных через сокет осуществляется одними и теми же функциями независимо от протокола. Но при этом программа должна учитывать, является ли протокол потоковым, дейтаграммным или иным. Кроме того, информация о протоколе требуется для создания сокета и для распределения ролей между клиентом и сервером при установлении соединения. Чтобы работать с любым протоколом, программа должна иметь возможность получить всю эту информацию и выполнить на основе ее те или иные действия. Могут также понадобиться такие сведения, как максимальное число сокетов, поддерживаемых провайдером протокола, допустимый диапазон адресов, максимальный размер сообщений для дейтаграммных протоколов и т.д. Для хранения полного описания протокола и его провайдера в WinSock 2 предусмотрена структура WSAPROTOCOL_INFO. Она не описана в модуле WinSock, т.к. в WinSock 1 ее нет. Тем, кто захочет использовать эту структуру, придется самостоятельно добавлять ее описание в программу. Листинг 2.36 показывает, как выглядит эта структура.
Листинг 2.36. Тип WSAPROTOCOL_INFO
// ***** Описание на C++ *****
typedef struct _WSAPROTOCOLCHAIN {
int ChainLen;
DWORD ChainEntries[MAX_PROTOCOL_CHAIN];
} WSAPROTOCOLCHAIN, *LPWSAPROTOCOLCHAIN;
typedef struct _WSAPROTOCOL_INFO {
DWORD dwServiceFlags1;
DWORD dwServiceFlags2;
DWORD dwServiceFlags3;
DWORD dwServiceFlgs4;
DWORD dwProviderFlags;
GUID ProviderId;
DWORD dwCatalogEntryId;
WSAPROTOCOLCHAIN ProtocolChain;
int iVersion;
int iAddressFamily;
int iMaxSockAddr;
int iMinSockAddr;
int iSocketType;
int iProtocol;
int iProtocolMaxOffset;
int iNetworkByteOrder;
int iSecurityScheme;
DWORD dwMessageSize;
DWORD dwProviderReserved;
TCHAR szProtocol[WSAPROTOCOL_LEN - 1];
} WSAPROTOCOL_INFO, *LPWSAPROTOCOL_INFO;
// ***** Описание на Delphi *****
TWSAProtocolChain = packed record
ChainLen: Integer;
ChainEntries: array[0..MAX_PROTOCOL_CHAIN - 1] of DWORD;
end;
//Структура на C++ содержит тип TCHAR, который, как мы
// говорили в главе 1, может означать как Char,
// так и WideChar, т.е. структура должна иметь
// два варианта описания: TWSAProtocolInfoA для
// однобайтной кодировки и TWSAProtocolInfo для
// двухбайтной. Соответственно, все функции
// использующие эту структуру, реализованы
// в системных библиотеках в двух вариантах.
// Здесь мы приводим только ANSI-вариант.
PWSAProtocolInfo = ^TWSAProtocolInfo;
TWSAProtocolInfo = packed record
dwServiceFlags1: DWORD;
dwServiceFlags2: DWORD;
dwServicsFlags3: DWORD;
dwServiceFlags4: DWORD;
dwProviderFlags: DWORD;
ProviderId: GUID;
dwCatalogEntryId: DWORD;
ProtocolChain: TWSAProtocolChain;
iVersion: Integer;
iAddressFamily: Integer;
iMaxSockAddr: Integer;
iMinSockAddr: Integer;
iSocketType: Integer;
iProtocol: Integer;
iProtocolMaxOffset: Integer;
iNetworkByteOrder: Integer;
iSecurityScheme: Integer;
dwMessageSize: DWORD;
dwProviderReserved: DWORD;
szProtocol: array [0..WSAPROTOCOL_LEN] of Char;
end;
Расшифровка полей типа TWSAProtocolInfo есть в MSDN, мы здесь не будем ее приводить.
Сама функция WSAEnumProtocols, которая позволяет получить список всех протоколов, провайдеры которых установлены на компьютере, приведена в листинге 2.37.
Листинг 2.37. Функция WSAEnumProtocols
// ***** описание на C++ *****
int WSAEnumProtocols(LPINT lpiProtocols, LPWSAPROTOCOL_INFO lpProtocolBuffer, LPDWORD lpdwBufferLength);
// ***** Описание на Delphi *****
function WSAEnumProtocols(lpiProtocols: PInteger; lpProtocolBuffer: PWSAProtocolInfo; var BufferLength: DWORD): Integer;
Примечание
В старых версиях MSDN в описании этой функции есть небольшая опечатка: тип параметра lpdwBufferLength назван LLPDWORD вместо LPDWORD.
Библиотека WS2_32.dll придерживается тех же правил насчет ANSI- и Unicode-вариантов функций, что и другие системные библиотеки (см. разд. 1.1.12), поэтому в ней нет функции с именем WSAEnumProtocols, а есть WSAEnumProtocolsA и WSAEnumProtocolsW. Эти функции работают с разными вариантами структуры WSAPROTOCOL_INFO, которые различаются типом элементов в последнем массиве — CHAR или WCHAR.
Параметр lpiProtocols указывает на первый элемент массива, содержащего список протоколов, информацию о которых нужно получить. Если этот указатель равен nil, то возвращается информация обо всех доступных протоколах. Параметр lpProtocolBuffer содержит указатель на начало массива структур типа TWSAProtocolInfo. Программа должна заранее выделить память под этот массив. Параметр BufferLength при вызове должен содержать размер буфера lpProtocolBuffer в байтах (именно размер в байтах, а не количество элементов). После завершения функции сюда помешается минимальный размер буфера, необходимый для размещения информации обо всех запрошенных протоколах. Если это значение больше переданного, функция завершается с ошибкой.
Если параметр lpiProtocols не равен нулю, он должен содержать указатель на массив, завершающийся нулем. Следовательно, если количество протоколов, запрашиваемых программой, равно N, этот массив должен состоять из N+1 элементов, и первые N элементов должны содержать номера протоколов, а последний элемент — ноль.
В системе может быть установлено несколько провайдеров для одного протокола. В этом случае информация о каждом провайдере будет помещена в отдельный элемент массива. Из-за этого число задействованных элементов в массиве lpProtocolBuffer может превышать количество протоколов, определяемых параметром lpiProtocols.
К сожалению, полную информацию о том, каким протоколам какие номера соответствуют, в документации найти не удалось. Можно только сказать, что для получения информации о протоколе TCP в массив lpiProtocols необходимо поместить константу IPPROTO_TCP, о протоколе UDP — константу IPPROTO_UDP.
Возвращаемое функцией значение равно числу протоколов, информация о которых помещена в массив, если функция выполнена успешно, и SOCKET_ERROR, если при ее выполнении возникла ошибка. Конкретная ошибка определяется стандартным методом, с помощью WSAGetLastError. Если массив lpProtocolBuffer слишком мал для хранения всей требуемой информации, функция завершается с ошибкой WSAENOBUFS.
WinSock 1 содержит аналогичную по возможности функцию EnumProtocols, возвращающую массив структур PROTOCOL_INFO. Эта структура содержит меньше информации о протоколе, чем WSAPROTOCOL_INFO и, в отличие от последней, не используется никакими другими функциями WinSock. Несмотря на то, что функция EnumProtocols и структура PROTOCOL_INFO описаны в первой версии WinSock, модуль WinSock их не импортирует, при необходимости их нужно импортировать самостоятельно. Но функция EnumProtocols считается устаревшей, использовать ее в новых приложениях не рекомендуется, поэтому практически всегда, за исключением редких случаев, требующих совместимости с WinSock 1, лучше выбрать более современную функцию WSAEnumProtocols.