使用C语言连接IPv6时出现的问题

6

我正在尝试编写一个通用的回显服务器,可以接受IPv4和IPv6连接。我正在使用设置了getaddrinfo的addrinfo结构。
IPv4连接没有问题,但我无法获得可工作的IPv6连接。我认为我的问题可能是由于错误的getaddrinfo参数,但我无法看到自己哪里出错了。
这是我的代码:

client.c

#include <stdio.h>      
#include <sys/types.h>
#include <sys/socket.h>   
#include <netdb.h>
#include <stdlib.h>
#include <string.h> 
#include <errno.h>

int main(int argc, char *argv[])
{
      int simpleSocket = 0, simplePort = 0,returnStatus = 0, n; 
      char buffer[1024] = "";
      struct hostent *hostinfo;
      struct addrinfo simpleServer, *res;

      if (3 != argc) {
          fprintf(stderr, "Usage: %s <server> <port>\n", argv[0]);
          exit(1);
      }

      simplePort = atoi(argv[2]);    

      memset(&simpleServer, 0, sizeof simpleServer);
      simpleServer.ai_family = AF_UNSPEC;  // use IPv4 or IPv6, whichever
      simpleServer.ai_socktype = SOCK_STREAM;
      simpleServer.ai_flags = AI_PASSIVE;     // fill in my IP for me

      returnStatus = getaddrinfo(argv[1], argv[2], &simpleServer, &res);

      simpleSocket = socket(res->ai_family, res->ai_socktype, res->ai_protocol);

      char *s = NULL;
      switch(res->ai_addr->sa_family) {
      case AF_INET: {
            struct sockaddr_in *addr_in = (struct sockaddr_in *)res;
            s = malloc(INET_ADDRSTRLEN);
            inet_ntop(AF_INET, &(addr_in->sin_addr), s, INET_ADDRSTRLEN);
            returnStatus = connect(simpleSocket, res->ai_addr, res->ai_addrlen);
            break;
      }
      case AF_INET6: {
            struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)res;
            s = malloc(INET6_ADDRSTRLEN);
            inet_ntop(AF_INET6, &(addr_in6->sin6_addr), s, INET6_ADDRSTRLEN);
            returnStatus = connect(simpleSocket, res->ai_addr, res->ai_addrlen);
            break;
      }
      default:
            break;
      }
      fprintf(stdout, "IP address: %s\n", s);

      returnStatus = connect(simpleSocket, res->ai_addr, res->ai_addrlen);
      fprintf(stdout, "Type a message \n");

      memset(buffer, '\0', strlen(buffer));
      fgets(buffer, sizeof(buffer), stdin);
      returnStatus = write(simpleSocket, buffer, sizeof(buffer));

      memset(&buffer, '\0', sizeof(buffer));
      fprintf(stdout, "Waiting server..\n");     
      returnStatus = read(simpleSocket, buffer, sizeof(buffer));

      fprintf(stdout, "Message: %s\n", buffer);

      close(simpleSocket);
      return 0;
}

server.c

#include <stdio.h>      
#include <sys/types.h>
#include <sys/socket.h>   
#include <netdb.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

int main(int argc, char *argv[])
{
      int simpleSocket = 0, simplePort = 0, returnStatus = 0, check = 1, n; 
      char buffer[1024];

      struct addrinfo simpleServer, *res;

      if (2 != argc) {
          fprintf(stderr, "Usage: %s <port>\n", argv[0]);
          exit(1);
      }

      simplePort = atoi(argv[1]);

      memset(&simpleServer, 0, sizeof simpleServer);
      simpleServer.ai_family = AF_UNSPEC;  // use IPv4 or IPv6, whichever
      simpleServer.ai_socktype = SOCK_STREAM;
      simpleServer.ai_flags = AI_PASSIVE;     // fill in my IP for me

      getaddrinfo(NULL, argv[1], &simpleServer, &res);

      simpleSocket = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
      returnStatus =bind(simpleSocket, res->ai_addr, res->ai_addrlen);
      returnStatus = listen(simpleSocket, 5);

      struct addrinfo clientName = { 0 };
      int clientNameLength = sizeof(clientName);
      int simpleChildSocket = 0;

      while (1) {
         while (1) { 
            simpleChildSocket = accept(simpleSocket,(struct sockaddr *)&clientName, &clientNameLength);

            fprintf(stdout,"Waiting..\n");

            memset(&buffer, '\0', sizeof(buffer));        
            returnStatus = read(simpleChildSocket, buffer, sizeof(buffer));

            fprintf(stdout, "Message: %s\n", buffer);

            write(simpleChildSocket, buffer, sizeof(buffer));     
         }
      }
      close(simpleChildSocket);
      close(simpleSocket);
      return 0;
}

