到特定远程ip的第一条UDP消息会丢失

Ole*_*ann 8 c sockets udp network-programming windows-7

我正在开发基于局域网的解决方案,其中"服务器"必须控制一些"播放器"我选择的协议是UDP,因为它很容易,我不需要连接,我的流量只包含不时的短命令我想使用混合广播消息进行同步,单个目标消息用于玩家个人命令.

多播TCP将是一种替代方案,但它更复杂,不完全适合任务,并且通常不受硬件的良好支持.

不幸的是我遇到了一个奇怪的问题:

使用"sendto"发送到特定ip的第一个数据报丢失. 接收到之后短时间发送到同一ip的任何数据报.但如果我等待一段时间(几分钟),第一次"发送"会再次丢失.

广播数据报始终有效.本地发送(到同一台计算机)始终有效.

我假设操作系统或路由器/交换机有一些从IP到MAC地址的转换表,这些转换表在不使用几分钟时会被遗忘,并且遗憾的是导致数据报丢失.我可以用不同的路由器/交换机硬件观察到这种行为,所以我的怀疑是windows网络层.

我知道UDP根据定义"不可靠",但我无法相信这一点到目前为止,即使物理连接正常工作且一切都很好,数据包也会丢失.然后它将毫无价值.

从技术上讲,我打开UDP套接字,将其绑定到端口和INADRR_ANY.然后我使用"sendto"和"recvfrom".我从不做连接 - 我不想,因为我有几个球员.据我所知,UDP应该在没有连接的情况下工作.

我目前的解决方法是我经常向所有特定的玩家ips发送虚拟数据报 - 这解决了问题,但它以某种方式"不满意"

问题:有人知道这个问题吗?它从何而来?我该如何解决?

编辑:

我把它归结为以下测试程序:

int _tmain(int argc, _TCHAR* argv[])
{
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);
    SOCKET Sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    SOCKADDR_IN Local = {0};
    Local.sin_family = AF_INET;
    Local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
    Local.sin_port = htons(1234);
    bind(Sock, (SOCKADDR*)&Local, sizeof(Local));
    printf("Press any key to send...\n");
    int Ret, i = 0;
    char Buf[4096];

    SOCKADDR_IN Remote = {0};
    Remote.sin_family = AF_INET;
    Remote.sin_addr.S_un.S_addr = inet_addr("192.168.1.12");  // Replace this with a valid LAN IP which is not the hosts one
    Remote.sin_port = htons(1235);

    while(true) {
        _getch();
        sprintf(Buf, "ping %d", ++i);
        printf("Multiple sending \"%s\"\n", Buf);

        // Ret = connect(Sock, (SOCKADDR*)&Remote, sizeof(Remote));
        // if (Ret == SOCKET_ERROR) printf("Connect Error!\n", Buf);
        Ret = sendto(Sock, Buf, strlen(Buf), 0, (SOCKADDR*)&Remote, sizeof(Remote));
        if (Ret != strlen(Buf)) printf("Send Error!\n", Buf);
        Ret = sendto(Sock, Buf, strlen(Buf), 0, (SOCKADDR*)&Remote, sizeof(Remote));
        if (Ret != strlen(Buf)) printf("Send Error!\n", Buf);
        Ret = sendto(Sock, Buf, strlen(Buf), 0, (SOCKADDR*)&Remote, sizeof(Remote));
        if (Ret != strlen(Buf)) printf("Send Error!\n", Buf);
        }
    return 0;
Run Code Online (Sandbox Code Playgroud)

该程序打开一个UDP套接字,并在每次击键时连续发送3个数据报到特定的IP.运行那个连线器观察你的UDP流量,按一个键,等一会再按一次键.您不需要远程IP上的接收器,没有区别,除非您不会得到黑色标记为"不可达"的数据包.这就是你得到的:

Wireshark快照

正如您所看到的,第一次发送启动了对IP的ARP搜索.虽然该搜索正在等待,但连续3次发送中的前2个丢失了.第二次击键(IP搜索完成后)正确发送了3条消息.您现在可以重复发送消息,它将一直有效,直到您等待(大约一分钟,直到地址转换再次丢失)然后您将再次看到丢失.

这意味着:发送UDP消息时没有发送缓冲区,并且有ARP请求待处理!除最后一条消息外,所有消息都会丢失."sendto"在成功发送之前不会阻塞,并且没有错误返回!

好吧,这让我感到惊讶并让我有点难过,因为这意味着我必须忍受我当前的解决方法或实现一个只能一次发送一条消息然后等待回复的ACK系统 - 这不容易更多并且意味着许多困难.

nos*_*nos 16

在其他人回答之后我就发布了这么久,但它直接相关.

如果目标地址(或目标网关)没有ARP条目,Winsock会丢弃UDP数据包.

因此,很可能一些第一个UDP数据包被丢弃,因为当时没有ARP条目 - 与大多数其他操作系统不同,winsock仅在ARP请求完成时排队1个数据包.

在此处记录:

当该IP地址被解析为MAC地址时,ARP仅为指定的目标地址排队一个出站IP数据报.如果基于UDP的应用程序将多个IP数据报发送到单个目标地址而它们之间没有任何暂停,则如果不存在任何ARP缓存条目,则可能会丢弃某些数据报.应用程序可以通过在发送数据包流之前调用Iphlpapi.dll例程SendArp()来建立ARP缓存条目来弥补这一点.

Mac OS XFreeBSD上可以观察到相同的行为:

当接口请求不在高速缓存中的地址的映射时,ARP将需要映射的消息排队,并在关联的相关网络上广播请求地址映射的消息.如果提供了响应,则缓存新映射并传输任何未决消息.在等待对映射请求的响应时,ARP将最多排队一个数据包; 只保留最近"传输"的数据包.