Параметр g выходной, он позволяет управлять присоединением создаваемого функцией WSAAccept сокета к группе. Параметр, как и все, связанное с группами, зарезервирован для использования в будущем.
Примечание
Если вы пользуетесь старой версией MSDN, то можете не обнаружить там описания параметра g — оно там отсутствует. Видимо, просто по ошибке.
И наконец, через параметр dwCallbackData в функцию обратного вызова передается значение параметра dwCallbackData, переданное в функцию WSAAccept. Программист должен сам решить, как ему интерпретировать это значение.
Функция должна вернуть CF_ACCEPT (0), если соединение принимается, CF_REJECT (1), если оно отклоняется, и CF_DEFER (2), если решение о разрешении или запрете соединения откладывается. Если функция обратного вызова вернула CF_REJECT, to WSAAccept завершается с ошибкой WSAECONNREFUSED, если CF_DEFER — то с ошибкой WSATRY_AGAIN (в последнем случае соединение остаётся в очереди, и информация о нем вновь будет передана в функцию обратного вызова при следующем вызове WSAAccept). Обе эти ошибки не фатальные, сокет остается в режиме ожидания соединения и может принимать подключения от новых клиентов.
Ранее уже обсуждалось, что функция connect на стороне клиента считается успешно завершенной тогда, когда соединение встало в очередь, а не тогда, когда оно реально принято сервером через функцию accept. По умолчанию для клиента, соединение с которым сервер отклонил, нет разницы, вызвал ли сервер функцию WSAAccept и сразу отклонил соединение, или установил его с помощью accept, а потом разорвал. В обоих случаях клиент сначала получит информацию об успешном соединении с сервером, а потом это соединение будет разорвано. Но при использовании WSAAccept можно установить такой режим работы, когда сначала выполняется функция. заданная параметром lpCondition, и лишь потом клиенту отправляется разрешение или запрет на подключение. Включается этот режим установкой параметра слушающего сокета SO_CONDITIONAL_ACCEPT, что иллюстрирует листинг 2.42.
Листинг 2.42. Включение режима ожидания реального подключения
var
Cond: BOOL;
begin
Cond := True;
setsockopt(S, SOL_SOCKET, SO_CONDITIONAL_ACCEPT, PChar(@Cond), SizeOf(Cond));
Этот режим снижает нагрузку на сеть и повышает устойчивость сервера против DoS-атак, заключающихся в многократном подключении-отключении посторонних клиентов, поэтому в серьезных серверах рекомендуется использовать эту возможность.
Из сказанного следует, что при использовании протокола TCP функция WSAAccept по сравнению с accept даёт два принципиальных преимущества: позволяет управлять качеством обслуживания и запрещать подключение нежелательных клиентов.
Некоторые протоколы поддерживают передачу информации не только при установлении связи, но и при её завершении. Для таких протоколов в WinSock2 предусмотрены функции WSASendDisconnect и WSARecvDisconnect. Так как протокол TCP не поддерживает передачу данных при закрытии соединения, для него эти функции не дают никаких преимуществ по сравнению с вызовом функции shutdown, поэтому мы не будем их здесь рассматривать.
Далее мы рассмотрим несколько новых функций, унифицирующих работу с различными протоколами.
Функция inet_addr, как это уже упоминалось, жестко связана с протоколом IP и не имеет смысла для других протоколов. WinSock 2 предлагает вместо нее функцию WSAStringToAddress, имеющую следующий прототип (листинг 2.43).
Листинг 2.43. Функция WSAStringToAddress
// ***** Описание на C++ *****
INT WSAStringToAddress(LPTSTR AddressString, INT AddressFamily, LPWSAPROTOCOL_INFO lpProtocolInfo, LPSOCKADDR lpAddress, LPINT lpAddressLength);
// ***** Описание на Delphi *****
function WSAStringToAddress(AddresString: PChar; AddressFamily: Integer; lpProtocolInfo: PWSAProtocolInfo; var Address: TSockAddr; var AddressLength: Integer): Integer;
Данная функция преобразует строку, задающую адрес сокета, в адрес, хранящийся в структуре TSockAddr. Параметр AddressString указывает на строку, хранящую адрес, параметр AddressFamily — на семейство адресов, для которого осуществляется трансляция. Если есть необходимость выбрать конкретный провайдер для протокола, в функцию может быть передан параметр lpProtocolInfo, в котором указан идентификатор провайдера. Если же программу устраивает провайдер по умолчанию, параметр lpProtocolInfo должен быть равен nil. Адрес возвращается через параметр Address. Параметр AddressLength при вызове функции должен содержать размер буфера, переданного через Address, а на выходе содержит реально использованное число байтов в буфере.
Функция возвращает 0 в случае успешного выполнения и SOCKET_ERROR — при ошибке.
Допустимый формат строки определяется протоколом (некоторые протоколы вообще не поддерживают текстовую запись адреса, и для них функция WSAStringToAddress неприменима). Для семейства AF_INET, к которому относятся TCP и UDP, адрес может задаваться в виде "IP1.IP2.IP3.IР4:Port" или "IP1.IP2.IP3.IP4", где IРn — n-й компонент IP-адреса, записанною в виде 4-байтных полей, Port — номер порта. Если порт явно не указан, устанавливается нулевой номер порта.
Таким образом, чтобы в структуре TSockAddr оказался, например, адрес 192.168.100.217 и порт с номером 5000, необходимо выполнить следующий код (листинг 2.44).
Листинг 2.44. Пример использования функции WSAStringToAddress
var
Addr: TSockAddr;
AddrLen: Integer;
begin
AddrLen := SizeOf(Addr);
WSAStringToAddress('192.168.100.217:5000', AF_INET, nil, Addr, AddrLen);
Существует также функция WSAAddressToString, обратная к WSAStringToAddrеss. Ее прототип приведен в листинге 2.45.
Листинг 2.45. Функция WSAAddressToString
// ***** Описание на C++ *****
INT WSAAddressToString(LPSOCKADDR lpsaAddress, DWORD dwAddressLength, LWSAPROTOCOL_INFO lpProtocolInfo, LPTSTR lpszAddressString, LPDWORD lpdwAddressStringLength);
// ***** Описание на Delphi *****
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.