Книга: Сетевое программирование. От основ до приложений
Назад: Глава 17. Альтернативы сокетам в ОС Windows
Дальше: Резервирование портов

Глава 18. Управление сетью в ОС Windows

Если вы думаете, что это просто, значит, вы неправильно поняли проблему.

Бьерн Страуструп, «FAQ: Did you really say that?», 2007

Введение

В этой главе мы сосредоточим внимание на параметрах сокетов, устройств и интерфейсов в ОС Windows.

Система предоставляет разнообразные API для управления поведением стека, что позволяет разработчикам создавать гибкие и эффективные сетевые приложения под различные потребности.

Так же как и в POSIX-системах, различными атрибутами сокета можно управлять, используя функции getsockopt() и setsockopt(). Опции сокетов разделяются по уровням.

Кроме того, существует набор вызовов ioctl. Они позволяют управлять дескриптором, уведомлениями, сетевыми интерфейсами, настройками протоколов и подсистем, портами и многим другим.

В дополнение к вызовам ioctl существуют функции для управления параметрами драйверов сетевых устройств и самими устройствами, а также дескрипторами.

Для удобства работы в API Windows предоставляются функции-обертки над опциями. В этой главе мы рассмотрим несколько примеров с использованием функций-оберток и расскажем, как этими функциями пользоваться.

Так же как и в POSIX API, в WinSock есть возможность работать со вспомогательными данными, которые используются с некоторыми параметрами сокетов и протоколов.

Данную главу можно использовать в качестве справочника при дальнейшей работе с книгой.

Функции-обертки над опциями

WinSock предоставляет удобные функции-обертки, которые облегчают работу с часто используемыми параметрами. Большая часть из таких функций объявлена в заголовочном файле ws2tcpip.h.

Для чего нужны обертки, хорошо видно на примере функции установки MTU:

int WSASetIPUserMtu(SOCKET Socket, DWORD Mtu);

{

    WSAPROTOCOL_INFOW Info;

    int InfoSize = sizeof(Info);

    int Error;

 

    // Получить информацию о сокете.

    Error = getsockopt(Socket, SOL_SOCKET, SO_PROTOCOL_INFO, (PCHAR)&Info,

                       &InfoSize);

    if (Error != SOCKET_ERROR)

    {

        // Определить, какое семейство адресов использует сокет.

        if (Info.iAddressFamily == AF_INET)

        {

            // Установить MTU для IPv4-сокета.

            Error = setsockopt(Socket, IPPROTO_IP, IP_USER_MTU,

                               (PCHAR)&Mtu, sizeof(Mtu));

#if(_WIN32_WINNT >= 0x0501)

        }

        else if (Info.iAddressFamily == AF_INET6)

        {

            // Установить MTU для IPv6-сокета.

            Error = setsockopt(Socket, IPPROTO_IPV6, IPV6_USER_MTU,

                               (PCHAR)&Mtu, sizeof(Mtu));

#endif //(_WIN32_WINNT >= 0x0501)

        }

        else

        {

            Error = SOCKET_ERROR;

            // Установить ошибку, если тип сокета не поддерживается.

            WSASetLastError(WSAEAFNOSUPPORT);

        }

    }

 

    return Error;

}

Функция универсальная: она работает верно, несмотря на некоторую избыточность: проверяет тип сокета, устанавливает системную ошибку, если он не поддерживается, и т.д.

С другой стороны, существуют функции, которые просто вызывают тот же setsockopt(), но с правильно заданными параметрами.

Например:

int WSASetFailConnectOnIcmpError(SOCKET Socket, DWORD Enabled);

{

    return setsockopt(Socket, IPPROTO_TCP, TCP_FAIL_CONNECT_ON_ICMP_ERROR,

                      (CHAR*)&Enabled, sizeof(Enabled));

}

Большинство функций возвращают код ошибки getsockopt() либо WSAIoctl(). Иногда — SOCKET_ERROR, если операция не поддерживается сокетом.

Использовать данные функции или нет, зависит от конкретного сценария. Если требуется реализовать кросс-платформенный или более эффективный код, иногда лучше напрямую работать с опциями или ioctl. С другой стороны, функции-обертки могут упростить работу с параметрами сокета в ОС Windows, ускоряют разработку и сокращают объем кода.

Некоторые из функций-оберток мы будем рассматривать вместе с опциями и ioctl, которые они используют.

Получение и установка MTU

Функции WSAGetIPUserMtu() и WSASetIPUserMtu() позволяют получить и задать значение пользовательского MTU, то есть максимальное количество байтов, которое может быть передано в одном IP-пакете без фрагментации:

int WSAGetIPUserMtu(SOCKET Socket, DWORD *Mtu);

int WSASetIPUserMtu(SOCKET Socket, DWORD Mtu);

Параметры функций:

Socket — дескриптор сокета.

Mtu — указатель или значение MTU.

Функции возвращают 0 в случае успеха или код ошибки в случае неудачи.

Проверка на исполнение блокирующего вызова

Функция WSAIsBlocking() позволяет определить, выполняется ли в данный момент блокирующий вызов Windows Sockets 1.1:

bool WSAIsBlocking();

Данная функция устарела.

Управление сокетами

POSIX-совместимые функции getsockopt() и setsockopt(), которые мы рассматривали в главе 8, присутствуют и в ОС Windows.

Функций ioctl() и fcntl() в ОС Windows нет. Их заменяют функции ioctlsocket() и WSAIoctl().

Кроме того, существует несколько функций, управляющих дескрипторами. Но сначала рассмотрим функции, управляющие сокетами.

Функции setsockopt() и getsockopt()

Прототипы функций получения и установки опций:

#include <winsock2.h>

#include <Mswsock.h>

 

int WSAAPI getsockopt(

    SOCKET s,

    int level,

    int optname,

    char *optval,

    int *optlen

);

 

int WSAAPI setsockopt(

    SOCKET s,

    int level,

    int optname,

    const char *optval,

    int optlen

);

Видно, что типы аргументов отличаются: в POSIX опция является указателем на void, а ее размер имеет тип socklen_t.

Функции возвращают 0 в случае успеха и SOCKET_ERROR в случае ошибки.

Названия констант для параметра level те же, что и в POSIX. Например, для уровня сокета это SOL_SOCKET.

Мы не будем рассматривать опции уровней IPPROTO_RM, начинающихся с RM_, NSPROTO_IPX, начинающихся с IPX_, SOL_APPLETALK, SOL_IRLMP и подобные. Большая часть опций рассмотрена в главе 8, а некоторые — в главах, где эти константы используются.

Полный список уровней также содержится в MSDN, в подразделе Socket Options в WinSock 2:

https://learn.microsoft.com/en-us/windows/win32/winsock/socket-options

Внимание! Далее рассмотрены только опции, специфичные для ОС Windows. Про общие для обеих систем опции читайте в главе 8 либо в MSDN.

Уровень сокета

Для работы с опциями, которые описаны ниже, параметр level должен быть равен SOL_SOCKET.

