Вызовы функций для приложений Winsock IPv6

Новые функции были введены в интерфейс сокетов Windows специально предназначен для упрощения программирования сокетов Windows. Одним из преимуществ этих новых функций сокетов Windows является встроенная поддержка IPv6 и IPv4.

К этим новым функциям сокетов Windows относятся следующие:

Кроме того, добавлены новые вспомогательные функции IP-адресов с поддержкой IPv6 и IPv4 для упрощения программирования сокетов Windows. К этим новым функциям вспомогательного IP-адреса относятся следующие:

При добавлении поддержки IPv6 в приложение следует использовать следующие рекомендации:

  • Используйте WSAConnectByName , чтобы установить подключение к конечной точке с именем узла и портом. Функция WSAConnectByName доступна в Windows Vista и более поздних версиях.
  • Используйте WSAConnectByList , чтобы установить подключение к одной из возможных конечных точек, представленных набором целевых адресов (имена узлов и портов). Функция WSAConnectByList доступна в Windows Vista и более поздних версиях.
  • Замените вызовы функции gethostbyname вызовами одной из новых функций getaddrinfo Windows Sockets. Функция getaddrinfo с поддержкой протокола IPv6 доступна в Windows XP и более поздних версиях. Протокол IPv6 также поддерживается в Windows 2000 при установке предварительной версии технологии IPv6 для Windows 2000.
  • Замените вызовы функции gethostbyaddr вызовами одной из новых функций getnameinfo Windows Sockets. Функция getnameinfo с поддержкой протокола IPv6 доступна в Windows XP и более поздних версиях. Протокол IPv6 также поддерживается в Windows 2000 при установке предварительной версии технологии IPv6 для Windows 2000.

WSAConnectByName

Функция WSAConnectByName упрощает подключение к конечной точке с помощью сокета на основе потока, учитывая имя узла или IP-адрес назначения (IPv4 или IPv6). Эта функция уменьшает исходный код, необходимый для создания IP-приложения, не зависящее от используемой версии протокола IP. WSAConnectByName заменяет следующие шаги в типичном TCP-приложении на один вызов функции:

  • Разрешите имя узла набору IP-адресов.
  • Для каждого IP-адреса:
    • Создайте сокет соответствующего семейства адресов.
    • Пытается подключиться к удаленному IP-адресу. Если подключение выполнено успешно, он возвращается; в противном случае выполняется попытка следующего удаленного IP-адреса узла.

Функция WSAConnectByName выходит за рамки только разрешения имени, а затем пытается подключиться. Функция использует все удаленные адреса, возвращаемые разрешением имен, и все исходные IP-адреса локального компьютера. Сначала он пытается подключиться с использованием пар адресов с наибольшей вероятностью успеха. Таким образом, WSAConnectByName не только гарантирует, что подключение будет установлено по возможности, но и сводит к минимуму время для установления подключения.

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

  • Функция setsockopt должна вызываться в сокете, созданном для семейства адресов AF_INET6, чтобы отключить параметр сокета IPV6_V6ONLY перед вызовом WSAConnectByName. Это достигается путем вызова функции setockopt в сокете с параметром уровня, равным IPPROTO_IPV6 (см. IPPROTO_IPV6 параметры сокета), параметр optname, заданный для IPV6_V6ONLY, и значение параметра optvalue, равное нулю.

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

В приведенном ниже примере кода показаны только несколько строк кода, необходимых для реализации приложения, не зависящее от версии IP.

Установка подключения с помощью WSAConnectByName

#ifndef UNICODE
#define UNICODE
#endif

#define WIN32_LEAN_AND_MEAN

#include <winsock2.h>
#include <Ws2tcpip.h>
#include <stdio.h>

// Link with ws2_32.lib
#pragma comment(lib, "Ws2_32.lib")

SOCKET OpenAndConnect(LPWSTR NodeName, LPWSTR PortName) 
{
    SOCKET ConnSocket;
    DWORD ipv6only = 0;
    int iResult;
    BOOL bSuccess;
    SOCKADDR_STORAGE LocalAddr = {0};
    SOCKADDR_STORAGE RemoteAddr = {0};
    DWORD dwLocalAddr = sizeof(LocalAddr);
    DWORD dwRemoteAddr = sizeof(RemoteAddr);
  
    ConnSocket = socket(AF_INET6, SOCK_STREAM, 0);
    if (ConnSocket == INVALID_SOCKET){
        return INVALID_SOCKET;
    }

    iResult = setsockopt(ConnSocket, IPPROTO_IPV6,
        IPV6_V6ONLY, (char*)&ipv6only, sizeof(ipv6only) );
    if (iResult == SOCKET_ERROR){
        closesocket(ConnSocket);
        return INVALID_SOCKET;       
    }

    bSuccess = WSAConnectByName(ConnSocket, NodeName, 
            PortName, &dwLocalAddr,
            (SOCKADDR*)&LocalAddr,
            &dwRemoteAddr,
            (SOCKADDR*)&RemoteAddr,
            NULL,
            NULL);
    if (bSuccess){
        return ConnSocket;
    } else {
        return INVALID_SOCKET;
    }
}

