使用UDP套接字进行多次sendto()

4
我有一个网络软件,它使用UDP与同一程序的其他实例通信。由于不同的原因,我必须在这里使用UDP。
最近,我在通过UDP发送大量数据时遇到了问题,不得不实现分段系统将我的消息分成小数据块。到目前为止,它运行良好,但是当我必须发送大量数据块时,我现在遇到了问题。
我有以下算法:
1. 将消息分成小数据块(约1500字节) 2. 遍历数据块列表,并为每个块使用sendto()发送
然而,当我发送大量数据块时,接收方仅收到前6条消息。有时会错过第六条并接收第七条。这取决于情况。
无论如何,sendto()总是表示成功。当我在回环接口(127.0.0.1)上测试我的软件时,这总是发生,但从未在我的局域网上发生。
如果我在sendto()之间添加类似于std::cout << "test" << std::endl;的内容,则每个帧都会被接收。
我知道UDP允许数据包丢失,我的帧可能因为很多原因而丢失,我想这与我以某种速率发送数据块有关。
这里应该采取什么正确的方法?
1. 实现一些确认机制(就像TCP)似乎过于繁琐。 2. 在sendto()之间添加任意等待时间很丑陋,可能会降低性能。 3. 增加(如果可能)接收器UDP内部缓冲区?我甚至不知道这是否可能。 4. 其他方法?
我真的需要你的建议。
非常感谢。
附加信息如请求所示
必须使用UDP的原因是因为我有几个限制:
1. TCP在NAT穿透方面工作不好(至少没有特定的配置) 2. 有些消息可能丢失。其他一些则不能。 3. 消息传递顺序无关紧要。

2
TCP并不是为了成为重量级协议而设计的。它的设计初衷是满足一个明确的需求:可靠、有序地传输数据包。如果你想要相同的保证,那么就没有捷径,也没有可以代替TCP的轻量级协议。无论你做什么,本质上都是TCP,只会更加容易出现错误。 - Marcelo Cantos
@Marcelo 是的,我完全同意。这正是我想要避免重新发明轮子的原因。 - ereOn
我不明白TCP在NAT穿透方面表现不佳的观点。UDP更难在NAT上运行。 - Marcelo Cantos
@Marcelo Cantos:我的程序使用点对点网络架构。主机经常需要使用“UDP打洞”来建立成功的通信。使用TCP这样做几乎是不可能的。 - ereOn
我现在明白了,你正在尝试实现STUN。这比可靠的UDP更难解决。请从这里开始查看相关信息和一些可以使用的库:http://en.wikipedia.org/wiki/Session_Traversal_Utilities_for_NAT。 - Marcelo Cantos
显示剩余2条评论
7个回答

2
如果你在仅发送6或7个数据包后就在环回接口上丢失了数据包,那么可能是因为你的接收缓冲区太小了。你可以使用setsockopt并设置SO_RCVBUF选项来增加它的大小。然而,如果你正在发送1500字节的数据,那么如果这确实是问题所在,那么接收缓冲区只有大约9K(或更可能是8K),但是这似乎是一个相当小的默认值。我相信在Windows上默认的接收缓冲区大小为16K。
即使假设增加接收缓冲区有所帮助,你仍然需要解决其他人提到的问题。还有一些其他的事情需要考虑,例如尝试动态确定最大数据包大小以避免分段。此外,手动将数据包大小和发送到确认之间的数量设置为可配置也是有意义的。

+1 因为你是唯一谈到 SO_RCVBUF 选项的人。 - ereOn