В MSDN эти опции легко найти по такому заголовку, как SOL_SOCKET Socket Options: https://docs.microsoft.com/ru-ru/windows/win32/winsock/sol-socket-socket-options.

Опции только для чтения:

SO_BSP_STATE — получить локальный адрес, локальный порт, удаленный адрес, удаленный порт, тип сокета и протокол, используемый сокетом. Параметр опции — указатель на структуру CSADDR_INFO, буквально содержащую те параметры, которые опция возвращает.

• SO_CONNECT_TIME — количество секунд, в течение которых сокет был подключен. Используется для сокетов, ориентированных на соединение. Сервер может проверить время подключения клиентских сокетов, ожидающих AcceptEx(), чтобы предотвратить атаку на перенасыщение ожидающими соединениями: если соединение «висит» слишком долго, оно просто может быть отключено.

• SO_PROTOCOL_INFO — возвращает структуру WSAPROTOCOL_INFO, которая содержит домен, тип, протокол сокета, GUID используемого сервис-провайдера, флаги, определяющие поведение сокета, и многие другие его параметры.

SO_PROTOCOL_INFOA и SO_PROTOCOL_INFOW — то же, что SO_PROTOCOL_INFO, но возвращают структуры WSAPROTOCOL_INFOA и WSAPROTOCOL_INFOW соответственно.

Опции прослушивающего сокета:

SO_PAUSE_ACCEPT — если опция установлена, сокет перестанет отвечать на входящие соединения, отправляя RST.

• SO_OPENTYPE — влияет на то, будут ли последующие создаваемые сокеты использовать overlapped API. Флаг для чтения и записи. Возможные значения:

• SO_SYNCHRONOUS_ALERT — использовать перекрывающийся режим.

• SO_SYNCHRONOUS_NONALERT — не использовать перекрывающийся режим.

Внимание! Вместо опции SO_OPENTYPE, которая устарела, используйте функцию WSASocket() и бит WSA_FLAG_OVERLAPPED в параметре dwFlags.

Опции управления портами:

SO_REUSE_MULTICASTPORT — если этот флаг установлен, сокет не будет использоваться для получения одноадресных пакетов, то есть его порт можно будет совместно использовать с другими приложениями, предназначенными только для многоадресной рассылки.

• SO_REUSE_UNICASTPORT — разрешить повторное использование эфемерного порта для функций с явной привязкой к порту типа ConnectEx(). Для функций с неявной привязкой, например connect(), этот параметр установлен по умолчанию.

SO_PORT_SCALABILITY — при включении для каждого из локальных IP-адресов можно использовать по 64 тысячи портов. Полезно для прокси-серверов, которые могут быстро исчерпать локальные порты. Чтобы решить проблему, к машине можно добавить IP-адреса. По умолчанию порты с подстановочными знаками, используемые bind(), ограничены размером диапазона портов на все IP-адреса: до 64 тысяч портов, а обычно меньше. На платформах, где доступны оба параметра, вместо этого параметра рекомендуется использовать SO_REUSE_UNICASTPORT.

Если установить большое число TCP-соединений, исчерпаются порты и для следующих подключений система вернет ошибку WSAENOBUFS. Хотя жесткий лимит — 64 тысячи портов, обычно это не более 16 тысяч.

Один вариант — привязать пространство портов к IP-адресу, что и делает опция SO_PORT_SCALABILITY, другой — использовать функции установки соединения, требующие явной привязки адреса. В случае обычного вызова bind(), показанного на рис. 18.1, порт и адрес сразу присваиваются сокету.

Рис. 18.1. Обычное поведение функции bind()

При использовании таких функций, как, например, ConnectEx(), которую мы рассмотрим в книге 2, и включенной опции SO_REUSE_UNICASTPORT эфемерный порт выделяется не в момент вызова bind(), а в момент подключения, то есть вызова функции ConnectEx(), как показано на рис. 18.2.

Рис. 18.2. Присвоение адреса при включенной опции SO_REUSE_UNICAST_PORT

Опции для управления приемом клиентов и обновлением параметров сокета:

SO_CONDITIONAL_ACCEPT — разрешить принимать или отклонять входящие подключения приложением, а не сетевым стеком. Опция весьма полезна для некоторых серверов. Если она включена, соединение не подтверждается, пока приложение не вернет управление из обработчика, автоматически вызываемого на каждое подключение. В случае, если приложение сделало вызов WSAAccept(), обработчик или функция условного принятия должны вернуть CF_ACCEPT, чтобы подтвердить соединение, или CF_REJECT, чтобы отклонить его. Если приложение не отправит подтверждение своевременно, соединение завершится с ошибкой WSAETIMEDOUT.

• SO_UPDATE_ACCEPT_CONTEXT — обновлять контекст сокета из accept() контекстом прослушивающего сокета из вызова listen(). Иными словами, если опция установлена, новые сокеты, которые были приняты с прослушивающего сокета, унаследуют его опции. Эта опция должна быть включена, чтобы сокеты наследовали параметры QoS, а также работали функции getsockname(), getpeername() и getsockopt()/setsockopt() на принятом функцией соединении.

SO_UPDATE_CONNECT_CONTEXT — опция используется с функциями ConnectEx(), WSAConnectByList() и WSAConnectByName(). Эта опция обновляет свойства сокета после установления соединения. Ее следует установить, если в подключенном сокете будут использоваться функции getpeername(), getsockname(), getsockopt(), setsockopt() или shutdown(), для обновления контекста нового сокета аналогично предыдущей.

Опции управления провайдером и завершением соединения:

PVD_CONFIG — позволяет задать параметры для сервис-провайдера. Функцио­нальность опции полностью определяется используемым сервис-провайдером.

SO_DONTLINGER — закрытие сокета не блокируется. В ином случае будет ожидаться подтверждение отправки неотправленных данных перед закрытием. Это опция просто включает или выключает отправку данных при разрыве соединения, не изменяя тайм-аут, заданный SO_LINGER. Не поддерживается дейтаграммными сокетами.

Опция SO_DONTLINGER была также реализована в ядре Linux 2.6.25 для совместимости, но затем удалена за ненадобностью.

Опции, влияющие на безопасность:

SO_EXCLUSIVEADDRUSE — запретить другому приложению использовать для прослушивания тот же порт, что и сервер, даже на разных адресах. Опция , защищая сервер от захвата порта злонамеренным приложением. Конфликтует с SO_REUSEADDR. Может быть установлена, только если приложение запущено пользователем из группы администраторов.

SO_RANDOMIZE_PORT — если опция установлена, для сокета будет выбран случайный эфемерный порт. Если для порта установлена опция SO_REUSE_UNICASTPORT, установка SO_RANDOMIZE_PORT завершится с ошибкой. Эта опция по умолчанию устанавливается для сокетов, которые еще не привязаны к адресу.

Опции только для сокетов, передающих сообщения:

SO_MAX_MSG_SIZE — вернуть максимальный размер исходящего сообщения для сокетов, ориентированных на сообщения.

• SO_MAXDG — вернуть максимальный размер в байтах для исходящих дейтаграмм, поддерживаемых протоколом.