WSAConnectByList

Функция WSAConnectByList устанавливает соединение с узлом, заданным набором возможных узлов (представленным набором конечных IP-адресов и портов). Функция принимает все IP-адреса и порты для конечной точки и всех IP-адресов локального компьютера и пытается подключиться с помощью всех возможных сочетаний адресов.

WSAConnectByList связан с функцией WSAConnectByName. Вместо того чтобы принимать одно имя узла, WSAConnectByList принимает список узлов (адреса назначения и пары портов) и подключается к одному из адресов и портов в указанном списке. Эта функция предназначена для поддержки сценариев, в которых приложению необходимо подключиться к любому доступному узлу из списка потенциальных узлов.

Аналогично WSAConnectByName, функция WSAConnectByList значительно сокращает исходный код, необходимый для создания, привязки и подключения сокета. Эта функция упрощает реализацию приложения, не зависящее от версии IP. Список адресов для узла, принятого этой функцией, может быть адресами IPv6 или IPv4.

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

  • Функция setsockopt должна вызываться в сокете, созданном для семейства адресов AF_INET6, чтобы отключить параметр сокета IPV6_V6ONLY перед вызовом WSAConnectByList. Это достигается путем вызова функции setockopt в сокете с параметром уровня, равным IPPROTO_IPV6 (см. IPPROTO_IPV6 параметры сокета), параметр optname, заданный для IPV6_V6ONLY, и значение параметра optvalue, равное нулю.
  • Все адреса IPv4 должны быть представлены в формате IPv4-сопоставленного IPv6-адреса, что позволяет приложению IPv6 взаимодействовать только с узлом IPv4. Формат IPv4-сопоставленного IPv6-адреса позволяет представлять IPv4-адрес узла IPv4 в виде IPv6-адреса. IPv4-адрес закодирован в 32-разрядные 32 бита IPv6-адреса, а высокий порядок 96 битов содержит фиксированный префикс 0:0:0:0:0:0:0:FFFF. В RFC 4291 указан формат адресов iPv6, сопоставленный с IPv4. Дополнительные сведения см. в www.ietf.org/rfc/rfc4291.txt. Макрос IN6ADDR_SETV4MAPPED в Mstcpip.h можно использовать для преобразования IPv4-адреса в требуемый формат IPv4-адресов, сопоставленных с IPv6.

Установка подключения с помощью WSAConnectByList

#ifndef UNICODE
#define UNICODE
#endif

#define WIN32_LEAN_AND_MEAN

#include <winsock2.h>
#include <Ws2tcpip.h>
#include <stdio.h>

// Link with ws2_32.lib
#pragma comment(lib, "Ws2_32.lib")

SOCKET OpenAndConnect(SOCKET_ADDRESS_LIST *AddressList) 
{
    SOCKET ConnSocket;
    DWORD ipv6only = 0;
    int iResult;
    BOOL bSuccess;
    SOCKADDR_STORAGE LocalAddr = {0};
    SOCKADDR_STORAGE RemoteAddr = {0};
    DWORD dwLocalAddr = sizeof(LocalAddr);
    DWORD dwRemoteAddr = sizeof(RemoteAddr);

    ConnSocket = socket(AF_INET6, SOCK_STREAM, 0);
    if (ConnSocket == INVALID_SOCKET){
        return INVALID_SOCKET;
    }

    iResult = setsockopt(ConnSocket, IPPROTO_IPV6,
        IPV6_V6ONLY, (char*)&ipv6only, sizeof(ipv6only) );
    if (iResult == SOCKET_ERROR){
        closesocket(ConnSocket);
        return INVALID_SOCKET;       
    }

    // AddressList may contain IPv6 and/or IPv4Mapped addresses
    bSuccess = WSAConnectByList(ConnSocket,
            AddressList,
            &dwLocalAddr,
            (SOCKADDR*)&LocalAddr,
            &dwRemoteAddr,
            (SOCKADDR*)&RemoteAddr,
            NULL,
            NULL);
    if (bSuccess){
        return ConnSocket;
    } else {
        return INVALID_SOCKET;
    }
}

