Dela via


Funktionsanrop för IPv6 Winsock-program

Nya funktioner har introducerats i Windows Sockets-gränssnittet som är särskilt utformat för att göra Windows Sockets-programmering enklare. En av fördelarna med dessa nya Windows Sockets-funktioner är integrerat stöd för både IPv6 och IPv4.

Dessa nya Windows Sockets-funktioner innehåller följande:

Dessutom har nya IP-hjälpfunktioner med stöd för både IPv6 och IPv4 lagts till för att förenkla Windows Sockets-programmering. Dessa nya IP-hjälpfunktioner innehåller följande:

När du lägger till IPv6-stöd för ett program bör följande riktlinjer användas:

  • Använd WSAConnectByName för att upprätta en anslutning till en slutpunkt med ett värdnamn och en port. Funktionen WSAConnectByName är tillgänglig i Windows Vista och senare.
  • Använd WSAConnectByList för att upprätta en anslutning till en av en samling möjliga slutpunkter som representeras av en uppsättning måladresser (värdnamn och portar). Funktionen WSAConnectByList är tillgänglig i Windows Vista och senare.
  • Ersätt gethostbyname funktionsanrop med anrop till en av de nya funktionerna getaddrinfo Windows Sockets. Funktionen getaddrinfo med stöd för IPv6-protokollet finns i Windows XP och senare. IPv6-protokollet stöds också i Windows 2000 när IPv6 Technology Preview för Windows 2000 installeras.
  • Ersätt gethostbyaddr funktionsanrop med anrop till en av de nya funktionerna getnameinfo Windows Sockets. Funktionen getnameinfo med stöd för IPv6-protokollet finns i Windows XP och senare. IPv6-protokollet stöds också i Windows 2000 när IPv6 Technology Preview för Windows 2000 installeras.

WSAConnectByName

Funktionen WSAConnectByName förenklar anslutningen till en slutpunkt med hjälp av en strömbaserad socket med tanke på målets värdnamn eller IP-adress (IPv4 eller IPv6). Den här funktionen minskar den källkod som krävs för att skapa ett IP-program som är agnostisk för den version av IP-protokollet som används. WSAConnectByName ersätter följande steg i ett typiskt TCP-program till ett enda funktionsanrop:

  • Matcha ett värdnamn till en uppsättning IP-adresser.
  • För varje IP-adress:
    • Skapa en socket för lämplig adressfamilj.
    • Försöker ansluta till fjärr-IP-adressen. Om anslutningen lyckades returneras den. annars provas nästa fjärranslutna IP-adress för värden.

Funktionen WSAConnectByName går utöver att bara matcha namnet och sedan försöka ansluta. Funktionen använder alla fjärradresser som returneras med namnmatchning och alla den lokala datorns käll-IP-adresser. Den försöker först ansluta med hjälp av adresspar med störst chans att lyckas. Därför WSAConnectByName- inte bara säkerställa att en anslutning upprättas om möjligt, utan också minimerar tiden för att upprätta anslutningen.

Om du vill aktivera både IPv6- och IPv4-kommunikation använder du följande metod:

  • Funktionen setsockopt måste anropas på en socket som skapats för AF_INET6-adressfamiljen för att inaktivera alternativet IPV6_V6ONLY socket innan du anropar WSAConnectByName. Detta uppnås genom att anropa funktionen setsockopt på socketen med parametern nivå inställd på IPPROTO_IPV6 (se IPPROTO_IPV6 Socket-alternativ), optname parameter inställd på IPV6_V6ONLYoch optvalue parametervärde inställt på noll.

Om ett program behöver binda till en specifik lokal adress eller port kan WSAConnectByName inte användas eftersom socketparametern för att WSAConnectByName måste vara en obundna socket.

Kodexemplet nedan visar bara några rader kod som behövs för att använda den här funktionen för att implementera ett program som är agnostiskt för IP-versionen.

Upprätta en anslutning med 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

Funktionen WSAConnectByList upprättar en anslutning till en värd med en uppsättning möjliga värdar (representeras av en uppsättning mål-IP-adresser och portar). Funktionen tar alla IP-adresser och portar för slutpunkten och alla den lokala datorns IP-adresser och försöker ansluta med alla möjliga adresskombinationer.

