【问题标题】:recvfrom not receiving depending on ai_family usedrecvfrom 不接收取决于使用的 ai_family
【发布时间】:2013-08-07 16:32:51
【问题描述】:

我正试图深入了解套接字编程,但遇到了一些(对我而言)意想不到的行为。

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

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

我尝试从另一台电脑连接,却得到相反的结果,IPv4 工作正常,IPv6 没有。我认为这可能是因为我使用了“6to4 网关”。我真的不知道这是什么意思。

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

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

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

提前致谢,如果这被认为是一个愚蠢的问题,我们深表歉意。

代码:

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);
}

【问题讨论】:

  • 我能想到几种可能发生这种情况的不同原因,从代码中的细微错误到您尝试它的计算机上配置错误的环回接口。我们将需要查看代码。另外,请尝试在不同的物理计算机上运行相同的程序。
  • 我使用了从指南复制的代码,结果与我自己的代码相同,所以我认为这不是问题。现在添加它。带有 localhost 的 AF_INET 确实在我刚刚测试过的另一台机器上工作(好主意)。不过,我更担心 AF_INET6 无法处理远程连接。
  • 如果相同的代码在另一台机器上运行,我们可能正在查看机器上的系统配置问题,而该机器上它不起作用。由于这是 Windows,我无法为您提供帮助。
  • 是的,看起来像。我显然也无法通过 IPv6 访问互联网,所以这可能是问题所在。尽管如此,仍然没有太多运气找到解决方法。

标签: c sockets localhost


【解决方案1】:

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 数据报。然后,您必须注意recvfrom() 返回的sockaddr 报告的地址族,以便了解任何给定数据报是使用IPv4 还是IPv6。

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

【讨论】:

  • 我不断地手动更改这些值以进行测试(确保客户端和服务器的值相同)。我上次运行代码时,它们恰好是 AF_UNSPEC。编辑:更改代码以修复混淆。
  • 我关于为addrinfo 列表中的每个条目创建单独的侦听套接字的评论仍然有效。此外,sin_port 采用网络字节顺序,因此在记录时使用ntohs()。你需要更好地处理你的sendto()recvto() 调用。除此之外,如果您仍然遇到问题,请使用 Wireshark 或其他数据包嗅探器来确保数据报在每一端都实际被传输和接收,并且正在使用预期的地址系列。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-04
  • 1970-01-01
  • 2015-05-23
  • 2012-09-06
  • 2011-05-12
  • 2012-03-16
相关资源
最近更新 更多