到特定远程 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 消息丢失的主要内容,如果未能解决你的问题,请参考以下文章