0

我编写的以下 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;
}
4

1 回答 1

3

如果你想让你的服务器监听 IPV4 和 IPV6 地址,你需要设置两个套接字来监听。

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

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,正如这个答案所提到的:如何同时支持 IPv4 和 IPv6 连接

于 2012-12-13T18:26:53.250 回答