getaddrinfo

Функция getaddrinfo также выполняет обработку многих функций. Ранее для создания, открытия, а затем привязки адреса к сокету требуются вызовы ряда функций сокетов Windows. С помощью функции getaddrinfo строки исходного кода, необходимые для выполнения такой работы, могут быть значительно сокращены. В следующих двух примерах показан исходный код, необходимый для выполнения этих задач с функцией getaddrinfo и без нее.

Выполнение open, Connect и Bind Using getaddrinfo

#ifndef UNICODE
#define UNICODE
#endif

#define WIN32_LEAN_AND_MEAN

#include <winsock2.h>
#include <Ws2tcpip.h>
#include <stdio.h>

// Link with ws2_32.lib
#pragma comment(lib, "Ws2_32.lib")

SOCKET OpenAndConnect(char *ServerName, char *PortName, int SocketType)
{
    SOCKET ConnSocket;
    ADDRINFO *AI;

    if (getaddrinfo(ServerName, PortName, NULL, &AI) != 0) {
        return INVALID_SOCKET;
    }

    ConnSocket = socket(AI->ai_family, SocketType, 0);
    if (ConnSocket == INVALID_SOCKET) {
        freeaddrinfo(AI);
        return INVALID_SOCKET;
    }

    if (connect(ConnSocket, AI->ai_addr, (int) AI->ai_addrlen) == SOCKET_ERROR) {
        closesocket(ConnSocket);
        freeaddrinfo(AI);
        return INVALID_SOCKET;
    }

    return ConnSocket;
}

Выполнение open, Connect и Bind без использования getaddrinfo

#ifndef UNICODE
#define UNICODE
#endif

#define WIN32_LEAN_AND_MEAN

#include <winsock2.h>
#include <Ws2tcpip.h>
#include <stdio.h>

// Link with ws2_32.lib
#pragma comment(lib, "Ws2_32.lib")

SOCKET OpenAndConnect(char *ServerName, unsigned short Port, int SocketType) 
{
    SOCKET ConnSocket;
    LPHOSTENT hp;
    SOCKADDR_IN ServerAddr;
    
    ConnSocket = socket(AF_INET, SocketType, 0); /* Open a socket */
    if (ConnSocket < 0 ) {
        return INVALID_SOCKET;
    }

    memset(&ServerAddr, 0, sizeof(ServerAddr));
    ServerAddr.sin_family = AF_INET;
    ServerAddr.sin_port = htons(Port);

    if (isalpha(ServerName[0])) {   /* server address is a name */
        hp = gethostbyname(ServerName);
        if (hp == NULL) {
            return INVALID_SOCKET;
        }
        ServerAddr.sin_addr.s_addr = (ULONG) hp->h_addr;
    } else { /* Convert nnn.nnn address to a usable one */
        ServerAddr.sin_addr.s_addr = inet_addr(ServerName);
    } 

    if (connect(ConnSocket, (LPSOCKADDR)&ServerAddr, 
        sizeof(ServerAddr)) == SOCKET_ERROR)
    {
        closesocket(ConnSocket);
        return INVALID_SOCKET;
    }

    return ConnSocket;
}

Обратите внимание, что оба этих примера исходного кода выполняют одни и те же задачи, но первый пример с помощью функции getaddrinfo требует меньше строк исходного кода и может обрабатывать адреса IPv6 или IPv4. Количество строк исходного кода, исключенных с помощью функции getaddrinfo , зависит.

Примечание.

В рабочем исходном коде приложение будет выполнять итерацию по набору адресов, возвращаемых функцией gethostbyname или getaddrinfo. Эти примеры опустите этот шаг в пользу простоты.

 

Другая проблема, которую необходимо устранить при изменении существующего приложения IPv4 для поддержки IPv6, связана с порядком вызова функций. Как getaddrinfo, так и gethostbyname требуют, чтобы последовательность вызовов функций выполнялись в определенном порядке.

На платформах, где используются IPv4 и IPv6, семейство адресов удаленного имени узла неизвестно заранее. Поэтому сначала необходимо выполнить разрешение адресов с помощью функции getaddrinfo , чтобы определить IP-адрес и семейство адресов удаленного узла. Затем функцию сокета можно вызвать, чтобы открыть сокет семейства адресов, возвращенный getaddrinfo. Это важное изменение в написании приложений сокетов Windows, так как многие приложения IPv4 обычно используют другой порядок вызовов функций.