SO_MAXPATHDG — вернуть максимальный размер в байтах для исходящих дейтаграмм, поддерживаемых протоколом на всем пути к указанному адресу.

Опции групп сокетов:

SO_GROUP_ID — идентификатор группы сокетов.

SO_GROUP_PRIORITY — приоритет сокета в группе относительно других сокетов. Значения — неотрицательные целые. Нулевое значение соответствует высшему приоритету.

Опции, которые не поддерживаются стеком TCP/IP:

SO_CONNDATA, SO_CONNOPT — дополнительные данные, отправляемые с сетевыми запросами для установления соединения.

• SO_CONNDATALEN, SO_CONNOPTLEN — длина дополнительных данных, отправляемых при соединении.

• SO_DISCDATA, SO_DISCOPT — дополнительные данные, отправляемые с сетевыми запросами на разрыв соединения.

• SO_DISCDATALEN, SO_DISCOPTLEN — длина дополнительных данных для отключения.

SO-USELOOPBACK — использовать локальную петлю для отправки данных. Этот параметр следует использовать в том случае, если все отправленные данные будут получены только локально без обработки их сетевым оборудованием.

Параметры, оканчивающиеся на OPT, используются устаревшими протоколами, такими как DECNet, OSI TP4 и подобными. Означают эти параметры то же, что и параметры, оканчивающиеся на DATA. Но значения констант разные.

Внимание! Далеко не все опции поддерживаются всеми версиями Windows. Например, SO_BSP_STATE и опции групп поддерживаются, начиная с ОС Windows Vista, опция SO_PROTECT поддерживалась только в ОС Windows 2003 Server, а опции SO_RCVLOWAT и SO_SNDLOWAT не поддерживаются вообще. За подробностями обращайтесь к MSDN.

Уровень IP

Параметр level должен быть равен IPPROTO_IP или SOL_IP.

Список опций IP приведен в разделе MSDN IPPROTO_IP Socket Options, доступном по ссылке https://docs.microsoft.com/en-us/windows/win32/winsock/ipproto-ip-socket-options.

Опции, используемые для управления фильтром интерфейсов дейтаграммных сокетов:

IP_IFLIST — включить или выключить фильтрацию по списку интерфейсов либо получить значение флага. Когда эта опция включена, прием дейтаграмм ограничивается интерфейсами в списке. Дейтаграммы, полученные через любые другие интерфейсы, будут проигнорированы.

• IP_GET_IFLIST — получить массив индексов разрешенных интерфейсов.

• IP_ADD_IFLIST — добавить индекс интерфейса в список разрешенных.

IP_DEL_IFLIST — удалить индекс интерфейса из списка разрешенных.

Записи в списках не удаляются автоматически и могут быть удалены только приложением. Если, например, интерфейс был удален из системы, список может устареть.

Сконвертировать индекс интерфейса в имя позволит функция if_indextoname().

Управление фрагментацией:

IP_DONTFRAGMENT — не фрагментировать данные протоколов, ориентированных на сообщения, таких как UDP и ICMP, независимо от значения локального MTU. Аналог IP_NODEFRAG, которого в ОС Windows нет.

Опции интерфейсов:

IP_ORIGINAL_ARRIVAL_IF — указывает, должна ли функция WSARecvMsg() возвращать в управляющих данных индекс интерфейса, через который был получен пакет. Параметр работает только для дейтаграммных и raw-сокетов. Используется с технологиями перехода на IPv6, такими как 6to4, ISATAP и Teredo, чтобы получить оригинальный интерфейс, после того как пакет через локальный туннель, имеющий свои интерфейсы, перешел с IPv4 в IPv6 или обратно.

• IP_RECVIF — указывает, должен ли стек IP-адресов заполнять в буфере управления индекс интерфейса, через который была получена дейтаграмма. Если это значение равно 1, функция WSARecvMsg() вернет управляющие данные, содержащие этот индекс.

IP_UNICAST_IF — возвращает или задает индекс интерфейса для отправки трафика IPv4. Узнать индекс интерфейса можно с помощью функции GetAdaptersAddresses().

Некоторые информационные опции:

IP_RECVDSTADDR — если параметр установлен, функция WSARecvMsg() вернет адрес получателя из дейтаграммы.

• IP_HOPLIMIT — получить значение максимального количества ретрансляций. По сути, для IP это TTL.

• IP_TCLASS — класс трафика пакета.

• IP_RECVTCLASS — получать класс трафика пакета со вспомогательными данными при вызове WSARecvMsg().

IP_PKTINFO_EX — получить расширенную информацию о пакете.

Структура для опции IP_PKTINFO_EX включает IN_PKTINFO и scope_id:

#include <ws2ipdef.h>

 

typedef struct in_pktinfo

{

    // Адрес источника либо назначения.

    IN_ADDR ipi_addr;

    // Индекс интерфейса.

    ULONG ipi_ifindex;

} IN_PKTINFO, *PIN_PKTINFO;

 

typedef struct in_pktinfo_ex

{

    // IN6_PKTINFO для IPv6.

    IN_PKTINFO pkt_info;

    // Идентификатор области.

    SCOPE_ID scope_id;

} IN_PKTINFO_EX, *PIN_PKTINFO_EX;

Для адресов IPv6 структура называется IPV6_PKTINFO_EX и содержит IPv6-адреса.

Широковещательный обмен данными:

IP_RECEIVE_BROADCAST — разрешить или блокировать прием широковещательного трафика.

Опции WFP:

IP_WFP_REDIRECT_CONTEXT, IP_WFP_REDIRECT_RECORDS — вспомогательные данные сокета, указывающие контекст и записи перенаправления для сокета UDP, используемого службой WFP пользовательского режима. Тип — cmsg_type, данные возвращаются в виде структуры WSACMSGHDR.

Прочее:

IP_ECN — получать уведомления о перегрузке.

• IP_RTHDR — получить или установить заголовок маршрутизации IPv6.

• IP_RECVRTHDR — получать заголовок маршрутизации во вспомогательных данных.

IP_NRT_INTERFACE — отметить интерфейс как работающий в режиме мягкого реального времени.

Операционная система мягкого реального времени — Near Real-Time system — отличается тем, что для выполнения некоторого вызова существует достаточно малый временной интервал.

Точное время завершения, как в жестких ОС РВ, неизвестно — это вызвано джиттером, который вносит система. Такой системой является Windows 10 IoT Enterprise, которая была выпущена для работы с промышленными IoT-устройствами.

Опция IP_NRT_INTERFACE — часть изменений, требуемых для поддержки работы приложений реального времени.

Уровень IPv6

Параметр level должен быть равен IPPROTO_IPV6.

Список опций IP приведен в разделе MSDN IPPROTO_IPV6 Socket Options по ссылке https://learn.microsoft.com/en-us/windows/win32/winsock/ipproto-ipv6-socket-options.

Управление фильтром выполняется через опции IPV6_ADD_IFLIST, IPV6-DEL_IFLIST, IPV6_GET_IFLIST, IPV6_IFLIST, аналогичные таковым для IP.