2
打电话时将UDP称为不可靠是一种简化,试图将TCP定义为解决所有网络问题的万灵药。同样地,将TCP定义为可靠也是错误的。虽然TCP确实有机制尝试确保数据传输,但引起UDP数据包丢失的许多故障也会导致TCP失败。
例如,硬件网络故障对UDP和TCP数据包具有相同的影响。如果故障持续存在,则TCP像UDP一样无法传输。事实上,在这种情况下,TCP的劣势在于它会尝试更长时间地完成一个无望的任务。现在,如果您要通过互联网发送数据,则TCP具有一些优势,因为发送数据包的路线不能预先定义。但是对于在局域网中发送数据,UDP完全可以胜任。如果数据包未到达目的地,则表示需要纠正硬件故障。这种情况下,TCP无济于事。
此外,在选择协议时,您还必须了解您的数据。如果您的数据是瞬态的,例如来自传感器的读数,则使用UDP比使用TCP更有意义。在此情况下,如果数据包丢失,则影响不大,因为很快就会有另一个数据包出现。另一方面,TCP将会回退并重试。等数据到达时,它已经过时了。
事实是,TCP是为流数据设计的。在这种情况下,重要的是所有数据包都可靠地按顺序到达。UDP则适用于分组数据,对于这种类型的数据,UDP是完全可以接受的,因为它可靠性高、开销低且能更快地检测和从网络故障中恢复过来。

1

实现确认机制听起来就像是你需要做的正是。这样你就可以确保一次最多只有N个数据包处于“在途”状态,并且你可以重新传输那些未被确认太久的数据包。


谢谢你的回答。你有关于我应该使用什么算法的建议吗?我猜为每个接收到的块发送一个确认信息是次优的。 - ereOn
正如其他人所说,这真的取决于您需求的具体情况。每收到一个数据包发送一个确认很可能是可以的(确认信息很小),至少作为第一步尝试。 - caf
在发送下一个数据包之前,每个数据包都需要1个确认可能非常低效。假设RTT为25ms。那么你只能每25ms发送1条消息,如果你的数据包最大大小为1500,则速度只有60kb / sec - 如果你需要速度,那就不太快了。 - nos
我最终实现了一种按数据包确认的机制。这个机制工作得很好,而且没有任何速度损失。(我不需要保证传递顺序,所以我可以异步发送消息/确认,从而避免了@nos建议的问题)。 - ereOn

1

你应该实现确认和重传。例如,对于每N个数据包,需要ack,并在重传缓冲区中保留N个数据包。

(也许你可以从rudp获取一些想法,或者在UDP上实现Il

UDP是不可靠的,也没有提供流量控制。简而言之,你会时不时地丢失数据包 - 特别是如果你发送数据很快 - 内核或任何路由器/交换机在其中会丢弃数据包,如果没有足够的空间 - 你几乎无法使用任何魔法来避免这种情况发生。


0
无论如何,sendto() 函数总是返回成功。当我在回环接口(127.0.0.1)上测试我的软件时,这种情况总是发生,但在我的局域网上从未发生过。
如果我在 sendto() 函数之间添加类似于 std::cout << "test" << std::endl; 的内容,则每个帧都会被接收到。
这听起来像是您的接收缓冲区太小了。
一些建议:
  1. 增加接收缓冲区大小。使用 setsockopt SO_RCVBUF。
  2. 让应用程序决定是否重新传输丢失的数据包。
  3. 确保有效负载适合 MTU,也就是说有效负载 + 头部 <= 1500,以避免 IP 分段。

0

TCP的存在就是为了解决这种问题。为什么不选择TCP呢?你将不得不解决所有相同的问题,并最终得出相同的解决方案,只是没有TCP堆栈数十年的研究、开发和调试的好处。

如果你真的必须使用UDP,那么第一个问题是,你愿意放弃TCP保证的哪些方面?你是否愿意接收无序的数据包?丢失一定比例的数据包是否可以接受?你能处理重复数据包的到来吗?这些问题的答案有望引导你进行设计。

如果不知道你的具体情况,很难用简单的“这样做就没问题”的回答来回答你的问题,除了当然是“使用TCP,你就没问题了”。


谢谢您的回答。我在问题末尾添加了更多信息。 - ereOn

0

UDP传输数据报,不可靠。

TCP传输数据流,可靠。

你想要的似乎是基于数据报的,但是可靠的。因此,你需要在UDP或TCP上构建一些东西来实现这一点。我相信有完整的协议规范构建在UDP之上,提供了这个功能。你只需要找到并实现它们。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接