Большинство приложений IPv4 сначала создают сокет для семейства адресов AF_INET, а затем выполняют разрешение имен, а затем используют сокет для подключения к разрешенным IP-адресу. При создании таких приложений с поддержкой IPv6 вызов функции сокета должен быть отложен до тех пор, пока после разрешения имен, когда семейство адресов было отпугнуто. Обратите внимание, что если разрешение имен возвращает как IPv4, так и IPv6-адреса, необходимо использовать отдельные сокеты IPv4 и IPv6 для подключения к этим целевым адресам. Все эти сложности можно избежать с помощью функции WSAConnectByName в Windows Vista и более поздних версий, поэтому разработчикам приложений рекомендуется использовать эту новую функцию.

В следующем примере кода показана правильная последовательность для первого разрешения имен (выполненная в четвертой строке в следующем примере исходного кода), а затем открытие сокета (выполнено в строке 19-й строки в следующем примере кода). Этот пример представляет собой фрагмент из файла Client.c, найденного в коде клиента с поддержкой IPv6 в приложении B. Функция PrintError, вызванная в следующем примере кода, указана в примере Client.c.

#ifndef UNICODE
#define UNICODE
#endif

#define WIN32_LEAN_AND_MEAN

#include <winsock2.h>
#include <Ws2tcpip.h>
#include <stdio.h>

// Link with ws2_32.lib
#pragma comment(lib, "Ws2_32.lib")

SOCKET OpenAndConnect(char *Server, char *PortName, int Family, int SocketType)
{

    int iResult = 0;
    SOCKET ConnSocket = INVALID_SOCKET;

    ADDRINFO *AddrInfo = NULL;
    ADDRINFO *AI = NULL;
    ADDRINFO Hints;

    char *AddrName = NULL;

    memset(&Hints, 0, sizeof (Hints));
    Hints.ai_family = Family;
    Hints.ai_socktype = SocketType;

    iResult = getaddrinfo(Server, PortName, &Hints, &AddrInfo);
    if (iResult != 0) {
        printf("Cannot resolve address [%s] and port [%s], error %d: %s\n",
               Server, PortName, WSAGetLastError(), gai_strerror(iResult));
        return INVALID_SOCKET;
    }
    //
    // Try each address getaddrinfo returned, until we find one to which
    // we can successfully connect.
    //
    for (AI = AddrInfo; AI != NULL; AI = AI->ai_next) {

        // Open a socket with the correct address family for this address.
        ConnSocket = socket(AI->ai_family, AI->ai_socktype, AI->ai_protocol);
        if (ConnSocket == INVALID_SOCKET) {
            printf("Error Opening socket, error %d\n", WSAGetLastError());
            continue;
        }
        //
        // Notice that nothing in this code is specific to whether we 
        // are using UDP or TCP.
        //
        // When connect() is called on a datagram socket, it does not 
        // actually establish the connection as a stream (TCP) socket
        // would. Instead, TCP/IP establishes the remote half of the
        // (LocalIPAddress, LocalPort, RemoteIP, RemotePort) mapping.
        // This enables us to use send() and recv() on datagram sockets,
        // instead of recvfrom() and sendto().
        //

        printf("Attempting to connect to: %s\n", Server ? Server : "localhost");
        if (connect(ConnSocket, AI->ai_addr, (int) AI->ai_addrlen) != SOCKET_ERROR)
            break;

        if (getnameinfo(AI->ai_addr, (socklen_t) AI->ai_addrlen, AddrName,
                        sizeof (AddrName), NULL, 0, NI_NUMERICHOST) != 0) {
            strcpy_s(AddrName, sizeof (AddrName), "<unknown>");
            printf("connect() to %s failed with error %d\n", AddrName, WSAGetLastError());
            closesocket(ConnSocket);
            ConnSocket = INVALID_SOCKET;
        }    
    }
    return ConnSocket;
}

Функции IP Helper

Наконец, приложения, использующие функцию Поддержки IP-адресов GetAdaptersInfo, и ее связанную структуру IP_ADAPTER_INFO, должны признать, что эта функция и структура ограничены IPv4-адресами. Замены этой функции и структуры с поддержкой IPv6 — это функция GetAdaptersAddresses и структура IP_ADAPTER_ADDRESSES. Приложения с поддержкой IPv6, использующие API вспомогательных IP-адресов, должны использовать функцию GetAdaptersAddresses и соответствующую структуру IP_ADAPTER_ADDRESSES с поддержкой IPv6, как определено в пакете средств разработки программного обеспечения Microsoft Windows (SDK).