Для работы с группами многоадресной рассылки, кроме опций, которые были описаны в главе 8, существуют опции IPV6_JOIN_GROUP и IPV6_LEAVE_GROUP, полностью аналогичные IPV6_ADD_MEMBERSHIP и IPV6_DROP_MEMBERSHIP.

Опция IPV6_PKTINFO позволяет включать и выключать доставку сообщения, которое содержит информацию о пакете. Это аналог опции IPV6_RECVPKTINFO в Linux.

Опция управления фрагментацией пакетов называется IPV6_DONTFRAG.

Опции интерфейсов IPV6_RECVIF и IPV6_UNICAST_IF аналогичны таковым для IPv4.

Информационные опции, аналогичные опциям IPv4: IPV6_RECVDSTADDR, IPV6_RECVRTHDR, IPV6_PKTINFO_EX.

Опции, влияющие на безопасность:

IPV6_PROTECTION_LEVEL — позволяет накладывать ограничения на доступ к сокетам IPv6 для защиты от внешних атак путем сужения диапазона адресов, с которыми возможен обмен:

• PROTECTION_LEVEL_UNRESTRICTED — используется приложениями, предназначенными для работы через интернет, включая приложения, использующие возможности обхода NAT по протоколу IPv6 через Teredo.

• PROTECTION_LEVEL_EDGERESTRICTED — используется приложениями, предназначенными для работы через интернет. Но этот параметр не разрешает обход NAT с помощью реализации Windows Teredo.

• PROTECTION_LEVEL_RESTRICTED — используется приложениями в локальной сети. Эти приложения обычно не тестируются и не защищаются против атак из интернета. Параметр ограничивает получаемый трафик локальным.

Приложения, работающие через интернет, могут обходить брандмауэр, поэтому они должны быть защищены разработчиком от атак из интернета, направленных на открытый порт.

Teredo — это технология, обеспечивающая передачу IPv6-трафика по сетям IPv4. В отличие от таких протоколов, как 6to4, может работать даже на устройствах, которые находятся за NAT, то есть позволяет обходить NAT. Она использует протокол туннелирования, инкапсулирующий пакеты IPv6 в UDP-дейтаграммы поверх IPv4.

Стандартизирована в RFC 4380 «Teredo: Tunneling IPv6 over UDP through Network Address Translations».

Управление классом и MTU:

IPV6_MTU — позволяет получить MTU. Однако не позволяет его установить, в отличие от Linux.

• IPV6_TCLASS — класс трафика пакета.

• IPV6_RECVTCLASS — если опция истинна, функция WSARecvMsg() вернет опцио­нальные вспомогательные данные, содержащие значение класса трафика из заголовка IPv6. Параметр работает только на дейтаграммных сокетах.

IPV6_USER_MTU — позволяет получить или установить верхнюю границу MTU в байтах для сокета. Если значение меньше системного MTU, исходящие пакеты, размер которых превышает это значение, либо будут фрагментированы, либо не будут отправляться — в зависимости от значения IPV6_DONTFRAG. Значение по умолчанию — IP_UNSPECIFIED_USER_MTU.

Raw-сокеты IPv6:

IPV6_HDRINCL — то же, что и IP_HDRINCL, но для IPv6.

IPV6_CHECKSUM — смещение контрольной суммы для отправки через raw-сокет.

Опции WFP — IPV6_WFP_REDIRECT_CONTEXT и IPV6_WFP_REDIRECT_RECORDS, назначение которых аналогично значению опций для IPv4.

Из прочих опций доступна IPV6_NRT_INTERFACE, аналогичная по назначению таковой для IPv4.

Уровень UDP

Параметр level должен быть равен IPPROTO_UDP.

Список опций IP приведен в разделе MSDN «IPPROTO_UDP Socket Options» по ссылке:

https://learn.microsoft.com/en-us/windows/win32/winsock/ipproto-udp-socket-options.

Управление контрольной суммой в UDP:

UDP_CHECKSUM_COVERAGE — если задан, UDP-дейтаграммы отправляются с контрольной суммой.

UDP_NOCHECKSUM — если задан, UDP-дейтаграммы отправляются с нулевой контрольной суммой. Если у провайдера нет механизма отключения вычисления контрольной суммы UDP, он может просто сохранить этот параметр, не предпринимая никаких действий. Параметр не поддерживается для IPv6.

Управление размером дейтаграмм:

UDP_RECV_MAX_COALESCED_SIZE — аналог UDP_CORK в Linux. Если его значение отлично от 0, несколько полученных дейтаграмм могут быть объединены в одно сообщение, прежде чем будут переданы приложению. Значение параметра — максимальный размер сообщения в байтах после объединения.

UDP_SEND_MSG_SIZE — если имеет значение, отличное от 0, сообщения, отправляемые приложением, разбиваются на несколько отдельных сообщений сетевым стеком. Значение параметра представляет размер каждого фрагмента сообщения в байтах.

Функции WSAGetUdpRecvMaxCoalescedSize() и WSASetUdpRecvMaxCoalescedSize() позволяют задать или получить максимальный размер объединенной UDP-дейтаграммы:

int WSAGetUdpRecvMaxCoalescedSize(SOCKET Socket, DWORD *MaxCoalescedMsgSize);

int WSASetUdpRecvMaxCoalescedSize(SOCKET Socket, DWORD MaxCoalescedMsgSize);

WSAGetUdpSendMessageSize() и WSASetUdpSendMessageSize() — функции для получения и установки размера сообщения, используемого на сокете для сегментации UDP:

int WSAGetUdpSendMessageSize(SOCKET Socket, DWORD *MsgSize);

int WSASetUdpSendMessageSize(SOCKET Socket, DWORD MsgSize);

Эти функции являются безопасной оберткой для получения и установки опции сокета UDP_SEND_MSG_SIZE.

Все функции вернут 0 в случае успеха или SOCKET_ERROR в случае ошибки.

Уровень TCP

Параметр level должен быть равен IPPROTO_TCP.

Список опций IP приведен в разделе MSDN IPPROTO_TCP Socket Options по ссылке:

https://learn.microsoft.com/en-us/windows/win32/winsock/ipproto-tcp-socket-options.

Настроек TCP в ОС Windows меньше, чем в Linux.

Обработка внеполосных данных:

TCP_BSDURGENT — если имеет значение «истина», стек будет использовать BSD-стиль для обработки внеполосных данных. Параметр противоположен TCP_EXPEDITED_1122.

TCP_EXPEDITED_1122 — если имеет значение «истина», используется обработка внеполосных данных, как указано в RFC 1122 Requirements for Internet Hosts.

Подключение и управление соединением:

TCP_FAIL_CONNECT_ON_ICMP_ERROR — если имеет значение «истина», вызов connect() возвращается при получении ошибки ICMP со значением WSAEHOSTUNREACH, а сокет будет закрыт. В противном случае сокет будет вести себя как обычно. Исходный адрес ошибки будет доступен через параметр сокета TCP_ICMP_ERROR_INFO.

