Sdílet prostřednictvím


Volání funkcí pro aplikace winsock IPv6

Nové funkce byly představeny v rozhraní Windows Sockets speciálně navržené tak, aby programování rozhraní Windows Sockets bylo jednodušší. Jednou z výhod těchto nových funkcí Windows Sockets je integrovaná podpora pro IPv6 i IPv4.

Mezi tyto nové funkce Windows Sockets patří:

Kromě toho byly přidány nové pomocné funkce protokolu IP s podporou IPv6 i IPv4 pro zjednodušení programování rozhraní Windows Sockets. Mezi tyto nové pomocné funkce IP adres patří:

Při přidávání podpory protokolu IPv6 do aplikace by se měly použít následující pokyny:

  • Pomocí WSAConnectByName vytvořte připojení ke koncovému bodu s názvem hostitele a portem. Funkce WSAConnectByName je k dispozici v systému Windows Vista a novějších verzích.
  • Použijte WSAConnectByList k navázání připojení k jedné z kolekce možných koncových bodů reprezentovaných sadou cílových adres (názvy hostitelů a porty). Funkce WSAConnectByList je k dispozici v systému Windows Vista a novějších verzích.
  • Nahraďte gethostbyname volání funkce voláním jedné z nových funkcí getaddrinfo Windows Sockets. Funkce getaddrinfo s podporou protokolu IPv6 je k dispozici v systému Windows XP a novějších verzích. Protokol IPv6 je také podporován v systému Windows 2000 při instalaci IPv6 Technology Preview pro Windows 2000.
  • Nahraďte gethostbyaddr volání funkce voláním jedné z nových funkcí getnameinfo Windows Sockets. Funkce getnameinfo s podporou protokolu IPv6 je k dispozici v systému Windows XP a novějších verzích. Protokol IPv6 je také podporován v systému Windows 2000 při instalaci IPv6 Technology Preview pro Windows 2000.

WSAConnectByName

Funkce WSAConnectByName zjednodušuje připojení ke koncovému bodu pomocí soketu založeného na datovém proudu s použitím názvu hostitele nebo IP adresy cíle (IPv4 nebo IPv6). Tato funkce snižuje zdrojový kód potřebný k vytvoření aplikace IP, která je nezávislá na verzi použitého protokolu IP. WSAConnectByName nahradí následující kroky v typické aplikaci TCP jedním voláním funkce:

  • Přeložte název hostitele na sadu IP adres.
  • Pro každou IP adresu:
    • Vytvořte soket příslušné řady adres.
    • Pokusí se připojit ke vzdálené IP adrese. Pokud bylo připojení úspěšné, vrátí se; jinak se zkusí další vzdálená IP adresa hostitele.

Funkce WSAConnectByName přesahuje jen překlad názvu a pokus o připojení. Funkce používá všechny vzdálené adresy vrácené překladem ip adres a všechny zdrojové IP adresy místního počítače. Nejprve se pokusí připojit pomocí párů adres s největší šancí na úspěch. Proto WSAConnectByName nejen zajistí, že se připojení naváže, pokud je to možné, ale také minimalizuje čas na navázání připojení.

Pokud chcete povolit komunikaci IPv6 i IPv4, použijte následující metodu:

  • Funkce setsockopt musí být volána na soketu vytvořeném pro řadu adres AF_INET6, aby bylo možné zakázat možnost IPV6_V6ONLY soketu před voláním WSAConnectByName. Toho dosáhnete voláním funkce setsockopt na soketu s parametrem úrovně nastaveným na IPPROTO_IPV6 (viz IPPROTO_IPV6 Možnosti soketu), parametr optname nastaven na IPV6_V6ONLYa optvalue hodnota parametru nastavená na nulu.

Pokud aplikace potřebuje vytvořit vazbu na konkrétní místní adresu nebo port, pak WSAConnectByName nelze použít, protože parametr soketu pro WSAConnectByName musí být nevázaný soket.

Následující příklad kódu ukazuje, že k implementaci aplikace, která je nezávislá na verzi PROTOKOLU IP, je potřeba použít pouze několik řádků kódu.

Vytvoření připojení pomocí 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

Funkce WSAConnectByList vytvoří připojení k hostiteli vzhledem k sadě možných hostitelů (reprezentované sadou cílových IP adres a portů). Funkce přebírá všechny IP adresy a porty pro koncový bod a všechny IP adresy místního počítače a pokusí se připojit pomocí všech možných kombinací adres.

WSAConnectByList souvisí s funkcí WSAConnectByName. Místo jednoho názvu hostitele WSAConnectByList přijímá seznam hostitelů (cílové adresy a dvojice portů) a připojuje se k jedné z adres a portů v zadaném seznamu. Tato funkce je navržená tak, aby podporovala scénáře, ve kterých se aplikace musí připojit k libovolnému dostupnému hostiteli ze seznamu potenciálních hostitelů.

