2012-12-13 37 views
0

我編寫的以下c函數返回一個文件描述符,它接受IPv4連接但不接受IPv6。 有人能幫我弄清楚出了什麼問題嗎? 我懷疑我沒有正確使用getaddrinfo()。在c web服務器上接受ipv6請求的麻煩

打開文件描述符以偵聽連接。

/* 
* open_listenfd - open and return a listening socket on port 
*  Returns -1 and sets errno on Unix error. 
*/ 
int open_listenfd(int port) 
{ 
    const char* hostname=0; 
    // Converts port to string 
    char* pName = malloc(numPlaces(port) + 1); 
    sprintf(pName, "%d", port); 
    const char* portname= pName; 

    struct addrinfo hints; 
    memset(&hints,0,sizeof(hints)); 
    hints.ai_family=AF_UNSPEC; 
    hints.ai_socktype=SOCK_STREAM; 
    hints.ai_protocol= 0; 
    hints.ai_flags=AI_PASSIVE|AI_ADDRCONFIG; 
    struct addrinfo* res=0; 
    int err=getaddrinfo(hostname,portname,&hints,&res); 
    free(pName); 
    if (err!=0) { 
      return -1; 
    } 

    int listenfd, optval=1; 
    struct sockaddr_in serveraddr; 

    /* Create a socket descriptor */ 
    if ((listenfd = socket(res->ai_family,res->ai_socktype, res->ai_protocol)) < 0) 
     return -1; 

    /* Eliminates "Address already in use" error from bind. */ 
    if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, 
       (const void *)&optval , sizeof(int)) < 0) 
     return -1; 

    /* Listenfd will be an endpoint for all requests to port 
     on any IP address for this host */ 
    bzero((char *) &serveraddr, sizeof(serveraddr)); 
    serveraddr.sin_family = /*AF_INET;*/ AF_UNSPEC; 
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); 
    serveraddr.sin_port = htons((unsigned short)port); 
    if (bind(listenfd, res->ai_addr, res->ai_addrlen/*(SA *)&serveraddr, sizeof(serveraddr)*/) < 0) 
    return -1; 
    freeaddrinfo(res); 

    /* Make it a listening socket ready to accept connection requests */ 
    if (listen(listenfd, 1024) < 0) 
     return -1; 
    return listenfd; 
} 
+0

您希望服務器僅偵聽ipv6連接還是還要偵聽ipv6連接? – alk

回答

3

如果你希望你的服務器偵聽IPv4和IPv6地址你 需要 可以設置兩個插座上偵聽。

getaddrinfo()可能會返回給定主機和/或服務的多個互聯網地址的信息。

hints結構體的成員ai_family指定了調用者感興趣的地址族。如果指定了AF_UNSPEC,則可能返回IPV4和IPV6地址。

要找出是否有可用的你可能會喜歡國防部您的代碼像這樣這樣的地址:

int open_listenfd(int port, int * pfdSocketIpV4, int * pfdSocketIpV6) 
{ 
    ... 

    struct addrinfo hints = {0}; 
    hints.ai_family = AF_UNSPEC; 
    hints.ai_socktype = SOCK_STREAM; 
    hints.ai_flags = AI_PASSIVE|AI_ADDRCONFIG; 
    hints.ai_next = NULL; 

    struct addrinfo * res = NULL; 

    int err=getaddrinfo(hostname, portname, &hints, &res); 
    free(pName); 
    if (err) 
    { 
    return -1; 
    } 

    struct addrinfo * pAddrInfoIpV4 = NULL; 
    struct addrinfo * pAddrInfoIpV6 = NULL; 

    { 
    struct addrinfo * pAddrInfo = res; 

    /* Loop over all address infos found until a IPV4 and a IPV6 address is found. */ 
    while (pAddrInfo) 
    { 
     if (!pAddrInfoIpV4 && AF_INET == pAddrInfo->ai_family) 
     { 
     pAddrInfoIpV4 = pAddrInfo; /* Take first IPV4 address available */ 
     } 
     else if (!pAddrInfoIpV6 && AF_INET6 == pAddrInfo->ai_family) 
     { 
     pAddrInfoIpV6 = pAddrInfo; /* Take first IPV6 address available */ 
     } 
     else 
     { 
     break; /* Already got an IPV4 and IPV6 address, so skip the rest */ 
     } 

     pAddrInfo= pAddrInfo->ai_next; /* Get next address info, if any */ 
    } 
    } 

    if (pAddrInfoIpV4) 
    { 
    ... /* create, bind and make IPV4 socket listen */ 
    int fdSocketIpV4 = socket(pAddrInfoIpV4->ai_family,... 

    *pfdSocketIpV4 = fdSocketIpV4; 
    } 

    if (pAddrInfoIpV6) 
    { 
    /* create, bind and make IPV6 socket listen */ 
    int fdSocketIpV6 = socket(pAddrInfoIpV6->ai_family,... 

    *pfdSocketIpV6 = fdSocketIpV6; 
    } 

    freeaddrinfo(res); 

    ... 

然後調用它像這樣:

... 
int fdSocketIpV4 = -1;  
int fdSocketIpV6 = -1; 

if (0 > open_listenfd(port, &fdSocketIpV4, &fdSocketIpV6)) 
{ 
    printf("Error executing 'open_listenfd()'\n"); 
} 
else 
{ 
    ... /* go for accepting connectings on 'fdSocketIpV4' and 'fdSocketIpV6' */ 

更新:

評論人:Per Johan sson另一種方法是設置一個雙棧套接字,同時支持Ipv4和Ipv6,如此答案所述:How to support both IPv4 and IPv6 connections

+0

謝謝,這工作得很好。 –

+1

@MLD_Saturn:不客氣。如果你喜歡這個答案,你可以自由地上傳它。如果它解決了你的問題,你也可以通過點擊複選標記來標記。 – alk

+1

你必須設置兩個套接字並不是真的。還可以選擇使用標記爲接受兩者的IPv6套接字。 –