为什么Socket.BeginReceive会丢失来自UDP的数据包?

时间:2010-09-25 08:47:23

标签: .net vb.net sockets network-programming 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数据乱序或丢失。

我认为这可以处理我的问题,但如果有人仍然看到上述内容有任何问题(或者我可以做得更好的话),我很乐意听到它。

5 个答案:

答案 0 :(得分:4)

UDP可以随时丢弃数据包。在这种情况下,您可以尝试在接收器上设置一个更大的套接字接收缓冲区来缓解。

答案 1 :(得分:1)

对不起。我不明白你的代码。为什么要在循环中包装异步方法?您应该首先阅读异步处理。

UDP仅保证收到完整的消息,没有别的。消息可能被删除或者顺序不正确。您需要应用自己的算法来处理它。例如,有一个叫Selective Repeat

接下来,如果您希望在短时间内收到大量消息,则不应在收到新消息之前处理每条消息。相反,将每个传入的消息入队,并有一个单独的线程负责处理。

第三:Udp消息应该使用BeginReceiveFrom / EndReceiveFrom进行异步处理,或者使用ReceiveFrom进行同步处理。

答案 2 :(得分:1)

如上所述,UDP不是可靠的协议。它是一种无连接协议,与TCP相比,它对IP数据包的开销要小得多。 UDP对于许多功能(包括广播和多播消息)非常有用,但它不能用于可靠的消息传递。如果缓冲区过载,网络驱动程序将丢弃数据报。如果您需要基于消息的通信和可靠的交付,或者您希望发送许多消息,您可以查看我们的MsgConnect产品(可用的免费开源版本),该产品通过套接字提供基于消息的数据传输以及通过UDP。

答案 3 :(得分:0)

尝试更简单的方法。让接收器在一个单独的线程中运行,在伪代码中看起来像这样:

do while socket_is_open???

  buffer as byte()

  socket receive buffer 'use the blocking version

  add buffer to queue of byte()

loop

在队列大小的另一个线程循环中确定您是否已收到数据包。如果你收到了数据包处理它们,那么就睡觉。

do

if queue count > 0

  do 

    rcvdata = queue dequeue

    process the rcvdata

  loop while queue count > 0

else

  sleep

end if

loop while socket_is_open???

答案 4 :(得分:0)

正如其他人已经说过的那样,UDP并不是一种有保障的传递机制。因此,即使wireshark向您显示数据包已发送,但这并不意味着数据包是在目的地接收的。接收主机上的TCP / IP堆栈仍然可以丢弃数据报。

您可以通过监视perfmon.exe中的以下性能计数器来确认这种情况。

  

收到本地计算机\ IPv4 \ Datagrams   丢弃

     

     

收到本地计算机\ IPv6 \ Datagrams   丢弃

如果您使用的是IPv6协议。

此外,您可以尝试降低发送数据报的速率,看看是否会降低丢弃率。