繁体   English   中英

为什么Socket.BeginReceive从UDP丢失数据包?

[英]Why is Socket.BeginReceive losing packets from 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,但是有同样的问题。

困扰我的第二个问题是使用Socket时接收缓冲区的全局性质,我不确定是否没有足够快地处理传入数据包以至于缓冲区将被覆盖。 尚不确定该怎么办,但我愿意提出建议。

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数据混乱或丢失的错误。

我认为这可以解决我的问题,但是如果有人仍然认为以上内容有问题(或者我可以做得更好),我很想听听。

UDP可以随时丢弃数据包。 在这种情况下,您可以尝试在接收器处设置更大的套接字接收缓冲区以减轻负担。

抱歉。 我不理解您的代码。 为什么将异步方法包装在循环中? 您应该从阅读异步处理开始。

UDP仅保证接收到完整的消息,除此之外别无其他。 一条消息可能被丢弃或顺序错误。 您需要应用自己的算法来处理。 例如,有一个称为选择性重复

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

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

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

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

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???

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

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

丢弃接收到的本地计算机\\ IPv4 \\数据报

要么

丢弃接收到的本地计算机\\ IPv6 \\数据报

如果您使用的是IPv6协议。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM