无法接受c web服务器上的ipv6请求

时间:2012-12-13 17:11:46

标签: c webserver ipv6

我编写的以下c函数返回一个接受IPv4连接但不接受IPv6的文件描述符。 有人能帮我弄清楚出了什么问题吗? 我怀疑我没有正确使用getaddrinfo()。

打开侦听连接的文件描述符。

/*
 * 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;
}

1 个答案:

答案 0 :(得分:3)

如果您希望服务器监听IPV4和IPV6地址,需要可以设置两个套接字来监听。

getaddrinfo()可能会返回给定主机和/或服务的多个互联网地址的信息。

传递的ai_family结构的成员hints指定调用者感兴趣的地址族。如果指定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 Johansson 所评论的另一种方法是建立一个双栈插槽,支持Ipv4和Ipv6,如本答案所述:How to support both IPv4 and IPv6 connections