【问题标题】:(Winsock) UDP receive works but send fails for same socket(Winsock) UDP 接收有效,但同一套接字的发送失败
【发布时间】:2020-02-09 17:41:54
【问题描述】:

我在使用 Windows 中的 UDP 套接字时遇到问题。我有一个单独的应用程序,我正在尝试与端口 1625 上的输出通信并在端口 26027 上接收。我尝试制作一个简单的可执行文件来读取一条消息并发送一条消息。读取工作正常,但发送结束时出现 WSAEADDRNOTAVAIL (10049) 错误。

为了排除故障,我还在同一台机器上尝试了 Linux 中的等效代码(使用适用于 Linux 的 Windows 子系统),它运行良好。所以我无法弄清楚问题是什么。我也尝试禁用 Windows 防火墙,但这并没有什么不同。任何建议将不胜感激。

Windows Visual C++ 代码:

#pragma comment(lib, "Ws2_32.lib")

#include <iostream>
#include <WS2tcpip.h>

#define MAXLINE 1024

int main()
{
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);

    // Define local port address.
    sockaddr_in local_port;
    memset(&local_port, 0, sizeof(local_port));
    local_port.sin_family = AF_INET;
    local_port.sin_addr.s_addr = INADDR_ANY;
    local_port.sin_port = htons(1625);

    // Bind local socket.
    int socket_id = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    bind(socket_id, (const struct sockaddr *)&local_port, sizeof(local_port));

    // Receive UDP Port message.
    char in_buffer[MAXLINE];
    int num_bytes = recv(socket_id, (char *)in_buffer, MAXLINE, 0);
    in_buffer[num_bytes] = '\0';
    printf("Received : %s\n", in_buffer);

    // Set up send destination port.
    sockaddr_in dest_port;
    memset(&dest_port, 0, sizeof(dest_port));
    dest_port.sin_family = AF_INET;
    dest_port.sin_addr.s_addr = INADDR_ANY;
    dest_port.sin_port = htons(26027);

    // Send UDP message to specific UDP port.
    char out_buffer[] = "Test message";
    int result = sendto(
        socket_id, out_buffer, strlen(out_buffer), 0, (struct sockaddr *)&dest_port, sizeof(dest_port));
    printf("Send result : %d -- WSA Error : %d\n", result, WSAGetLastError());

    closesocket(socket_id);
    return 0;
}

运行此可执行文件的终端输出是:

Received : 5e4009df*755=-0.0028:761=0.6942

Send result : -1 -- WSA Error : 10049

WSL linux C++ 代码(相同的源代码,除了 WSA 包含和错误输出):

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#define MAXLINE 1024

int main()
{
    // Define local port address.
    sockaddr_in local_port;
    memset(&local_port, 0, sizeof(local_port));
    local_port.sin_family = AF_INET;
    local_port.sin_addr.s_addr = INADDR_ANY;
    local_port.sin_port = htons(1625);

    // Bind local socket.
    int socket_id = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    bind(socket_id, (const struct sockaddr *)&local_port, sizeof(local_port));

    // Receive UDP Port message.
    char in_buffer[MAXLINE];
    int num_bytes = recv(socket_id, (char *)in_buffer, MAXLINE, 0);
    in_buffer[num_bytes] = '\0';
    printf("Received : %s\n", in_buffer);

    // Set up send destination port.
    sockaddr_in dest_port;
    memset(&dest_port, 0, sizeof(dest_port));
    dest_port.sin_family = AF_INET;
    dest_port.sin_addr.s_addr = INADDR_ANY;
    dest_port.sin_port = htons(26027);

    // Send UDP message to specific UDP port.
    char out_buffer[] = "Test message";
    int result = sendto(
        socket_id, out_buffer, strlen(out_buffer), 0, (struct sockaddr *) &dest_port, sizeof(dest_port));
    printf("Send result : %d\n", result);

    close(socket_id);
    return 0;
}

运行此可执行文件的终端输出是:

Received : 5e4009df*755=-0.0028:761=0.6942

Send result : 12

我还可以验证通过此 Linux 实现到端口 26027 的输出是否已被其他应用程序接收,并且还可以在 Wireshark 中看到它。

编辑:
在下面 Remy 的回答之后,我能够按照下面的 cmets 来完成这项工作。澄清我的网络:

如果我使用 Wireshark 查看我的网络现在看起来像:

127.0.0.1   UDP 50223 → 1625 Len=32  
127.0.0.1   UDP 1625 → 26027 Len=12  

我的节点绑定到 1625,它可以从某个未知端口号(本例中为 50223)recv()UDP 和 sendto() 端口 26027 绑定。

【问题讨论】:

    标签: c++ udp winsock


    【解决方案1】:

    您不能将 recv() 与 UDP 套接字一起使用,除非您首先调用 connect() 将对等方的 IP/端口静态分配给套接字,而您没有这样做。所以recv() 会失败,但你没有检查。您需要改用recvfrom()

    另外,无论如何,您都不能照原样将数据包发送到INADDR_ANY (0.0.0.0)。这就是您收到发送错误的原因。

    sendto Function

    WSAEADDRNOTAVAIL

    远程地址不是有效地址,例如,ADDR_ANY

    Windows Sockets Error Codes

    WSAEADDRNOTAVAIL
    10049

    无法分配请求的地址。 请求的地址在其上下文中无效。这通常是由于尝试绑定到对本地计算机无效的地址所致。 当远程地址或端口对远程计算机无效(例如,地址或端口 0)时,connect、sendto、WSAConnect、WSAJoinLeaf 或 WSASendTo 也可能导致此问题

    您需要发送到一个实际的IP/端口,例如发送到recvfrom()收到数据包时报告的对等方的IP/端口。

    【讨论】:

    • 谢谢!我的问题是没有在发送中指定 IP 地址。这是在 localhost (127.0.0.1) 上完成的,所以也许 linux 实现默认是这样,但 winsock 没有,这就是为什么只有一个版本有效的原因。我还发现beej.us/guide/bgnet/html 上的指南非常有助于区分绑定/连接和现代化我的设置以使用getaddrinfo()
    • 也许我的措辞中存在脱节,但connect() 对我来说不适用于recv(),但bind() 工作正常——收到的字符串我在我的结果中列出问题是预期的。如果我用 Wireshark 查看它,我的网络现在看起来像: 127.0.0.1 UDP 50223 → 1625 Len=32 127.0.0.1 UDP 1625 → 26027 Len=12 我的节点绑定到 1625,它可以从某个未知端口号recv() UDP (本例中为 50223)和sendto() 端口 26027。
    • 如果你使用recv而不调用connect,你将不知道你收到的数据报来自哪里。通常,您需要知道这一点才能发送回复。
    • 我明白了,这是有道理的。我想我的用例不那么传统,我只对每个端口的单向通信感兴趣。我正在尝试与运行 lua 脚本并已设置为发布到特定端口的飞行模拟游戏交互,它会侦听另一个端口上的输入。所以我将我的套接字绑定到数据发布到的位置,这样我就可以收听了。为了获得更安全的连接,我似乎也可以使用 bind->listen->accept 序列。
    • @cjtytler connect() 需要使用recv()send(),但不能使用recvfrom()sendto()。您应该使用recvfrom(),这样您就可以发现哪个对等方正在向您发送数据包。另外,accept() 只能用于 TCP,不能用于 UDP。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-03-18
    • 1970-01-01
    • 2016-01-07
    • 2013-11-19
    • 2015-02-21
    • 2015-03-14
    • 2014-08-17
    相关资源
    最近更新 更多