TCP_ICMP_ERROR_INFO — прочитать сведения об ошибке ICMP, полученной сокетом TCP в результате неудачного подключения, если была установлена опция TCP_FAIL_CONNECT_ON_ICMP_ERROR. Ошибка будет возвращена как структура ICMP_ERROR_INFO.

TCP_NOSYNRETRIES — если опция установлена, не отправлять SYN при активной попытке TCP-соединения.

TCP_CONGESTION_ALGORITHM — выбрать алгоритм избежания перегрузок. Опция принимает значения от 0 до 7. Опция не документирована, но судя по ­командлетам PowerShell, некоторые ее значения таковы:

0 Default. Алгоритм по умолчанию. Вероятно, это NewReno, но зависит от версии ОС.

1 NewReno. Описан в RFC 6582 «The NewReno Modification to TCP's Fast Recovery Algorithm».

2 CTCP. Compound TCP — алгоритм от Microsoft, появившийся в стеке Vista. Старается уменьшить время отклика и увеличить пропускную способность. Описан в IETF draft «Compound TCP: A New TCP Congestion Control for High-Speed and Long Distance Networks».

3 DCTCP. Data Center TCP. Уменьшает задержки и потери пакетов. Описан в RFC 8257 «Data Center TCP (DCTCP): TCP Congestion Control for Data Centers».

4 LEDBAT. Увеличивает задержку, используя доступную полосу пропускания. Описан в RFC 6817 «Low Extra Delay Background Transport (LEDBAT)».

• 5 CUBIC. Стандартный алгоритм для быстрых и протяженных сетей. Описан в «RFC 9438 CUBIC for Fast and Long-Distance Networks».

Структура ICMP_ERROR_INFO:

#include <ws2ipdef.h>

 

typedef struct icmp_error_info

{

    // IP-адрес источника ошибки ICMP.

    SOCKADDR_INET srcaddress;

    // Протокол ошибки ICMP: IPPROTO_ICMP или IPPROTO_ICMPV6.

    IPPROTO protocol;

    // Тип ошибки ICMP.

    UINT8 type;

    // Код ошибки ICMP.

    UINT8 code;

} ICMP_ERROR_INFO, *PICMP_ERROR_INFO;

Функции WSAGetFailConnectOnIcmpError() и WSASetFailConnectOnIcmpError() также позволяют как получать, так и устанавливать значение опции TCP_FAIL_CONNECT_ON_ICMP_ERROR:

int WSAGetFailConnectOnIcmpError(SOCKET Socket, DWORD *Enabled);

int WSASetFailConnectOnIcmpError(SOCKET Socket, DWORD Enabled);

Если параметр Enabled установлен в false, сокет будет продолжать работать, даже если он получит сообщение об ошибке ICMP.

Обертка над опцией TCP_ICMP_ERROR_INFO — функция WSAGetIcmpErrorInfo().

Она принимает дескриптор сокета и указатель на структуру ICMP_ERROR_INFO, в которую будет записана информация об ошибке ICMP:

int WSAGetIcmpErrorInfo(SOCKET Socket, ICMP_ERROR_INFO *Info);

Тайм-ауты и временные метки:

TCP_MAXRT — количество секунд ожидания подключения. Значение –1 отключает тайм-аут. В этом случае время ожидания экспоненциально увеличивается для каждого повторного соединения, пока не достигнет максимального значения. Максимальное значение по умолчанию — 60 секунд.

• TCP_MAXRTMS — количество миллисекунд ожидания подключения. В остальном аналогична опции TCP_MAXRT и похожа на опцию TCP_USER_TIMEOUT в Linux. Поддерживается, начиная с ОС Windows 10 1607, и в приложениях, ориентированных на все новые версии ОС, следует использовать эту опцию, а не TCP_MAXRT.

• TCP_TIMESTAMPS — включить или отключить метки времени по стандарту RFC 1323 «TCP Extensions for High Performance».

Вспомогательные данные

WinSock позволяет работать со вспомогательными данными, которые используются для работы с некоторыми параметрами сокетов и протоколов. Делать это можно, применяя функции, которые являются расширениями Microsoft. Для отправки данных используется функция WSASendMsg():

#include <Mswsock.h>

 

int WSAAPI WSASendMsg(

    SOCKET handle,

    LPWSAMSG lpMsg,

    DWORD dwFlags,

    LPDWORD lpNumberOfBytesSent,

    LPWSAOVERLAPPED lpOverlapped,

    LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine

);

Параметры функции WSASendMsg():

handle — дескриптор сокета.

• lpMsg — указатель на отправляемые данные. Соответствует структуре msghdr в POSIX.

• dwFlags — флаги, изменяющие поведение функции. Поддерживаются уже рассмотренные в главе 8 флаги MSG_DONTROUTE и MSG_PARTIAL. Напомним, что MSG_DONTROUTE игнорируется провайдером от Microsoft.

• lpNumberOfBytesSent — указатель на количество байтов, отправленных вызовом, если операция ввода-вывода завершается немедленно.

• lpOverlapped — необязательный указатель на структуру WSAOVERLAPPED.

lpCompletionRoutine — указатель на функцию завершения, вызываемую после завершения передачи данных.

Возвращает 0 в случае успеха и SOCKET_ERROR при неудаче. Для получения данных используется функция WSARecvMsg():

#include <Mswsock.h>

 

int WSARecvMsg(

    SOCKET s,

    LPWSAMSG lpMsg,

    LPDWORD lpdwNumberOfBytesRecvd,

    LPWSAOVERLAPPED lpOverlapped,

    LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine

);

Параметры функции WSARecvMsg():

s — дескриптор сокета.

• lpMsg — буфер для принимаемых сообщений.

• lpdwNumberOfBytesRecvd — указатель на переменную, в которую будет записано количество принятых байтов, если вызов блокирующий.

• lpOverlapped — необязательный указатель на структуру WSAOVERLAPPED.

lpCompletionRoutine — указатель на функцию завершения, вызываемую после завершения операции передачи.

Возвращаемые значения такие же, как и для WSASendMsg().

Структура, которая используется в данных функциях, не является POSIX-совместимой. Она немного отличается по именам атрибутов и составу:

#include <winsock2.h>

 

typedef struct _WSAMSG

{

    // Указатель на структуру SOCKET_ADDRESS,

    // в которой хранится информация об адресе удаленного абонента.

    // Используется с сокетами без соединения.

    LPSOCKADDR name;

    // Размер name в байтах.

    int namelen;

    // Массив структур WSABUF, используемый для получения данных сообщения.

    LPWSABUF lpBuffers;

    // Количество буферов в массиве lpBuffers. Тип может быть DWORD.

    ULONG dwBufferCount;

    // Опциональные управляющие данные.

    WSABUF Control;

    // Управляющие флаги. Тип может быть DWORD.

    ULONG dwFlags;

} WSAMSG, *PWSAMSG, *LPWSAMSG;

Заголовок вспомогательных данных такой же, как в Linux:

#include <winsock2.h>

 

struct wsacmsghdr

{

    // Размер данных вместе с заголовком.