WSAConnectByList är relaterad till funktionen WSAConnectByName. I stället för att ta ett enda värdnamn accepterar WSAConnectByList en lista med värdar (måladresser och portpar) och ansluter till någon av adresserna och portarna i den angivna listan. Den här funktionen är utformad för att stödja scenarier där ett program måste ansluta till alla tillgängliga värdar från en lista över potentiella värdar.

På samma sätt som WSAConnectByNameminskar funktionen WSAConnectByList avsevärt den källkod som krävs för att skapa, binda och ansluta en socket. Den här funktionen gör det mycket enklare att implementera ett program som är agnostiskt för IP-versionen. Listan över adresser för en värd som accepteras av den här funktionen kan vara IPv6- eller IPv4-adresser.

För att både IPv6- och IPv4-adresser ska kunna skickas i den enda adresslista som godkänts av funktionen måste följande steg utföras innan funktionen anropas:

  • Funktionen setsockopt måste anropas på en socket som skapats för AF_INET6-adressfamiljen för att inaktivera alternativet IPV6_V6ONLY socket innan du anropar WSAConnectByList. Detta uppnås genom att anropa funktionen setsockopt på socketen med parametern nivå inställd på IPPROTO_IPV6 (se IPPROTO_IPV6 Socket-alternativ), optname parameter inställd på IPV6_V6ONLYoch optvalue parametervärde inställt på noll.
  • Alla IPv4-adresser måste representeras i IPv4-mappat IPv6-adressformat som gör att ett IPv6-program endast kan kommunicera med en IPv4-nod. Med IPv4-mappat IPv6-adressformat kan IPv4-adressen för en IPv4-nod representeras som en IPv6-adress. IPv4-adressen kodas till 32 bitar av IPv6-adressen i låg ordning och 96 bitar med hög ordning innehåller det fasta prefixet 0:0:0:0:0:FFFF. IPv4-mappat IPv6-adressformat anges i RFC 4291. Mer information finns i www.ietf.org/rfc/rfc4291.txt. Det IN6ADDR_SETV4MAPPED makrot i Mstcpip.h kan användas för att konvertera en IPv4-adress till det IPv4-mappade IPv6-adressformatet.

Upprätta en anslutning med 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

Funktionen getaddrinfo utför också bearbetningen av många funktioner. Tidigare krävdes anrop till ett antal Windows Sockets-funktioner för att skapa, öppna och sedan binda en adress till en socket. Med funktionen getaddrinfo kan de rader med källkod som krävs för att utföra sådant arbete minskas avsevärt. Följande två exempel illustrerar den källkod som krävs för att utföra dessa uppgifter med och utan funktionen getaddrinfo.

Utför en open, connect och bindning med hjälp av 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;
}

Utför en open, connect och bind utan att använda 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;
}

Observera att båda dessa källkodsexempel utför samma uppgifter, men det första exemplet med hjälp av funktionen getaddrinfo kräver färre rader källkod och kan hantera IPv6- eller IPv4-adresser. Antalet rader med källkod som elimineras med hjälp av funktionen getaddrinfo varierar.

Not

I produktionskällkoden itererar programmet via den uppsättning adresser som returneras av funktionen gethostbyname eller getaddrinfo. De här exemplen utelämnar det steget till förmån för enkelhetens skull.

 

Ett annat problem som du måste åtgärda när du ändrar ett befintligt IPv4-program för att stödja IPv6 är associerat med ordningen i vilken funktioner anropas. Både getaddrinfo och gethostbyname kräva att en sekvens med funktionsanrop görs i en viss ordning.

På plattformar där både IPv4 och IPv6 används är adressfamiljen för fjärrvärdnamnet inte känd i förväg. Så adressmatchning med hjälp av getaddrinfo-funktionen måste köras först för att fastställa IP-adressen och adressfamiljen för fjärrvärden. Sedan kan funktionen socket anropas för att öppna en socket för adressfamiljen som returneras av getaddrinfo. Detta är en viktig förändring i hur Windows Sockets-program skrivs, eftersom många IPv4-program tenderar att använda en annan ordning för funktionsanrop.

De flesta IPv4-program skapar först en socket för AF_INET-adressfamiljen, utför sedan namnmatchning och använder sedan socketen för att ansluta till den lösta IP-adressen. När du gör sådana program IPv6-kompatibla måste socket--funktionsanropet fördröjas tills efter namnmatchningen när adressfamiljen har avklassats. Observera att om namnmatchningen returnerar både IPv4- och IPv6-adresser måste separata IPv4- och IPv6-socketar användas för att ansluta till dessa måladresser. Alla dessa komplexiteter kan undvikas med hjälp av funktionen WSAConnectByName i Windows Vista och senare, så programutvecklare uppmanas att använda den här nya funktionen.

