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 アドレスをリッスンさせたい場合は、リッスンする 2 つのソケットをセットアップする必要があります。

getaddrinfo()特定のホストやサービスの複数のインターネット アドレスの情報を返す場合があります。

渡された構造体のメンバーai_familyhints、呼び出し元が関心を持っているアドレス ファミリを指定します。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の両方をサポートするデュアル スタック ソケットをセットアップすることです。

于 2012-12-13T18:26:53.250 に答える