recvfrom不接收取决于使用的ai_family

时间:2013-08-07 16:32:52

标签: c sockets localhost

我正试图了解套接字编程并遇到了一些意想不到的(对我来说)行为。

当我尝试将数据发送到“localhost”并将addrinfo.ai_family设置为AF_INET时,我发送的消息不会从我的客户端进程传递到我的主机进程(recvfrom()不会返回)。如果我将它设置为AF_INET6一切都很好。对于AF_UNSPEC也是如此,在这种情况下,它会选择IPv6 addrinfo(列表中的第一个)。主机和客户端当然都使用相同的ai_family。

我也尝试使用从beej's guide to network programming粘贴的代码副本,但结果相同。我正在使用DGRAM套接字。

我尝试从不同的PC连接我得到相反的结果,IPv4工作正常,IPv6没有。我想这可能是因为我使用的是'6to4网关'。我真的不知道这意味着什么。

问题与我自己的机器有关,因为代码在我测试的另一台机器上通过IPv4工作。 我不能说这是发送还是接收问题。

什么阻止我使用AF_INET套接字向localhost发送数据或从localhost接收数据?

我在用MingW编译的windows7 64bit机器上。 如果它有任何区别我正在为具有不同参数的主机和客户端进程运行相同的程序。我一起运行发布和调试程序(因此它不是同一个程序两次)但得到了相同的结果。

如果这被认为是一个愚蠢的问题,请提前致谢并道歉。

代码:

typedef struct addrinfo addrinfo_t;
typedef struct sockaddr_storage sockaddr_storage_t;
typedef struct sockaddr_in sockaddr_in_t;
typedef struct sockaddr_in6 sockaddr_in6_t;

void connect_to_server(const char* server_name, const char* message)
{
    int status;
    init_networking();
    addrinfo_t hints;
    addrinfo_t* res;
    memset(&hints, 0, sizeof(addrinfo_t));
    hints.ai_family = AF_INET; //or AF_INET6
    hints.ai_socktype = SOCK_DGRAM;
    if ((status = getaddrinfo(server_name, "4950", &hints, &res)) != 0)
    {
        fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(status));
        exit(1);
    }
    SOCKET s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
    if (s == -1)
    {
        fprintf(stderr, "could not create a socket, errno: %u\n", errno);
        exit(1);
    }

    int bytes_sent = sendto(s, message, strlen(message), 0, res->ai_addr, res->ai_addrlen);
    close(s);
    printf("Sent %i bytes to port %i\n", bytes_sent, ((sockaddr_in_t*)res->ai_addr)->sin_port);
    freeaddrinfo(res);
}

void setup_server()
{
    int status;
    init_networking();
    addrinfo_t hints;
    addrinfo_t* res;
    memset(&hints, 0, sizeof(addrinfo_t));
    hints.ai_family = AF_INET; //or AF_INET6
    hints.ai_socktype = SOCK_DGRAM;
    hints.ai_flags = AI_PASSIVE;

    if ((status = getaddrinfo(NULL, "4950", &hints, &res)) != 0)
    {
        fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(status));
        exit(1);
    }

    SOCKET s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
    if (s == -1)
    {
        fprintf(stderr, "could not create a socket, errno: %u\n", errno);
        exit(1);
    }
    //Bind the socket to own address (mostly the port number contained in the address)
    if (bind(s, res->ai_addr, res->ai_addrlen) < 0)
    {
        fprintf(stderr, "failed to bind, errno: %u\n", errno);
        exit(1);
    }
    freeaddrinfo(res);

    const size_t read_buffer_size = 1024;
    void* read_buffer = malloc(read_buffer_size);

    sockaddr_storage_t peer_address;
    int peer_address_length = sizeof(sockaddr_storage_t);

    sockaddr_storage_t own_sock_addr;
    int own_sock_addr_len = sizeof(sockaddr_storage_t);
    getsockname(s, (struct sockaddr*)&own_sock_addr, &own_sock_addr_len);
    printf("Listening on port %i\n", ((sockaddr_in_t*)&own_sock_addr)->sin_port);

    int bytes_received = recvfrom(s,
                                read_buffer,
                                read_buffer_size-1,
                                0,
                                (struct sockaddr*)&peer_address,
                                &peer_address_length                    );

    printf("Received %i byte message:\n%s\n", bytes_received, (char*)read_buffer);
}

1 个答案:

答案 0 :(得分:2)

AF_INET适用于IPv4,AF_INET6适用于IPv6。发送IPv4数据报时,接收方必须使用IPv4套接字或IPv6 dual stack socket(同时接受IPv4和IPv6流量的IPv6套接字)在目标IP /端口上接收数据。发送IPv6数据报时,接收方必须使用IPv6套接字接收数据。否则,数据报将被忽略,因此听起来一台机器正在使用忽略IPv4数据报的IPv6套接字,而另一台机器正在使用忽略IPv6数据报的IPv4套接字。

当您呼叫getaddrinfo()时,您将AF_UNSPEC指定为客户端和服务器中的地址系列。 AF_UNSPEC告诉getaddrinfo()您需要IPv4和IPv6地址,因此它会返回链接列表,其中可能包含所有可用的多个条目IPv4 IPv6地址。在服务器端,您只为列表中的第一个条目(可能是IPv4或IPv6)创建单个侦听套接字。在客户端,您只为列表中的第一个条目创建单个发送套接字,该条目可以是IPv4或IPv6。因此,在两个操作中使用的实际地址族将是随机的,有时可能彼此不匹配。

在服务器端,您需要:

  1. 直接使用AF_INETAF_INET6,而不是AF_UNSPEC,然后相应地对客户进行编码以匹配。

  2. 遍历整个addrinfo列表,为每个条目创建一个单独的侦听套接字。这样,客户端就可以将数据发送到服务器正在侦听的任何IP /端口系列。

  3. 仅在创建侦听套接字时使用AF_INET6,但在它们上启用dual stack functionality(仅限Vista +),以便它们可以同时接收IPv4和IPv6数据报。然后,您必须注意sockaddr recvfrom()报告的地址系列,以便知道任何给定的数据报是使用IPv4还是IPv6。

  4. 在客户端,您需要直接使用AF_INETAF_INET6,而不是AF_UNSPEC,具体取决于服务器实际监听的内容。将AF_UNSPEC用于UDP客户端套接字(对TCP客户端套接字有意义)是没有意义的,除非您正在实现的UDP协议使用ack回复每个数据报。没有它,客户端无法知道服务器是否接受IPv4或IPv6数据报(除非用户告诉应用程序)。使用acks,客户端可以遍历返回的addrinfo列表,将数据报发送到列表中的条目,等待几秒钟获取确认,如果没有收到,则转到列表中的下一个条目,根据需要重复,直到ack实际到达或列表用完为止。