Följande kodexempel visar rätt sekvens för att utföra namnmatchning först (utförs på den fjärde raden i följande källkodsexempel) och öppnar sedan en socket (som utförs i 19- rad i följande kodexempel). Det här exemplet är ett utdrag från filen Client.c som finns i IPv6-Enabled Client Code- i bilaga B. Funktionen PrintError som anropas i följande kodexempel visas i Exemplet 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-hjälpfunktioner

Slutligen måste program som använder funktionen IP Helper GetAdaptersInfo, och dess associerade struktur IP_ADAPTER_INFO, känna igen att både den här funktionen och strukturen är begränsade till IPv4-adresser. IPv6-aktiverade ersättningar för den här funktionen och strukturen är GetAdaptersAddresses funktion och IP_ADAPTER_ADDRESSES struktur. IPv6-aktiverade program som använder API:et för IP-hjälp bör använda funktionen GetAdaptersAddresses och motsvarande IPv6-aktiverade IP_ADAPTER_ADDRESSES struktur, som båda definieras i Microsoft Windows Software Development Kit (SDK).

Rekommendationer

Det bästa sättet att se till att programmet använder IPv6-kompatibla funktionsanrop är att använda funktionen getaddrinfo för att hämta översättning från värd till adress. Från och med Windows XP gör funktionen getaddrinfogethostbyname inte att fungera, och programmet bör därför använda funktionen getaddrinfo i stället för framtida programmeringsprojekt. Även om Microsoft fortsätter att stödja gethostbynameutökas inte den här funktionen för att stödja IPv6. För transparent stöd för att hämta IPv6- och IPv4-värdinformation måste du använda getaddrinfo.

I följande exempel visas hur du bäst använder funktionen getaddrinfo. Observera att funktionen, när den används korrekt som det här exemplet visar, hanterar både IPv6- och IPv4-värd-till-adress-översättning korrekt, men den hämtar även annan användbar information om värden, till exempel vilken typ av sockets som stöds. Det här exemplet är ett utdrag från Client.c-exemplet som finns i bilaga 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;
}

Not

Versionen av getaddrinfo funktion som stöder IPv6 är ny för Windows XP-versionen av Windows.

 

Kod att undvika

Översättning av värdadresser har traditionellt uppnåtts med hjälp av funktionen gethostbyname. Från och med Windows XP:

  • Funktionen getaddrinfo gör funktionen gethostbyname föråldrad.
  • Dina program bör använda funktionen getaddrinfo i stället för funktionen gethostbyname.

Kodningsuppgifter

Om du vill ändra ett befintligt IPv4-program för att lägga till stöd för IPv6-

  1. Hämta verktyget Checkv4.exe. Det här verktyget installeras som en del av Windows SDK. En äldre version av verktyget Checkv4.exe ingick också som en del av Microsoft IPv6 Technology Preview för Windows 2000.
  2. Kör verktyget Checkv4.exe mot koden. Se Använda Checkv4.exe-verktyget för att lära dig mer om hur du kör versionsverktyget mot dina filer.
  3. Verktyget varnar dig för användning av gethostbyname, gethostbyaddroch andra IPv4-only-funktioner och ger rekommendationer om hur du ersätter dem med den IPv6-kompatibla funktionen, till exempel getaddrinfo och getnameinfo.
  4. Ersätt alla instanser av funktionen gethostbyname och tillhörande kod efter behov med funktionen getaddrinfo. I Windows Vista använder du funktionen WSAConnectByName eller WSAConnectByList när det är lämpligt.
  5. Ersätt alla instanser av funktionen gethostbyaddr och tillhörande kod efter behov med funktionen getnameinfo.

Du kan också söka i kodbasen efter instanser av funktionerna gethostbyname och gethostbyaddr och ändra all sådan användning (och annan associerad kod efter behov) till getaddrinfo och getnameinfo funktioner.

IPv6-guide för Windows Sockets-program

Ändra datastrukturer för IPv6 Winsock-program

Dual-Stack Sockets för IPv6 Winsock-program

användning av hårdkodade IPv4-adresser

problem med användargränssnittet för IPv6 Winsock-program

underliggande protokoll för IPv6 Winsock-program