function WSAAddressToString(var Address: TSockAddr; dwAddressLength: DWORD; lpProtocolInfo: PWSAProtocolInfo; lpszAddressString: PChar; var AddressStringLength: DWORD): Integer;
Как нетрудно догадаться по названию функции, она преобразует адрес, заданный структурой
TSockAddr
, в строку. Адрес задаётся параметром
Address
, параметр
dwAddressLength
определяет длину буфера
Address
. Необязательный параметр
lpProtocolInfo
содержит указатель на структуру
TWSAProtocolInfo
, с помощью которой можно определить, какой именно провайдер должен выполнить преобразование. Параметр
lpszAddressString
содержит указатель на буфер, заранее выделенный программой, в который будет помещена строка. Параметр
AddressStringLength
на входе должен содержать
размер буфера, заданного параметром
lpszAddressString
, а на выходе содержит длину получившейся строки.
Функция возвращает ноль в случае успеха и
SOCKET_ERROR
— при ошибке. Ранее мы уже обсуждали различные форматы представления целых чисел, а также то, что формат, предусмотренный сетевым протоколом, может не совпадать с форматом, используемым узлом. Напомним, что для преобразования из сетевого формата в формат узла предназначены функции
htons
,
ntohs
,
htonl
и
ntohl
, привязанные к протоколам стека TCP/IP (другие протоколы могут иметь другой формат представления чисел). WinSock 2 предлагает аналоги этих функций
WSAHtons
,
WSANtohs
,
WSAHtonl
и
WSANtohl
, которые учитывают особенности конкретного протокола. Мы здесь рассмотрим только функцию
WSANtohl
, преобразующую 32-битное целое из сетевого формата в формат узла. Остальные три функции работают аналогично. Листинг 2.46 содержит прототип функции
WSANtohl
.
Листинг 2.46. Функция
WSANtohl
// ***** Описание на C++ *****
int WSANtohl(SOCKET s, u_long netlong, u_long FAR *lphostlong);
// ***** Описание на Delphi *****
function WSANtohl(S: TSocket; NetLong: Cardinal; var HostLong: Cardinal): Integer;
Параметр
S
задает сокет, для которого осуществляется преобразование. Так как сокет всегда связан с конкретным протоколом, этого параметра достаточно, чтобы библиотека могла определить, по какому закону преобразовывать число из сетевого формата в формат хоста. Число в сетевом формате задаётся параметром
NetLong
, результат преобразования помещается в параметр
HostLong
.
Функция возвращает ноль в случае успешного выполнения операции и
SOCKET_ERROR
— при ошибке.
Если программа работает только с протоколами стека TCP/IP, старые варианты функций удобнее новых, потому что возвращают непосредственно результат преобразования, который можно использовать в выражениях. При работе с новыми функциями для получения результата следует заводить отдельную переменную, поэтому эти функции целесообразны тогда, когда программа должна единым образом работать с разными протоколами. Последняя функция, которую мы здесь рассмотрим, не имеет прямых аналогов среди старых функций. Называется она
WSADuplicateSocket
и служит для копирования дескриптора сокета в другой процесс. Прототип функции
WSADuplicateSocket
приведен в листинге 2.47.
Листинг 2.47. Функция
WSADuplicateSocket
// ***** Описание на C++ *****
int WSADuplicateSocket(SOCKET s, DWORD dwProcessId, LPWSAPROTOCOL_INFO lpProtocolInfo);
// ***** Описание на Delphi *****
function WSADuplicateSocket(S: TSocket; dwProcessID: DWORD; var ProtocolInfo: TWSAProtocolInfo): Integer;
Параметр
S
задает сокет, дескриптор которого нужно скопировать, параметр
dwProcessID
— идентификатор процесса, для которого предназначена копия, функция помещает в структуру
ProtocolInfo
информацию, необходимую для создания копии дескриптора другим процессом. Затем эта структура должна быть каким-то образом передана другому процессу, который передаст ее в функцию
WSASocket
и получит свою копию дескриптора для работы с данным сокетом.
Функция
WSADuplicateSocket
возвращает ноль при успешном завершении и
SOCKET_ERROR
— в случае ошибки. Как мы помним, сокет является объектом, внутренняя структура которого остается скрытой от использующей его программы. Программа манипулирует только дескриптором сокета — некоторым уникальным идентификатором этого объекта. Функция
WSADuplicateSocket
позволяет другой программе получить новый дескриптор для уже существующего сокета. Старый и новый дескриптор становятся равноправными. Чтобы освободить
сокет, нужно закрыть все его дескрипторы с помощью функции
closesocket
. Если во входной буфер сокета поступают данные, их получит та программа, которая первой вызовет соответствующую функцию чтения, поэтому совместное использование одного сокета разными программами требует синхронизации их работы. MSDN рекомендует такую схему работы, при которой одна программа только создаёт сокет и устанавливает соединение, а затем передает сокет другой программе, которая реализует через него ввод-вывод. Первая программа при этом закрывает свой дескриптор. Такой алгоритм работы позволяет полностью исключить проблемы, возникающие при совместном доступе разных программ к одному сокету.
Отметим, что функция
WSADuplicateSocket
может быть полезна только для копирования дескрипторов между разными процессами. Разные нити одного процесса не нуждаются в этой функции, т.к., находясь в одном адресном пространстве, они могут работать с одним и тем же дескриптором.
2.2.5. Асинхронный режим, основанный на сообщениях
Все операции с сокетами, которые мы рассматривали раньше, являлись синхронными. Программа, использующая такие сокеты, должна сама время от времени проверять тем или иным способом, пришли ли данные, установлена ли связь и т.п. Асинхронные сокеты позволяют программе получать уведомления о событиях, происходящих с сокетом: поступлении данных, освобождении места в буфере, закрытии и т.п. Такой способ работы лучше подходит для событийно-ориентированных программ, типичных для Windows. Поддержка асинхронных сокетов впервые появилась в WinSock 1 и была основана на сообщениях, которые обрабатывались оконными процедурами. В WinSock 2 этот асинхронный режим остался без изменений. Программист указывает, какое сообщение какому окну должно приходить при возникновении события на интересующем его сокете.
Асинхронный режим с уведомлением через сообщения устанавливается функцией
WSAAsyncSelect
, имеющей следующий прототип:
function WSAAsyncSelect(S: TSocket; HWindow: HWND; wMsg: u_int; lEvent: LongInt): Integer;
Параметр
S
определяет сокет, для которого устанавливается асинхронный режим работы. Параметр
HWindow
— дескриптор окна, которому будут приходить сообщения,
wMsg
— сообщение, a
lEvent
задает события, которые вызывают отправку сообщения. Для этого параметра определены константы, комбинация которых задает интересующие программу события. Мы не будем рассматривать здесь все возможные события, остановимся только на самых главных (табл. 2.2).
Таблица 2.2. Асинхронные события сокета
Событие
Комментарий
FD_READ
Сокет готов к чтению
FD_WRITE
Сокет готов к записи
FD_ACCEPT
В очереди сокета есть подключения (применимо только для сокетов, находящихся в режиме ожидания подключения)
FD_CONNECT
Соединение установлено (применимо только для сокетов, для которых вызвана функция
connect
или аналогичная ей)
FD_CLOSE
Соединение закрыто
Каждый последующий вызов
WSAAsyncSelect
для одного и того же сокета отменяет предыдущий вызов. Таким образом, в результате выполнения следующего кода форма будет получать только сообщения, показывающие готовность сокета к чтению, а готовность к записи не приведет к отправке сообщения (листинг 2.48).
// Теперь окно не будет получать уведомления о возможности записи
WSAAsyncSelect
связывает с сообщением именно сокет, а не его дескриптор. Это означает, что если две программы используют один сокет (копия дескриптора которого была создана с помощью функции
WSADuplicateSocket
), и первая программа вызывает
WSAAsyncSelect
со своим дескриптором, а затем вторая — со своим, то вызов
WSAAsyncSelect
, сделанный во второй программе, отменит вызов, сделанный в первой.