为什么 Socket.BeginReceive 会丢失 UDP 数据包?
以下代码等待通过 UDP 的数据。我有一个测试函数,可以发送 1000 个数据包(数据报?),每个数据包 500 字节。每次我运行测试函数时,接收器仅收到前几十个数据包,但丢弃其余的数据包。我使用 Wireshark 查看了传入的网络数据,发现所有 1000 个数据包实际上都已收到,但只是没有进入可能的应用程序代码。
以下是一些相关的 VB.NET 3.5 代码:
Private _UdbBuffer As Byte()
Private _ReceiveSocket As Socket
Private _NumReceived As Integer = 0
Private _StopWaitHandle As AutoResetEvent
Private Sub UdpListen()
_StopWaitHandle = New AutoResetEvent(False)
_UdpEndPoint = New Net.IPEndPoint(Net.IPAddress.Any, UDP_PORT_NUM)
_ReceiveSocket = New Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)
_ReceiveSocket.Bind(_UdpEndPoint)
ReDim _UdbBuffer(10000)
While Not _StopRequested
Dim ir As IAsyncResult = _ReceiveSocket.BeginReceive(_UdbBuffer, 0, 10000, SocketFlags.None, AddressOf UdpReceive, Nothing)
If Not _StopRequested Then
Dim waitHandles() As WaitHandle = {_StopWaitHandle, ir.AsyncWaitHandle}
If (WaitHandle.WaitAny(waitHandles) = 0) Then
Exit While
End If
End If
End While
_ReceiveSocket.Close()
End Sub
Private Sub UdpReceive(ByVal ar As IAsyncResult)
Dim len As Integer
If ar.IsCompleted Then
len = _ReceiveSocket.EndReceive(ar)
Threading.Interlocked.Increment(_NumReceived)
RaiseStatus("Got " & _NumReceived & " packets")
End If
End Sub
我发送数据如下(暂时不担心数据包内容):
For i as UShort = 0 to 999
Dim b(500) as Byte
_UdpClient.Send(b, b.Length)
Next
如果我在每次调用 Send 后添加一个小的延迟,则更多的数据包可以通过;然而,由于 Wireshark 说无论如何都已收到,看来问题出在我的接收代码中。我应该提到 UdpListen 是在单独的线程上运行的。
知道为什么我会丢包吗?我也尝试过 UdpClient.BeginReceive/EndReceive 但遇到了同样的问题。
困扰我的第二个问题是使用套接字时接收缓冲区的全局性质,我不确定是否处理传入数据包的速度不够快,以至于缓冲区会被覆盖。还不知道该怎么办,但我愿意接受建议。
9 月 26 日:更新
根据对这篇文章和其他帖子的回复中提出的各种有些相互冲突的建议,我对我的代码做了一些更改。感谢所有参与各种活动的人;我现在从拨号到快速以太网获取所有数据包。正如您所看到的,这是我的代码有问题,而不是 UDP 丢弃数据包的事实(事实上,自从我修复以来,我还没有看到超过很小比例的数据包被丢弃或乱序)。
区别:
1) 用 BeginReceiveFrom()/EndReceiveFrom() 替换 BeginReceive()/EndReceive()。但这本身并没有产生明显的影响。
2) 链接 BeginReceiveFrom() 调用,而不是等待异步句柄设置。不确定这里是否有任何好处。
3) 将 Socket.ReceiveBufferSize 显式设置为 500000,这足以以快速以太网速度传输 1 秒的数据。事实证明,这是一个与传递给 BeginReceiveFrom() 的缓冲区不同的缓冲区。这是最大的好处。
4)我还修改了我的发送例程,在发送一定数量的字节后根据预期带宽进行限制,等待几毫秒。这对我的接收代码有很大的好处,尽管 Wireshark 说即使没有这种延迟,我的所有数据仍然可以通过。
我最终没有使用单独的处理线程,因为据我了解,每次调用 BeginReceiveFrom 都会在新的工作线程上调用我的回调。这意味着我可以同时运行多个回调。这也意味着一旦我调用 BeginReceiveFrom 我就有时间做我的事情(只要我不花太长时间并耗尽可用的工作线程)。
Private Sub StartUdpListen()
_UdpEndPoint = New Net.IPEndPoint(Net.IPAddress.Any, UDP_PORT_NUM)
_ReceiveSocket = New Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)
_ReceiveSocket.ReceiveBufferSize = 500000
_ReceiveSocket.Bind(_UdpEndPoint)
ReDim _Buffer(50000)
_ReceiveSocket.BeginReceiveFrom(_Buffer, 0, _Buffer.Length, SocketFlags.None, _UdpEndPoint, AddressOf UdpReceive, Nothing)
End Sub
Private Sub UdpReceive(ByVal ar As IAsyncResult)
Dim len As Integer = _ReceiveSocket.EndReceiveFrom(ar, _UdpEndPoint)
Threading.Interlocked.Increment(udpreceived)
Dim receiveBytes As Byte()
ReDim receiveBytes(len - 1)
System.Buffer.BlockCopy(_Buffer, 0, receiveBytes, 0, len)
_ReceiveSocket.BeginReceiveFrom(_Buffer, 0, _UdbBuffer.Length, SocketFlags.None, _UdpEndPoint, AddressOf UdpReceive, Nothing)
//' At this point, do what we need to do with the data in the receiveBytes buffer
Trace.WriteLine("count=" & udpreceived)
End Sub
上面没有显示的是错误处理和处理 UDP 数据乱序或丢失的情况。
我认为这可以解决我的问题,但如果有人仍然认为上述内容有任何问题(或者我可以做得更好),我很想听听。
The following code waits for data over UDP. I have a test function that sends 1000 packets (datagrams?) of 500 bytes each. Each time I run the test function, the receiver gets only the first few dozen packets but drops the rest. I looked at the incoming network data using Wireshark and I see all 1000 packets are actually received, but just don't make it to may app's code.
Here is some of the relevant VB.NET 3.5 code:
Private _UdbBuffer As Byte()
Private _ReceiveSocket As Socket
Private _NumReceived As Integer = 0
Private _StopWaitHandle As AutoResetEvent
Private Sub UdpListen()
_StopWaitHandle = New AutoResetEvent(False)
_UdpEndPoint = New Net.IPEndPoint(Net.IPAddress.Any, UDP_PORT_NUM)
_ReceiveSocket = New Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)
_ReceiveSocket.Bind(_UdpEndPoint)
ReDim _UdbBuffer(10000)
While Not _StopRequested
Dim ir As IAsyncResult = _ReceiveSocket.BeginReceive(_UdbBuffer, 0, 10000, SocketFlags.None, AddressOf UdpReceive, Nothing)
If Not _StopRequested Then
Dim waitHandles() As WaitHandle = {_StopWaitHandle, ir.AsyncWaitHandle}
If (WaitHandle.WaitAny(waitHandles) = 0) Then
Exit While
End If
End If
End While
_ReceiveSocket.Close()
End Sub
Private Sub UdpReceive(ByVal ar As IAsyncResult)
Dim len As Integer
If ar.IsCompleted Then
len = _ReceiveSocket.EndReceive(ar)
Threading.Interlocked.Increment(_NumReceived)
RaiseStatus("Got " & _NumReceived & " packets")
End If
End Sub
I am sending the data as follows (not worried about the packet content for now):
For i as UShort = 0 to 999
Dim b(500) as Byte
_UdpClient.Send(b, b.Length)
Next
If I add a small delay after each call to Send, more packets make it through; however since Wireshark says that they were all received anyways, it seems that the problem is in my receive code. I should mention that UdpListen is running on a separate thread.
Any idea why I am dropping packets? I also tried UdpClient.BeginReceive/EndReceive but had the same problem.
A second issue that bothers me is the global nature of the receive buffer when using Sockets and I am not sure if I don't process incoming packets quickly enough that the buffer will be overwritten. Not sure what to do about that just yet but I am open to suggestions.
Sep 26: Update
Based on the various, somewhat conflicting suggestions from replies to this and other posts, I made some changes to my code. Thanks to all who chimed in various bits; I now get all my packets from dial-up to Fast Ethernet. As you can see, it was my code at fault and not the fact that UDP drops packets (in fact I have not seen more than a tiny percentage of packets being dropped or out of order since my fixes).
Differences:
1) Replaced BeginReceive()/EndReceive() with BeginReceiveFrom()/EndReceiveFrom(). By itself this had no notible effect though.
2) Chaining BeginReceiveFrom() calls instead of waiting for the async handle to set. Not sure if any benefit here.
3) Explicitly set the Socket.ReceiveBufferSize to 500000 which is enough for 1 second worth of my data at Fast Ethernet speed. Turns out this is a different buffer than the one passed to BeginReceiveFrom(). This had the biggest benefit.
4) I also modified my send routine to wait a couple of ms after having sent a certain number of bytes to throttle based on expected bandwidth. This had a big benefit for my receiving code even though Wireshark said all my data still made it across even without this delay.
I did NOT end up using a separate processing thread because, as I understand it, each call to BeginReceiveFrom will invoke my callback on a new worker thread. This means that I can have more than one callback running at the same time. It also means that once I call BeginReceiveFrom I have time to do my stuff (as long as I don't take too long and exaust the available worker threads).
Private Sub StartUdpListen()
_UdpEndPoint = New Net.IPEndPoint(Net.IPAddress.Any, UDP_PORT_NUM)
_ReceiveSocket = New Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)
_ReceiveSocket.ReceiveBufferSize = 500000
_ReceiveSocket.Bind(_UdpEndPoint)
ReDim _Buffer(50000)
_ReceiveSocket.BeginReceiveFrom(_Buffer, 0, _Buffer.Length, SocketFlags.None, _UdpEndPoint, AddressOf UdpReceive, Nothing)
End Sub
Private Sub UdpReceive(ByVal ar As IAsyncResult)
Dim len As Integer = _ReceiveSocket.EndReceiveFrom(ar, _UdpEndPoint)
Threading.Interlocked.Increment(udpreceived)
Dim receiveBytes As Byte()
ReDim receiveBytes(len - 1)
System.Buffer.BlockCopy(_Buffer, 0, receiveBytes, 0, len)
_ReceiveSocket.BeginReceiveFrom(_Buffer, 0, _UdbBuffer.Length, SocketFlags.None, _UdpEndPoint, AddressOf UdpReceive, Nothing)
//' At this point, do what we need to do with the data in the receiveBytes buffer
Trace.WriteLine("count=" & udpreceived)
End Sub
What is not shown above is the error handling and dealing with UDP data being out of order or missing.
I think this handles my issue, but if anybody still sees anything wrong with the above (or something I could do better) I would love to hear about it.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
UDP 可以随时丢弃数据包。在这种情况下,您可以尝试在接收器处设置更大的套接字接收缓冲区来缓解。
UDP can drop packets whenever it likes. In this case you could try setting a much larger socket receive buffer at the receiver to mitigate.
如上所述,UDP 不是一个可靠的协议。它是一种无连接协议,与 TCP 相比,它对 IP 数据包的开销要少得多。 UDP 对于许多功能(包括广播和多播消息)非常有用,但它不能用于可靠的消息传递。如果缓冲区过载,网络驱动程序将丢弃数据报。如果您需要可靠传递的基于消息的通信,或者您希望发送许多消息,您可以查看我们的 MsgConnect产品(提供免费开源版本),它通过套接字和 UDP 提供基于消息的数据传输。
As said above, UDP is not a reliable protocol. It's a connectionless protocol which puts much less overhead on IP packets, than TCP does. UDP is quite good for many functions (including broadcast and multicast messages) but it can't be used for reliable message delivery. If the buffer is overloaded, network driver will just drop datagrams. If you need message-based communication with reliable delivery or you expect many messages to be sent, you can check our MsgConnect product (free open-source version available), which provides message-based data transfer over sockets as well as over UDP.
对不起。我不明白你的代码。为什么要将异步方法包装在循环中?您应该从阅读异步处理开始。
UDP 只保证接收到完整的消息,除此之外别无其他。消息可能会被丢弃或以错误的顺序发送。您需要应用自己的算法来处理该问题。例如,有一种称为选择性重复。
接下来,如果您希望在短时间内收到大量消息,则不应在收到新消息之前处理每条消息。相反,将每个传入消息排入队列并使用单独的线程来负责处理。
第三:Udp 消息应使用 BeginReceiveFrom/EndReceiveFrom 进行异步处理,或使用 ReceiveFrom 进行同步处理。
Sorry. I do not understand your code. Why are you wrapping asynchronous methods in a loop? You should begin with reading up on asynchronous handling.
UDP only guarantee that complete messages are received, nothing else. A message can be dropped or come in incorrect order. You need to apply your own algorithm to handle that. There are one called Selective Repeat for instance.
Next, You should not process each message before receiving a new one if you are expecting to receive messages a lot of messages in a short period of time. Instead, enqueue each incoming message and have a separate thread that takes care of the processing.
Third: Udp messages should be processed with BeginReceiveFrom/EndReceiveFrom for asynchronous processing or ReceiveFrom for synchronous.
尝试更简单的方法。让接收器在一个单独的线程中运行,该线程在伪代码中看起来像这样:
在另一个线程中循环队列大小以确定是否已收到数据包。如果您收到了数据包,请处理它们,否则就睡觉。
Try a simpler approach. Have the receiver run in a separate thread that would look something like this in pseudo code:
In another thread loop on the queue size to determine if you have received packets. If you have received packets process them, else sleep.
正如其他人已经说过的,UDP 不是一种有保证的传送机制。因此,即使wireshark显示数据包已发送,但这并不意味着数据包已在目的地收到。接收主机上的 TCP/IP 堆栈仍然可能丢弃数据报。
您可以通过监视 perfmon.exe 中的以下性能计数器来确认是否发生这种情况。
如果您使用 IPv6 协议,
。您也可以尝试降低发送数据报的速率,看看是否会降低丢弃率。
As others have already said, UDP is not a guaranteed delivery mechanism. SO, even though wireshark is showing you that the packets were sent, it does not mean that the packets were received at the destination. The TCP/IP stack on the receiving host could still discard the datagrams.
You can confirm this is happening by monitoring the following performance counters in perfmon.exe.
if you are using IPv6 protocol.
Also you can try reducing the rate at which you send datagrams and see if that reduces the discard rate.