1
你为什么在一个无限循环中嵌套另一个无限循环? - Some programmer dude
4
关于你的问题,在服务器上使用getaddrinfo返回的第一个条目来创建套接字并绑定到接口。如果你想要同时使用IPv4和IPv6来接受连接,你需要两个套接字,将其中一个绑定到IPv4地址,另一个绑定到IPv6地址。 - Some programmer dude
2
哦,你需要在每次调用accept之前设置clientNameLength,因为它可能会修改参数。 - Some programmer dude
在某些系统上,绑定到::的套接字可以接受IPv4和IPv6连接。 - kasperd
你如何准确地启动服务器和客户端?你得到了什么输出?你从哪里推断出它失败了? - Ctx
2
@JoachimPileborg: "你需要两个套接字,一个绑定到IPv4地址,另一个绑定到IPv6地址" - 在支持双栈套接字的平台上,您可以在同一个监听套接字上接受IPv4和IPv6客户端。创建一个单独的IPv6(AF_INET6)套接字,并在调用bind()之前使用setsockopt()禁用其IPV6_V6ONLY选项。accept()报告的客户端IP地址将告诉您客户端是IPv4还是IPv6,因此请确保您的接收addr缓冲区足够大以容纳sockaddr_insockaddr_in6。最好使用sockaddr_storage作为缓冲区。 - Remy Lebeau
2个回答

4
正如@JoachimPileborg在评论中提到的那样,问题在于您的服务器代码没有为getaddrinfo()提供的每个地址打开侦听套接字。您在simpleServer结构中指定了AF_UNSPEC,因此getaddrinfo()将为您提供一个列表,其中包含IPv4IPv6地址。您只为该列表中的第一个条目创建了一个侦听套接字,这恰好是一个IPv4地址。这就是为什么您的IPv4客户端成功而IPv6客户端失败的原因。您需要遍历该列表,为每个条目创建一个单独的侦听套接字。
您还犯了其他错误,比如每次调用accept()时未重置clientNameLength变量、在应该使用sockaddr_storage的地方却使用addrinfo作为您的clientName缓冲区,以及未注意read()的返回值等。
尝试像这样做一些内容:
include <stdio.h>      
#include <sys/types.h>
#include <sys/socket.h>   
#include <netdb.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <poll.h>

#define MAX_SERVERS 10
#define MAX_CLIENTS 50
#define MAX_SOCKETS (MAX_SERVERS + MAX_CLIENTS)

int main(int argc, char *argv[])
{    
    int simpleSocket, simplePort, returnStatus, n, m; 
    char buffer[1024];
    pollfd simpleSockets[MAX_SOCKETS];
    int numSockets = 0, numServers = 0;

    struct addrinfo simpleServer, *res, *addr;

    if (2 != argc)
    {
        fprintf(stderr, "Usage: %s <port>\n", argv[0]);
        exit(1);
    }

    simplePort = atoi(argv[1]);

    memset(&simpleServer, 0, sizeof simpleServer);
    simpleServer.ai_family = AF_UNSPEC;  // use IPv4 and/or IPv6, whatever is available
    simpleServer.ai_socktype = SOCK_STREAM;
    simpleServer.ai_flags = AI_PASSIVE;     // fill in my IP for me

    if (0 != getaddrinfo(NULL, argv[1], &simpleServer, &res))
    {
        fprintf(stderr, "getaddrinfo failed\n");
        exit(1);
    }

    addr = res;
    while (NULL != addr)
    {
        simpleSocket = socket(res->ai_family, addr->ai_socktype, addr->ai_protocol);
        if (-1 == simpleSocket)
        {
            fprintf(stderr, "socket failed\n");
        }
        else
        {
            returnStatus = bind(simpleSocket, addr->ai_addr, addr->ai_addrlen);
            if (0 == returnStatus)
                returnStatus = listen(simpleSocket, 5);

            if (0 == returnStatus)
            {
                simpleSockets[numSockets].fd = simpleSocket;
                simpleSockets[numSockets].events = POLLIN;
                simpleSockets[numSockets].revents = 0;
                ++numSockets;
                ++numServers;
                if (MAX_SERVERS == numServers)
                    break;
            }
            else
            {
                fprintf(stderr, "bind/listen failed\n");
                close(simpleSocket);
            }
        }
        addr = addr->next;
    }

    freeaddrinfo(res);

    if (0 == numServers)
    {
        fprintf(stderr, "no servers are listening\n");
        exit(1);
    }

    struct sockaddr_storage clientName;
    int clientNameLength;

    while (1)
    { 
        returnStatus = poll(simpleSockets, numSockets, -1);
        if (-1 == returnStatus)
        {
            fprintf(stderr, "poll failed\n");
            exit(1);
        }

        if (0 == returnStatus)
            continue;

        for (n = 0; n < numSockets; ++n)
        {
            if (simpleSockets[n].revents & POLLIN)
            {
                if (n < numServers)
                {
                    clientNameLength = sizeof(clientName);

                    simpleSocket = accept(simpleSockets[n].fd, (struct sockaddr *)&clientName, &clientNameLength);
                    if (-1 == simpleSocket)
                    {
                        fprintf(stderr, "accept failed\n");
                        continue;
                    }

                    for (m = numServers; m < numSockets; ++m)
                    {
                        if (-1 == simpleSockets[m].fd)
                        {
                            simpleSockets[m].fd = simpleSocket;
                            simpleSockets[m].events = POLLIN;
                            simpleSockets[m].revents = 0;
                            simpleSocket = -1;
                            break;
                        }
                    }

                    if ((-1 != simpleSocket) && (MAX_SOCKETS > numSockets))
                    {
                        simpleSockets[numSockets].fd = simpleSocket;
                        simpleSockets[numSockets].events = POLLIN;
                        simpleSockets[numSockets].revents = 0;
                        ++numSockets;
                        simpleSocket = -1;
                    }

                    if (-1 != simpleSocket)
                    {
                        fprintf(stderr, "Too many clients connected\n");
                        close(simpleSocket);
                    }
                    else
                        fprintf(stdout, "Client connected\n");
                }
                else
                {
                    returnStatus = read(simpleSockets[n].fd, buffer, sizeof(buffer));
                    if (0 >= returnStatus)
                    {
                        if (0 > returnStatus)
                            fprintf(stdout, "Client error, disconnecting\n");
                        else
                            fprintf(stdout, "Client disconnected\n");

                        close(simpleSockets[n].fd);
                        simpleSockets[n].fd = -1;
                        simpleSockets[n].events = 0;
                        simpleSockets[n].revents = 0;

                        continue;
                    }

                    fprintf(stdout, "Message: %.*s\n", returnStatus, buffer);
                    write(simpleSockets[n].fd, buffer, returnStatus);     
                }
            }

            if (simpleSockets[n].revents & (POLLERR|POLLHUP|POLLNVAL))
            {
                if (simpleSockets[n].revents & POLLHUP)
                    fprintf(stdout, "Client disconnected\n");
                else if (n >= numServers)
                    fprintf(stdout, "Client error, disconnecting\n");
                else
                    fprintf(stdout, "Server error, closing\n");

                close(simpleSockets[n].fd);
                simpleSockets[n].fd = -1;
                simpleSockets[n].events = 0;
                simpleSockets[n].revents = 0;
            }
        }
    }

    for (n = 0; n < numSockets; ++n)
    {
        if (-1 != simpleSockets[n].fd)
            close(simpleSockets[n].fd);
    }

    return 0;
}