Podobně jako WSAConnectByNameWSAConnectBy List výrazně snižuje zdrojový kód potřebný k vytvoření, vytvoření vazby a připojení soketu. Tato funkce usnadňuje implementaci aplikace, která je nezávislá na verzi PROTOKOLU IP. Seznam adres hostitele přijatého touto funkcí může být adresy IPv6 nebo IPv4.

Aby funkce mohla předávat adresy IPv6 i IPv4 v jediném seznamu adres přijatých funkcí, je nutné před voláním funkce provést následující kroky:

  • Funkce setsockopt musí být volána na soketu vytvořeném pro rodinu adres AF_INET6, aby se před voláním WSAConnectByListzakázala možnost IPV6_V6ONLY soketu . Toho dosáhnete voláním funkce setsockopt na soketu s parametrem úrovně nastaveným na IPPROTO_IPV6 (viz IPPROTO_IPV6 Možnosti soketu), parametr optname nastaven na IPV6_V6ONLYa optvalue hodnota parametru nastavená na nulu.
  • Všechny adresy IPv4 musí být reprezentované ve formátu IPv4 mapované adresy IPv6, který umožňuje komunikaci s uzlem IPv4 pouze aplikací. Formát IPv4 mapované adresy IPv6 umožňuje reprezentovat adresu IPv4 uzlu IPv4 jako adresu IPv6. Adresa IPv4 se zakóduje do 32 bitů adresy IPv6 s nízkým pořadím a 96 bitů s vysokým pořadím obsahuje pevnou předponu 0:0:0:0:0:0:FFFF. Formát IPv4 mapované adresy IPv6 je zadaný v dokumentu RFC 4291. Další informace naleznete v tématu www.ietf.org/rfc/rfc4291.txt. Makro IN6ADDR_SETV4MAPPED v Mstcpip.h lze použít k převodu adresy IPv4 na požadovaný formát IPv4 adresy IPv6.

Vytvoření připojení pomocí 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

Funkce getaddrinfo také provádí zpracování mnoha funkcí. Dříve se vyžadovala volání řady funkcí Windows Sockets k vytvoření, otevření a vytvoření vazby adresy k soketu. S funkcí getaddrinfo lze výrazně snížit řádky zdrojového kódu potřebného k provedení takové práce. Následující dva příklady ilustrují zdrojový kód potřebný k provedení těchto úloh s funkcí getaddrinfo.

Provedení operace Open, Connect a 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;
}

Provedení příkazu Open, Connect a Bind Without 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, 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;
}

Všimněte si, že oba tyto příklady zdrojového kódu provádějí stejné úlohy, ale první příklad pomocí funkce getaddrinfo, vyžaduje méně řádků zdrojového kódu a může zpracovávat adresy IPv6 nebo IPv4. Počet řádků zdrojového kódu eliminovaných pomocí funkce getaddrinfo se liší.

Poznámka

V produkčním zdrojovém kódu by vaše aplikace iterovala sadou adres vrácených gethostbyname nebo getaddrinfo funkce. Tyto příklady tento krok vynechat ve prospěch jednoduchosti.

 

Dalším problémem, který musíte vyřešit při úpravě existující aplikace IPv4 na podporu protokolu IPv6, je přidružen pořadí, ve kterém se volají funkce. getaddrinfo i gethostbyname vyžadují, aby se posloupnost volání funkcí provedla v určitém pořadí.

Na platformách, kde se používají protokoly IPv4 i IPv6, není řada adres názvu vzdáleného hostitele předem známa. Překlad adres pomocí funkce getaddrinfo musí být spuštěn jako první, aby bylo možné určit IP adresu a řadu adres vzdáleného hostitele. Potom lze volat funkci soketu, která otevře soket řady adres vrácených getaddrinfo. Jedná se o důležitou změnu způsobu psaní aplikací Windows Sockets, protože mnoho aplikací IPv4 obvykle používá jiné pořadí volání funkcí.

Většina aplikací IPv4 nejprve vytvoří soket pro řadu AF_INET adres, pak provede překlad ip adres a pak se pomocí soketu připojí k přeložené IP adrese. Při vytváření takových aplikací podporujících protokol IPv6 musí být volání funkce soketu zpožděné, dokud se po vyřešení ip adresy nezpožďuje řada adres. Upozorňujeme, že pokud překlad ip adres vrátí adresy IPv4 i IPv6, musí být pro připojení k těmto cílovým adresám použity samostatné sokety IPv4 a IPv6. Všechny tyto složitosti se dají vyhnout pomocí funkce WSAConnectByName ve Windows Vista a novějších verzích, takže vývojáři aplikací by měli tuto novou funkci používat.