Рекомендации

Лучший подход к использованию вызовов функций, совместимых с IPv6, — использовать функцию getaddrinfo для получения перевода узлов в адрес. Начиная с Windows XP функция getaddrinfo делает функцию gethostbyname ненужной, поэтому приложение должно использовать функцию getaddrinfo вместо будущих проектов программирования. Хотя корпорация Майкрософт продолжит поддерживать gethostbyname, эта функция не будет расширена для поддержки IPv6. Для прозрачной поддержки получения сведений о узле IPv6 и IPv4 необходимо использовать getaddrinfo.

В следующем примере показано, как лучше использовать функцию getaddrinfo . Обратите внимание, что функция при правильном использовании, как показано в этом примере, обрабатывает как преобразование IPv6, так и IPv4 узла в адрес, но также получает другие полезные сведения о узле, например тип поддерживаемых сокетов. Этот пример представляет собой фрагмент из примера Client.c, найденного в приложении B.

#ifndef UNICODE
#define UNICODE
#endif

#define WIN32_LEAN_AND_MEAN

#include <winsock2.h>
#include <Ws2tcpip.h>
#include <stdio.h>

// Link with ws2_32.lib
#pragma comment(lib, "Ws2_32.lib")

int ResolveName(char *Server, char *PortName, int Family, int SocketType)
{

    int iResult = 0;

    ADDRINFO *AddrInfo = NULL;
    ADDRINFO *AI = NULL;
    ADDRINFO Hints;

   //
    // By not setting the AI_PASSIVE flag in the hints to getaddrinfo, we're
    // indicating that we intend to use the resulting address(es) to connect
    // to a service.  This means that when the Server parameter is NULL,
    // getaddrinfo will return one entry per allowed protocol family
    // containing the loopback address for that family.
    //
    
    memset(&Hints, 0, sizeof(Hints));
    Hints.ai_family = Family;
    Hints.ai_socktype = SocketType;
    iResult = getaddrinfo(Server, PortName, &Hints, &AddrInfo);
    if (iResult != 0) {
        printf("Cannot resolve address [%s] and port [%s], error %d: %s\n",
               Server, PortName, WSAGetLastError(), gai_strerror(iResult));
        return SOCKET_ERROR;
    }
     return 0;
}

Примечание.

Версия функции getaddrinfo , поддерживающей IPv6, является новой для выпуска Windows XP Windows.

 

Код, чтобы избежать

Преобразование адресов узла традиционно достигнуто с помощью функции gethostbyname. Начиная с Windows XP:

  • Функция getaddrinfo делает функцию gethostbyname устаревшей.
  • Приложения должны использовать функцию getaddrinfo вместо функции gethostbyname .

Задачи программирования

Изменение существующего приложения IPv4 для добавления поддержки IPv6

  1. Получите программу Checkv4.exe. Эта программа устанавливается в составе пакета SDK для Windows. Более ранняя версия средства Checkv4.exe также была включена в состав предварительной версии технологии Microsoft IPv6 для Windows 2000.
  2. Запустите программу Checkv4.exe в коде. Ознакомьтесь с помощью программы Checkv4.exe, чтобы узнать о запуске служебной программы версии для файлов.
  3. Программа оповещает об использовании функции gethostbyname, gethostbyaddr и других функций, доступных только для IPv4, и предоставляет рекомендации по замене их функцией, совместимой с IPv6, например getaddrinfo и getnameinfo.
  4. Замените все экземпляры функции gethostbyname и связанный код соответствующим образом с функцией getaddrinfo . В Windows Vista при необходимости используйте функцию WSAConnectByName или WSAConnectByList.
  5. Замените все экземпляры функции gethostbyaddr и связанный код соответствующим образом с функцией getnameinfo.

Кроме того, вы можете искать базу кода для экземпляров функций gethostbyname и gethostbyaddr, а также изменять все такое использование (и другой связанный код, как это возможно) на функции getaddrinfo и getnameinfo.

Руководство по IPv6 для приложений сокетов Windows

Изменение структур данных для приложений Winsock IPv6

Сокеты с двойным стеком для приложений Winsock IPv6

Использование жестких IPv4-адресов

Проблемы с пользовательским интерфейсом для приложений Winsock IPv6

Базовые протоколы для приложений Winsock IPv6