    UINT cmsg_len;

    // Протокол, например IPPROTO_IP или IPPROTO_IPV6.

    int cmsg_level;

    // Тип вспомогательных данных.

    int cmsg_type;

    // После заголовка идет массив данных cmsg_data[].

} WSACMSGHDR;

За ним идут данные, для работы с которыми используется набор макросов:

#include <winsock2.h>

 

#define LPCMSGHDR *WSA_CMSG_FIRSTHDR(LPWSAMSG msg);

#define LPCMSGHDR *WSA_CMSG_NXTHDR(LPWSAMSG msg, LPWSACMSGHDR cmsg);

#define UCHAR *WSA_CMSG_DATA(LPWSACMSGHDR pcmsg);

#define UINT WSA_CMSG_SPACE(UINT length);

#define UINT WSA_CMSG_LEN(UINT length);

Они аналогичны макросам в Linux, но называются иначе:

WSA_CMSG_FIRSTHDR() — получить указатель на первый заголовок cmsghdr или nullptr.

• WSA_CMSG_NXTHDR() — получить указатель на следующий заголовок cmsghdr.

• WSA_CMSG_SPACE() — получить размер буфера для элемента данных.

• WSA_CMSG_LEN() — получить выровненную длину элемента вспомогательных данных для сохранения в поле cmsg_len структуры cmsghdr.

WSA_CMSG_DATA() — получить указатель на начало данных, идущих после заголовка cmsghdr.

Сообщения будут приходить, когда установлены опции, описанные ранее, либо когда сделан запрос через WSARecvMsg().

В заголовке управляющих данных, в поле Control для получения или установки параметров необходимо установить опции.

Для протокола IP это:

IP_ORIGINAL_ARRIVAL_IF — получить интерфейс, откуда пришли данные.

• IP_PKTINFO — задать или получить вместе с данными информацию о пакете.

IP_ECN — задать или получить явное уведомление о перегрузке.

Структуры, которые содержат информацию о пакете для IP_PKTINFO и IPV6_PKTINFO, предоставляют только адрес и индекс интерфейса:

// Для IPv4.

typedef struct in_pktinfo

{

    IN_ADDR ipi_addr;

    ULONG ipi_ifindex;

} IN_PKTINFO, *PIN_PKTINFO;

 

// Для IPv6.

typedef struct in6_pktinfo

{

    IN6_ADDR ipi6_addr;

    ULONG ipi6_ifindex;

} IN6_PKTINFO, *PIN6_PKTINFO;

Для IPv6:

IPV6_DSTOPTS — получить или установить параметры заголовка для отправляемых пакетов, то есть поля заголовка опций IPv6.

• IPV6_HOPLIMIT — получить или установить максимальное количество промежуточных узлов, через которые может быть ретранслирован пакет, иными словами — TTL.

• IPV6_HOPOPTS — отправить или получить опции, которые передаются между промежуточными узлами.

• IPV6_NEXTHOP — адрес следующего промежуточного узла.

• IPV6_PKTINFO — получить информацию о пакете.

• IPV6_RTHDR — задать или получить заголовок маршрутизации IPv6.

IPV6_ECN — получать явное уведомление о перегрузке.

Опции IP_ECN и IPV6_ECN специфичны для Windows. ECN, или явное уведомление о перегрузке, — это механизм, позволяющий ECN-абонентам узнать о возникновении затора в сети без отбрасывания пакетов, через установку битов в заголовке IP.

Подобное, например, используется в протоколе QUIC.

Помимо опций ECN, во вспомогательных данных есть две функции, которые требуются для работы с ним.

Функция WSAGetRecvIPEcn() дает возможность узнать, включено ли получение сообщений о ECN, а функция WSASetRecvIPEcn() позволяет включить или выключить заполнение стеком кодовых точек ECN:

#include <ws2tcpip.h>

 

int WSAGetRecvIPEcn(

  SOCKET Socket,

  DWORD  *Enabled

);

 

int WSASetRecvIPEcn(

  SOCKET Socket,

  DWORD  Enabled

);

Подробнее о ECN можно прочитать в разделе MSDN Winsock Explicit Congestion Notification (ECN) и в RFC 3168 «The Addition of Explicit Congestion Notification (ECN) to IP».

Функции сокетного ioctl

Частичный аналог функции ioctl() в ОС Windows называется ioctlsocket(). Прототип и возможности этих функций различаются достаточно сильно:

#include <winsock2.h>

 

int WSAAPI ioctlsocket(SOCKET s, long cmd, u_long *argp);

Параметры функции ioctlsocket():

s — дескриптор сокета.

• cmd — команда, выполняемая над сокетом.

argp — указатель на параметр команды.

В случае успеха функция возвращает 0, а в случае ошибки — SOCKET_ERROR.

Как пример использования переведем сокет в неблокирующий режим:

int i_mode = 1;

 

if (ioctlsocket(m_socket, FIONBIO, &i_mode) != NO_ERROR)

    std::cerr

        << "ioctlsocket failed with error:" << WSAGetLastError()

        << std::endl;

Расширенным вариантом ioctlsocket() является функция WSAIoctl(). Она также используется для задания или извлечения параметров, связанных с сокетом, транспортным протоколом или подсистемой связи.

Ее прототип:

#include <winsock2.h>

 

int WSAAPI WSAIoctl(

    SOCKET s,

    DWORD dwIoControlCode,

    LPVOID lpvInBuffer,

    DWORD cbInBuffer,

    LPVOID lpvOutBuffer,

    DWORD cbOutBuffer,

    LPDWORD lpcbBytesReturned,

    LPWSAOVERLAPPED lpOverlapped,

    LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine

);

Параметры функции WSAIoctl():

s — дескриптор, определяющий сокет.

• dwIoControlCode — выполняемая команда.

• lpvInBuffer — указатель на входной буфер.

• cbInBuffer — размер входного буфера в байтах.

• lpvOutBuffer — указатель на выходной буфер.

• cbOutBuffer — размер выходного буфера в байтах.

• lpcbBytesReturned — указатель на фактическое количество байтов, которое вернула функция.

• lpOverlapped — необязательный указатель на структуру WSAOVERLAPPED.

lpCompletionRoutine — необязательный указатель на подпрограмму завершения для сокетов, использующих перекрывающийся ввод-вывод.

Возвращаемые значения такие же, как у функции ioctlsocket().

Собственно, «расширенной» относительно ioctlsocket() данная функция является лишь потому, что может работать в overlapped-режиме.

Некоторые вызовы ioctl могут блокироваться на неопределенное время, и для того, чтобы не блокировать выполнение кода такими вызовами, можно использовать функцию WSAIoctl().

Блокировка ioctl зависит от провайдера, но в штатном провайдере от Microsoft реализованы следующие блокирующие вызовы:

SIO_ADDRESS_LIST_CHANGE;

SIO_FINDROUTE;

SIO_FLUSH;

SIO_GET_QOS;

SIO_GET_GROUP_QOS.

SIO_ROUTING_INTERFACE_CHANGE.

SIO_SET_QOS.