Následující příklad kódu ukazuje správnou sekvenci pro první provedení překladu ip adres (provedená ve čtvrtém řádku v následujícím příkladu zdrojového kódu) a otevření soketu (provedená v 19 řádku v následujícím příkladu kódu). Tento příklad je výňatek ze souboru Client.c nalezeného v IPv6-Enabled klientském kódu v dodatku B. Funkce PrintError volána v následujícím příkladu kódu je uvedena v ukázce 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;
}

Pomocné funkce PROTOKOLU IP

Aplikace, které používají pomocnou funkci PROTOKOLU IP GetAdaptersInfoa její přidružená struktura IP_ADAPTER_INFO, musí rozpoznat, že tato funkce i struktura jsou omezeny na adresy IPv4. Nahrazení této funkce a struktury s podporou protokolu IPv6 jsou funkce GetAdaptersAddresses a struktura IP_ADAPTER_ADDRESSES. Aplikace s podporou protokolu IPv6, které využívají pomocné rozhraní API protokolu IP, by měly používat getAdaptersAddresses funkci a odpovídající strukturu IP_ADAPTER_ADDRESSES s podporou protokolu IPv6, jak definovanou v sadě Microsoft Windows Software Development Kit (SDK).

Doporučení

Nejlepším přístupem k zajištění toho, aby vaše aplikace používala volání funkcí kompatibilní s protokolem IPv6, je použít funkci getaddrinfo pro získání překladu mezi hostiteli. Počínaje systémem Windows XP funkce getaddrinfo způsobí, že funkce gethostbyname nepotřebná a vaše aplikace by proto měla místo toho použít funkci getaddrinfo. I když Microsoft bude i nadále podporovat gethostbyname, tato funkce nebude rozšířena na podporu IPv6. Pokud chcete transparentní podporu pro získání informací o hostitelích IPv6 a IPv4, musíte použít getaddrinfo.

Následující příklad ukazuje, jak nejlépe použít funkci getaddrinfo. Všimněte si, že funkce, pokud se správně používá v tomto příkladu, zpracovává správně překlad IPv6 i IPv4 mezi hostiteli, ale také získá další užitečné informace o hostiteli, například typ podporovaných soketů. Tato ukázka je výňatek z ukázky Client.c nalezené v dodatku 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;
}

Poznámka

Verze funkce getaddrinfo, která podporuje protokol IPv6, je nová pro systém Windows XP verze Windows.

 

Kód, který se má vyhnout

Překlad adres hostitele byl tradičně dosaženo pomocí funkce gethostbyname. Počínaje systémem Windows XP:

  • Funkce getaddrinfo způsobí, že funkce gethostbyname zastaralá.
  • Aplikace by měly místo funkce gethostbyname použít funkci getaddrinfo.

Úkoly kódování

Úprava existující aplikace IPv4 pro přidání podpory pro IPv6

  1. Získejte nástroj Checkv4.exe. Tento nástroj je nainstalován jako součást sady Windows SDK. Starší verze nástroje Checkv4.exe byla také zahrnuta jako součást technologie Microsoft IPv6 Technology Preview pro Windows 2000.
  2. Spusťte nástroj Checkv4.exe proti kódu. Informace o spuštění nástroje verze pro vaše soubory najdete v tématu Použití nástroje Checkv4.exe Utilit y.
  3. Nástroj vás upozorní na použití gethostbyname, gethostbyaddra dalších funkcí jen pro IPv4 a poskytuje doporučení, jak je nahradit funkcí kompatibilní s IPv6, jako je getaddrinfo a getnameinfo.
  4. Nahraďte všechny instance gethostbyname funkce a přidružený kód podle potřeby za getaddrinfo funkce. V systému Windows Vista použijte WSAConnectByName nebo WSAConnectByList funkce podle potřeby.
  5. Nahraďte všechny instance gethostbyaddr funkce a přidružený kód podle potřeby funkcí getnameinfo.

Případně můžete v základu kódu vyhledat instance gethostbyname a gethostbyaddr funkce a podle potřeby změnit všechna taková použití (a další přidružený kód) na getaddrinfo a getnameinfo funkce.

Průvodce IPv6 pro aplikace windows Sockets

změna datových struktur pro aplikace winsock IPv6

Dual-Stack sokety pro aplikace winsock IPv6

použití pevně zakódovaných adres IPv4

problémy s uživatelským rozhraním pro aplikace rozhraní Winsock protokolu IPv6

podkladových protokolů pro aplikace winsock IPv6