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
, сделанный во второй программе, отменит вызов, сделанный в первой.
Для того, чтобы получать сообщения при готовности сокета как к чтению, так и к записи, нужно выполнить следующий код.
WSAAsyncSelect(S, Form1.Handle, WM_USER, FD_READ or FD_WRITE);
При необходимости с помощью
or
можно комбинировать и большее число констант.
Из сказанного следует, что нельзя связать с разными событиями одного и того же сокета разные сообщения (или отправлять сообщения разным окнам), т.к. при одном вызове
WSAAsyncSelect
можно передать только один дескриптор окна и один номер сообщения, а следующий вызов этой функции, с другим дескриптором и/или номером, отменит предыдущий. Функция
WSAAsyncSelect
переводит сокет в неблокирующий режим. Если необходимо использовать асинхронный сокет в блокирующем режиме, после вызова
WSAAsyncSelect
требуется перевести его в этот режим вручную.
Сообщение, которое связывается с асинхронным сокетом, может быть любым. Обычно его номер выбирают от
WM_USER
и выше, чтобы исключить путаницу со стандартными сообщениями.
При получении сообщения его параметр
wParam
содержит дескриптор сокета, на котором произошло событие. Младшее слово
lParam
содержит произошедшее событие (одну из констант
FD_XXX
), а старшее слово — код ошибки если она произошла. Для выделения кода события и кода ошибки из
lParam
в библиотеке WinSock предусмотрены макросы
WSAGETSELECTEVENT
и
WSAGETSELECTERROR
соответственно. В модуле WinSock они заменены функциями
WSAGetSelectEvent
и
WSAGetSelectError
. Одно сообщение может информировать только об одном событии на сокете. Если произошло несколько событий, в очередь окна будет добавлено несколько сообщений.
Сокет, созданный при вызове функции
accept
, наследует режим того сокета, который принял соединения. Таким образом, если сокет, находящийся в режиме ожидания подключения, является асинхронным, то и сокет, порожденный функцией
accept
, будет асинхронным, и тот же набор его событий будет связан с тем же сообщением, что и у исходного сокета.
Рассмотрим подробнее каждое из перечисленных событий.
Событие
FD_READ
возникает, когда во входной буфер сокета поступают данные (если на момент вызова
WSAAsyncSelect
, разрешающего такие события, в буфере сокета уже есть данные, то событие также возникает). Как только соответствующее сообщение помещается в очередь окна, дальнейшая генерация таких сообщений для этого сокета блокируется, т.е. получение новых данных не будет приводить к появлению новых сообщений (при этом сообщения, связанные с другими событиями этого сокета или с событием
FD_READ
других сокетов, будут по-прежнему помещаться при необходимости в очередь окна). Генерация сообщений снова разрешается после того, как будет вызвана функция для чтения данных из буфера сокета (это может быть функция
recv
,
recvfrom
,
WSARecv
или
WSARecvFrom
, мы в дальнейшем будем говорить только о функции
recv
, потому что остальные ведут себя в этом отношении аналогично).