如果您使用支持双栈套接字的平台,则您的服务器不必创建任何IPv4监听套接字。它只能创建IPv6套接字,然后禁用它们的IPV6_V6ONLY选项。这将允许它们接受IPv4和IPv6客户端。由accept()返回的客户端地址将告诉您连接了IPv4还是IPv6客户端。

memset(&simpleServer, 0, sizeof simpleServer);
simpleServer.ai_family = AF_INET6;
simpleServer.ai_socktype = SOCK_STREAM;
simpleServer.ai_flags = AI_PASSIVE;

if (0 != getaddrinfo(NULL, argv[1], &simpleServer, &res))
{
    fprintf(stderr, "getaddrinfo failed\n");
    exit(1);
}

addr = res;
while (NULL != addr)
{
    simpleSocket = socket(res->ai_family, addr->ai_socktype, addr->ai_protocol);
    if (-1 == simpleSocket)
    {
        fprintf(stderr, "socket failed\n");
    }
    else
    {
        n = 0;
        returnStatus = setsockopt(simpleSocket, IPPROTO_IPV6, IPV6_V6ONLY, &n, sizeof(n));
        ...
    }
    addr = addr->next;
}

...

1

client.c代码有缺陷,它将完整的addrinfo存储在sockaddr_in结构中。 res是类型为addrinfo的(http://man7.org/linux/man-pages/man3/getaddrinfo.3.html

struct addrinfo {
   int              ai_flags;
   int              ai_family;
   int              ai_socktype;
   int              ai_protocol;
   socklen_t        ai_addrlen;
   struct sockaddr *ai_addr;
   char            *ai_canonname;
   struct addrinfo *ai_next;
}; 

所以,不是使用
struct sockaddr_in *addr_in = (struct sockaddr_in *)res; 而应该使用 struct sockaddr_in *addr_in = (struct sockaddr_in *)res->ai_addr;

像这样

case AF_INET: {
    struct sockaddr_in *addr_in = (struct sockaddr_in *)res->ai_addr;
    s = malloc(INET_ADDRSTRLEN);
    inet_ntop(AF_INET, &(addr_in->sin_addr), s, INET_ADDRSTRLEN);
    returnStatus = connect(simpleSocket, res->ai_addr, res->ai_addrlen);
    break;
  }
  case AF_INET6: {
    struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)res->ai_addr;
    s = malloc(INET6_ADDRSTRLEN);
    inet_ntop(AF_INET6, &(addr_in6->sin6_addr), s, INET6_ADDRSTRLEN);
    returnStatus = connect(simpleSocket, res->ai_addr, res->ai_addrlen);
    break;
  }

经过这次修改,代码按预期工作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接