9

我正在开发一个基于 LAN 的解决方案,它的“服务器”必须控制多个“玩家” 我选择的协议是 UDP,因为它很简单,我不需要连接,我的流量不时只包含短命令我想混合使用广播消息进行同步,并使用单个目标消息进行播放器个人命令。

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

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

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

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

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

我知道 UDP 从定义上讲是“不可靠的”,但我不敢相信这会发展到如此地步,即使物理连接正常并且一切都定义明确的数据包可能会丢失。那么它实际上将一文不值。

从技术上讲,我正在打开一个 UDP 套接字,将它绑定到一个端口和 INADRR_ANY。然后我使用“sendto”和“recvfrom”。我从不进行连接——我不想这样做,因为我有几个玩家。据我所知,UDP应该在没有连接的情况下工作。

我目前的解决方法是我定期向所有特定的玩家 ip 发送虚拟数据报 - 这解决了问题,但它在某种程度上“不满意”

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

编辑:

我将其归结为以下测试程序:

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;

该程序打开一个 UDP 套接字,并在每次击键时连续发送 3 个数据报到特定 IP。通过wireshark 运行该命令,观察您的UDP 流量,按一个键,稍等片刻,然后再次按一个键。您不需要远程 IP 上的接收器,没有区别,除了您不会收到黑色标记的“不可访问”数据包。这就是你得到的:

Wireshark 快照

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

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

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

4

4 回答 4

16

我在其他人回答很久之后才发布这个,但它是直接相关的。

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

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

这记录在这里

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

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

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

于 2012-09-26T09:01:41.257 回答
4

UDP 数据包应该在接收时被缓冲,但是一个 UDP 数据包(或持有它的以太网帧)可以在给定机器上的几个点被丢弃:

  1. 网卡没有足够的空间来接受它,
  2. 操作系统网络堆栈没有足够的缓冲内存将其复制到,
  3. 防火墙/数据包过滤丢弃规则匹配,
  4. 没有应用程序正在侦听目标 IP 和端口,
  5. 侦听应用程序套接字的接收缓冲区已满。

前两点是关于过多的流量,这里不太可能出现这种情况。然后我相信第 4 点不适用,您的软件正在等待数据。第 5 点是关于您的应用程序处理网络数据的速度不够快 - 似乎也并非如此。

MAC 和 IP 地址之间的转换是通过地址解析协议完成的。如果您的网络配置正确,这不会导致数据包丢失。

我会禁用 Windows 防火墙和任何防病毒/深度数据包检查软件,并使用wireshark检查线路上的内容。这很可能会为您指明正确的方向 - 如果您可以在“发送到”机器上嗅探那些第一个数据包,然后检查本地配置(防火墙等);如果不这样做,请检查您的网络 - 路径中的某些东西干扰了您的流量。

希望这可以帮助。

于 2012-08-05T00:12:36.463 回答
1

erm ..... 它是你的计算机在做 ARP 请求。当您第一次开始发送时,您的 com 不知道接收方的 mac 地址,因此无法发送任何数据包。它使用接收方的 ip 地址进行 ARP 请求以获取 MAC 地址。在此过程中,您尝试发送的任何 udp 数据包都无法发送出去,因为目标 MAC 地址仍然未知。

一旦您的 com 收到 mac 地址,它就可以开始发送。但是,mac 地址只会在您的 com 的 ARP 缓存中保留 2 分钟(如果在您和接收者之间没有检测到进一步的活动)或 10 分钟(完全清除 ARP 缓存,即使连接处于活动状态)。这就是为什么你每隔几分钟就会遇到这个问题。

于 2012-08-24T03:48:26.260 回答
-3

有人知道这个问题吗?

真正的问题是您假设 UDP 数据包发送是可靠的。它不是。

您丢失当前网络配置的第一个数据包这一事实实际上是次要问题。你也许可以解决这个问题,但在任何时候你仍然容易受到数据包丢失的影响。

如果丢包对你来说是个问题,那么你真的应该使用 TCP。您可以在 UDP 上构建可靠的协议,但除非您有充分的理由这样做,否则不建议这样做。

于 2012-08-06T19:34:13.370 回答