SIO_SET_GROUP_QOS.

Эти вызовы мы опишем далее.

Дополнительно существует функция WSANSPIoctl(), позволяющая выполнять вызовы управления вводом-выводом в зарегистрированном пространстве имен. Функция используется для выполнения единственного вызова: SIO_NSP_NOTIFY_CHANGE.

Состав сокетного ioctl

Так же как и в Linux, в ОС Windows коды ioctl структурированы. Их формат следующий:

I

O

V

T

Семейство провайдеров или адресов

Код

3

3

2

22

22222221111

11111

1

0

9

87

65432109876

543210...

Биты читаются сверху вниз и слева направо, то есть 31, 30, 29, ...

Параметры имеют следующие значения:

I — ioctl использует входной буфер. Пример: IOC_IN.

O — ioctl использует выходной буфер. Пример: IOC_OUT.

V — ioctl не имеет параметров. Пример: IOC_VOID.

T — 2-битное число, определяющее тип ioctl:

0 — Unix ioctl, используемый в BSD. Например, FIONREAD и FIONBIO.

1 — универсальный ioctl Windows Sockets 2.

2 — ioctl применяется только к заданному семейству адресов.

3 — ioctl применяется только к заданному провайдеру, как и IOC_VENDOR.

Семейство провайдеров транспорта или семейства адресов — 11-битное число, определяющее провайдер, которому принадлежит код, если T == 3 или содержит семейство адресов, к которому применяется код, если T == 2. Если это код ioctl Unix, то этот параметр имеет то же значение, что и код в Unix.

Код — 16-разрядное количество, содержащее конкретный код ioctl для операции.

Коды ioctl описаны в MSDN по ссылке . В каждом из кодов указана структура, но для краткости мы приведем только сами коды.

Рассмотрим поддерживаемые WinSock коды ioctl.

Unix ioctl

Отдельно стоит выделить Unix-совместимые ioctl. В WinSock их совсем немного:

FIONBIO — включить или отключить неблокирующий режим сокета.

• FIONREAD — получить количество непрочитанных данных в очереди приема.

SIOCATMARK — возвращает значение, не равное 0, если входящий поток данных находится на отметке срочности.

Подробнее эти вызовы описаны в главе 10. Далее мы рассмотрим ioctl WinSock2.

Управление дескриптором

Коды ioctl для перевода дескрипторов сокетов в дескрипторы базового провайдера.

Переводить одни дескрипторы в другие необходимо при добавлении новых функций расширения WinSock, чтобы предотвратить нарушения работы LSP, которые не работают с IFS:

SIO_BASE_HANDLE — вернуть базовый дескриптор для заданного сокета. Код ioctl, используемый для преобразования дескриптора сокета в дескриптор базового провайдера. Он не используется какой-либо функцией расширения WinSock и не должен перехватываться LSP Winsock.

• SIO_BSP_HANDLE — вернуть базовый дескриптор сокета для использования в функции WSASendMsg().

• SIO_BSP_HANDLE_SELECT — вернуть базовый дескриптор сокета для использования в функции select().

• SIO_BSP_HANDLE_POLL — вернуть базовый дескриптор сокета для использования в функции WSAPoll().

• SIO_TRANSLATE_HANDLE — получает соответствующий дескриптор для сокетов, допустимый в контексте интерфейса-компаньона, например TH_NETDEV и TH_TAPI.

Управление уведомлениями

События на сокетах могут генерировать уведомления, и для их включения существует несколько вызовов:

SIO_SOCKET_CLOSE_NOTIFY — установить уведомление при закрытии данного сокета.

• SIO_QUERY_TARGET_PNP_HANDLE — запросить у базового провайдера дескриптор, который можно использовать для получения уведомлений о событиях Plug and Play.

• SIO_NSP_NOTIFY_CHANGE — проверить, являются ли по-прежнему допустимыми результаты, возвращаемые по дескриптору запроса hLookup предыдущими вызовами WSALookupServiceBegin() или WSALookupServiceNext().

Управление сетевыми интерфейсами

Вызовы для получения интерфейсов и привязки к ним сокетов:

SIO_ASSOCIATE_HANDLE — связать сокет с дескриптором интерфейса. Поскольку флаг MSG_DONTROUTE и аналогичная опция в ОС Windows игнорируются провайдером от Microsoft, этот ioctl полезен для того, чтобы указать, какой интерфейс будет использован для отправки данных сокетом.

• SIO_GET_INTERFACE_LIST — получить список настроенных IP-интерфейсов и их параметров в виде массива структур INTERFACE_INFO.

• SIO_GET_INTERFACE_LIST_EX — возвращает список настроенных IP-интер­фейсов и их параметров в виде массива INTERFACE_INFO_EX структур.

• SIO_INDEX_BIND — привязать сокет к индексу интерфейса, указанному в качестве входного параметра, а не к адресу.

• SIO_ROUTING_INTERFACE_CHANGE — получать уведомления о любых изменениях в локальном интерфейсе маршрутизации, используемом для обмена данными с удаленным адресом. Входной буфер — структура SOCKADDR, которая содержит удаленный адрес. Если интерфейс заданного маршрута изменится, приложение будет уведомлено. При использовании адреса INADDR_ANY уведомления будут приходить о любых изменениях маршрутизации. Ожидать событий изменения маршрутизации можно через WSAEventSelect() или WSAAsyncSelect() с установленным в маске событий флагом FD_ROUTING_INTERFACE_CHANGE. Либо использовать перекрывающийся ввод-вывод: ­дескриптор события, который сигнализирует об изменении маршрутизации, должен быть задан в структуре WSAOVERLAPPED.

SIO_ROUTING_INTERFACE_QUERY — определить, какой интерфейс следует использовать для отправки данных абоненту. Провайдеры транспорта могут отправлять уведомление об изменении маршрутизации всегда, не учитывая адрес. Поэтому каждый раз необходимо вызывать SIO_ROUTING_INTERFACE_QUERY для получения интерфейса. Вызову передается адрес удаленного абонента — структуры SOCKADDR. В выходной буфер будет записан массив из одной или нескольких структур SOCKADDR, с адресами локальных интерфейсов, которые могут быть использованы.

Для более плотной работы с интерфейсами, вероятно, лучше использовать не эти ioctl, а функции IP Helper API.

Структура INTERFACE_INFO выглядит следующим образом:

#include <ws2ipdef.h>

 

// Тип адреса в структуре ниже.

typedef union sockaddr_gen

{

    struct sockaddr Address;

    struct sockaddr_in AddressIn;

    struct sockaddr_in6_old AddressIn6;

} sockaddr_gen;

 

typedef struct _INTERFACE_INFO

{

    // Состояние интерфейса.

    ULONG iiFlags;

    // Адрес интерфейса.

    sockaddr_gen iiAddress;

    // Широковещательный адрес интерфейса.

    // Для соединения "точка-точка" — адрес подключенного абонента.

    sockaddr_gen iiBroadcastAddress;

    // Маска сети, которую использует данный интерфейс.

    sockaddr_gen iiNetmask;

} INTERFACE_INFO, *LPINTERFACE_INFO;

