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

Posted

技术标签:

【中文标题】到特定远程 IP 的第一条 UDP 消息丢失【英文标题】:First UDP message to a specific remote ip gets lost 【发布时间】:2012-08-02 12:08:56 【问题描述】:

我正在开发一个基于 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 上的接收器,没有区别,除了您不会收到黑色标记的“不可访问”数据包。 这就是你得到的:

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

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

嗯,这让我很吃惊,也让我有点难过,因为这意味着我必须接受我目前的解决方法,或者实现一个一次只发送一条消息然后等待回复的 ACK 系统 - 这不会再简单,也意味着许多困难。

【问题讨论】:

您说,即使在工作网络链接的情况下,UDP 也会丢弃数据包,这是毫无价值的。我不同意;如果你在你的协议中实现 ACK,你可以让它相当可靠,但如果你想要任何一种可靠性,你确实需要这样做。 “多播 TCP 将是一个替代方案”。不,它不会,没有这样的东西,所以难怪它没有“得到硬件的良好支持”。你的意思是多播UDP? @icktoofay:我使用 UDP 是因为它很简单。引入 ACK 会大大增加复杂性。我必须解决诸如“重新发送命令之前要等待多长时间”之类的问题,最后玩家也遇到了同样的问题,那么如果发送了命令但没有 ACK 怎么办 - 服务器会重复命令,这会导致问题。 @OleDittmann:是的,添加 ACK 确实会增加复杂性。但是,如果您想要可靠性,您需要在其之上分层您自己的机制(如 ACK)来提供它。期望 UDP,一个 不可靠 协议,这样做是不合理的。正如您所说,UDP 并非“毫无价值”;它只是需要更多的工作,但它确实需要这项工作。 @OleDittmann:如果您不想在 UDP 之上分层自己的可靠性机制,但需要可靠性,则不能使用 UDP。对不起。 【参考方案1】:

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

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

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

这是记录在here:

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

在Mac OS X 和FreeBSD 上可以观察到相同的行为:

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

【讨论】:

谢谢,这解释了我的发现。 这应该是公认的答案,因为它准确地描述了这里发生的事情。 它是按套接字还是按目标地址排队?【参考方案2】:

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

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

【讨论】:

这基本上是我自己发现的。问题是,为什么会这样?网络层中是否真的没有缓冲区来存储消息,直到它们至少可以在短时间内发送。或者为什么“发送”不只是简单地阻塞等待 arp 请求的短暂超时。【参考方案3】:

有人知道这个问题吗?

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

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

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

【讨论】:

好吧,我假设它至少在一个受控的明确定义且不改变环境的 LAN 中是可靠的。 @Ole Dittmann:TCP 出了什么问题?您有严格的实时要求吗? 没什么,只是更努力地编程并且不太适合给定的任务(面向消息,广播)。是的,我有严格的实时要求。 花时间在 *** 上找出这种 UDP 情况的问题比仅仅用 TCP 编写东西更省力吗? 后来你总是更聪明【参考方案4】:

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

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

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

MAC 和 IP 地址之间的转换是通过 Address Resolution Protocol 完成的。如果您的网络配置正确,这不会导致丢包。

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

希望这会有所帮助。

【讨论】:

是的,我可能会在星期一再做一次。但几年前我已经做过一些类似的研究。这个问题困扰了我很长一段时间,甚至在不同的项目中,有些是用 C++ 编写的,有些是用 C# 编写的。我尝试切换防火墙和防病毒软件。我尝试了不同的硬件开关。而且我还尝试用wireshark进行检查。不记得到底是什么wireshark说的,但我没有解决办法。将再试一次并进行新的分析。 “MAC 和 IP 地址之间的转换是通过地址解析协议完成的。如果您的网络配置正确,这不会导致数据包丢失。” - 似乎它会导致下降(请参阅我在主帖中的编辑)。那么,我该如何配置呢? 从您发布的 wireshark 图片来看,192.168.1.12 上的目标端口上似乎没有任何 UDP 数据包侦听 - 这就是 ICMP 告诉您的。 是的,没错。远程计算机上没有接收器。但这没关系。有的话也没关系。 嗅探接收器!你怎么知道它是在 ARP 完成后发出的第三个数据包,而不是第一个?数据包确实在 NIC 输入上缓冲。

以上是关于到特定远程 IP 的第一条 UDP 消息丢失的主要内容,如果未能解决你的问题,请参考以下文章

UDP丢包修复

RabbitMQ消息丢失问题和保证消息可靠性-消费端不丢消息和HA

Linux UDP

rabbitmq保证消息不丢失?

kafka什么时候会丢消息(转)

关于MQ的几件小事如何保证消息不丢失