Флаги в поле iiFlags:

IFF_UP — интерфейс активен и работает.

• IFF_BROADCAST — поддерживается широковещательная передача.

• IFF_LOOPBACK — интерфейс локальной петли.

• IFF_POINTTOPOINT — интерфейс типа «точка-точка».

• IFF_MULTICAST — поддерживается многоадресная рассылка.

Единственное отличие структуры INTERFACE_INFO_EX в том, что тип адресов в ней — SOCKET_ADDRESS, а не sockaddr_gen.

Остальные структуры мы уже рассматривали ранее.

Операции ввода-вывода

Вызовы, перечисленные ниже, позволяют выполнить настройку ввода-вывода:

SIO_ENABLE_CIRCULAR_QUEUEING — включить циклическую очередь. Настраивает базовый провайдер транспорта на работу при переполнении входящей очереди дейтаграммных сообщений. Если опция включена, новое сообщение никогда не будет удалено из-за переполнения. Вместо этого удаляется самое старое сообщение в очереди. Опция применима только для ненадежных протоколов, ориентированных на сообщения.

• SIO_FIND_ROUTE — найти маршрут до удаленного адреса. Входной параметр — указатель на sockaddr. Если маршрут к адресу уже есть в кэше, он будет удален и найден повторно. Используется для проверки доступности адреса. Провайдер TCP/IP от Microsoft может не реализовывать этот вызов в некоторых версиях ОС.

• SIO_FLUSH — удалить содержимое очереди отправки сокета.

Управление буферами

Чтобы узнать размер буфера, который будет оптимальным для выделения пользователем, существуют следующие вызовы:

SIO_IDEAL_SEND_BACKLOG_QUERY — позволяет узнать идеальный размер буфера отправки, который зависит от многих факторов, таких как пропускная способность сети, задержка, потери пакетов, алгоритмы управления перегрузкой и т.д.

SIO_IDEAL_SEND_BACKLOG_CHANGE — уведомляет приложение при изменении идеального размера буфера отправки.

Функция idealsendbacklogquery() возвращает размер идеального буфера отправки в байтах или SOCKET_ERROR, если произошла ошибка:

int idealsendbacklogquery(SOCKET s, ULONG *pISB);

Параметры функции idealsendbacklogquery():

s — дескриптор сокета;

pISB — указатель на размер буфера.

Если приложение использует слишком маленький буфер для отправки данных, оно тратит больше времени на ожидание подтверждений. Если же буфер слишком большой, он занимает излишек памяти. Кроме того, это вызывает задержки при отправке данных, так как перед отправкой буфер необходимо заполнить.

Включать уведомления об изменении идеального размера буфера позволяет функция idealsendbacklognotify():

int idealsendbacklognotify(

    SOCKET s,

    LPWSAOVERLAPPED lpOverlapped,

    LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);

Параметры функции idealsendbacklognotify():

s — дескриптор сокета.

• lpOverlapped — указатель на структуру WSAOVERLAPPED, если сокет перекрывающийся, и nullptr, если нет.

lpCompletionRoutine — указатель на функцию, вызываемую при завершении операции для перекрывающихся сокетов.

Как ioctl, так и функция idealsendbacklognotify(), работающая поверх него, принимают lpCompletionRoutine — указатель на функцию, которая будет вызвана при необходимости изменения размера буфера.

Привязка обработки к процессорному ядру

Если алгоритмы сокета выполняются на определенном ядре, часть данных можно держать в кэше ядра, а не загружать каждый раз из памяти.

В некоторых случаях привязка сокета к ядру может значительно увеличить производительность. Для того чтобы выполнить привязку, существуют следующие опции:

SIO_QUERY_RSS_SCALABILITY_INFO — включить масштабирование принимающей стороны, или RSS — receive-side scaling. NDIS 5.1 обрабатывал принятые данные на одном процессоре. Данный вызов позволяет включить режим, при котором обработка распределяется между несколькими процессорами.

• SIO_QUERY_RSS_PROCESSOR_INFO — запросить связь между сокетом, ядром процессора RSS и узлом NUMA. Возвращает структуру, содержащую номер группы и относительный номер процессора в группе, а также идентификатор узла NUMA.

SIO_CPU_AFFINITY — включить распределение обработки данных по ядрам. Каждый сокет привязывается к своему ядру. Все пакеты одного потока данных группируются на основе адресов, портов и протокола. Сгруппированные пакеты будут попадать в один и тот же сокет, который обрабатывается заданным ядром. Вызов необходимо сделать до привязки адресов. Параметр вызова — индекс процессора типа unsigned short, начиная с 0.

SIO_QUERY_RSS_SCALABILITY_INFO принимает структуру для переключения режима:

typedef struct _RSS_SCALABILITY_INFO

{

    BOOLEAN RssEnabled;

} RSS_SCALABILITY_INFO, *PRSS_SCALABILITY_INFO;

SIO_QUERY_RSS_PROCESSOR_INFO возвращает структуру SOCKET_PROCESSOR_AFFINITY:

#include <ws2def.h>

 

// Описание логического процессора.

typedef struct _PROCESSOR_NUMBER

{

    // Группа процессора.

    WORD Group;

    // Номер процессора в группе.

    BYTE Number;

    // Зарезервировано.

    BYTE Reserved;

} PROCESSOR_NUMBER, *PPROCESSOR_NUMBER;

 

typedef struct _SOCKET_PROCESSOR_AFFINITY

{

    // Процессор.

    PROCESSOR_NUMBER Processor;

    // Идентификатор NUMA-узла.

    USHORT NumaNodeId;

    // Зарезервировано.

    USHORT Reserved;

} SOCKET_PROCESSOR_AFFINITY, *PSOCKET_PROCESSOR_AFFINITY;

Временные метки

Получение временных меток обычно полезно для отладки сетевого взаимодействия:

SIO_GET_TX_TIMESTAMP — получать метки времени для передаваемых пакетов. Входной параметр — идентификатор метки uint32_t, выходной параметр — метка времени uint64_t. Работает только на дейтаграммных сокетах.

SIO_TIMESTAMPING — изменить настройки получения меток времени.

Вызов SIO_TIMESTAMPING принимает в качестве параметра указатель на структуру:

#include <mstcpip.h>

 

typedef struct _TIMESTAMPING_CONFIG

{

    // Направление, для которого применяются метки.

    ULONG  Flags;

    // Число буферизованных меток.

    USHORT TxTimestampsBuffered;

} TIMESTAMPING_CONFIG, *PTIMESTAMPING_CONFIG;

Флаги направления могут иметь следующие значения:

TIMESTAMPING_FLAG_RX — метки принятых дейтаграмм.

TIMESTAMPING_FLAG_TX — метки отправленных дейтаграмм.

Вызов задает число меток, которые можно принять. Если места в буфере нет, новые метки будут отброшены.

Для работы с временными метками в IP Helper API есть несколько функций.

Назад: Глава 17. Альтернативы сокетам в ОС Windows
Дальше: Резервирование портов