.NET:如果收到的数据包太靠近,则UDPClient.BeginReceive()无法运行回调函数

时间:2014-01-26 17:37:59

标签: .net vb.net multithreading sockets asynchronous

首先我要说这与UDP作为协议的可靠性无关。我知道数据包可以丢弃,无序到达等等。此外,这不是数据速率严格来说的问题(我不是每秒处理过多的数据包),也不是它是数据量的问题(不是客户端底层缓冲区大小的问题)。

我的典型UDP监听器如下所示(VB.NET如下):

Private ListenSocket As System.Net.Sockets.UdpClient
Private UDPSyncResult As System.IAsyncResult
Private UDPState As New Object
Private ReadOnly UDPReadLock As New Object
Private Sub StartListening()
    Try
        ListenSocket = New System.Net.Sockets.UdpClient
        ListenSocket.Client.SetSocketOption( _
            Net.Sockets.SocketOptionLevel.Socket, _
            Net.Sockets.SocketOptionName.ReuseAddress, True)
        ListenSocket.Client.Bind(New System.Net.IPEndPoint( _
            System.Net.IPAddress.Any, ListenPort))
        UDPSyncResult = ListenSocket.BeginReceive(AddressOf ProcessPacket, _
            UDPState)
    Catch ex As Exception
        'Handle an exception
    End Try
End Function

Private Sub ProcessPacket(ByVal ar As IAsyncResult)
    SyncLock UDPReadLock
        Try
            If ar Is UDPSyncResult Then
                Dim Data As Byte() = ListenSocket.EndReceive(ar, _
                    New System.Net.IPEndPoint(System.Net.IPAddress.Any, 0))
                ParsePacket(Data)
                UDPSyncResult = ListenSocket.BeginReceive( _
                    AddressOf ProcessPacket, UDPState)
            End If
        Catch ex As Exception
            Try
                UDPSyncResult = ListenSocket.BeginReceive( _
                    AddressOf ProcessPacket, UDPState)
            Catch ex2 As Exception
                'Do nothing
            End Try
        End Try
    End SyncLock
End Sub

ParsePacket将是一个对数据做某事的Sub。

如果您有一台服务器,例如,“尽可能快地”发送两个数据包,每个数据包,例如120字节的数据,每对发送一次(因此总数据速率很低,但是,两个数据包到达“完全相同的时间”)然后发生的事情是,经常可以看到ProcessPacket被调用两次,但不可避免地,它将被调用一次然后再也不会被调用。也就是说,有些东西会杀死整个检测数据包,调用回调函数机制。

它看起来像一个与线程冲突有关的问题,因为它可以工作很长时间然后突然根本不起作用。我的代码有问题吗?

为了进行测试,可以通过运行简单的服务器轻松复制问题:

Private Sub StartTalking()
    Try
        TalkSocket = New System.Net.Sockets.UdpClient
        TalkSocket.Connect(System.Net.IPAddress.Parse(TalkIP), _
            TalkPort)
    Catch ex As Exception

    End Try
End Sub

Private Sub SendTwoPackets()
    Dim Packet As Byte() = AssemblePacket()
    Try
        TalkSocket.Send(Packet, Packet.Length)
        TalkSocket.Send(Packet, Packet.Length)
    Catch ex As Exception

    End Try
End Sub

SendTwoPackets分配给按钮,然后点击即可。 AssemblePacket将创建要发送的数据数组。

修改

在重写我的回调以使其更整洁之后,我意识到问题出在我的ar Is UDPSyncResult检查上。如果这是False,那么我再也不会打电话给BeginReceive了。所以现在我的回调看起来更像是这样:

Private Sub ProcessPacket(ByVal ar As IAsyncResult)
    SyncLock UDPReadLock
        Try
            Dim Data As Byte() = ListenSocket.EndReceive(ar, _
                New System.Net.IPEndPoint(System.Net.IPAddress.Any, 0))
            ParsePacket(Data)
            UDPSyncResult = ListenSocket.BeginReceive( _
                AddressOf ProcessPacket, UDPState)
        Catch ex As Exception
            Try
                UDPSyncResult = ListenSocket.BeginReceive( _
                    AddressOf ProcessPacket, UDPState)
            Catch ex2 As Exception
                'Do nothing
            End Try
        End Try
    End SyncLock
End Sub

我读了MSDN article on asynchronous methods但老实说在这个例子中使用回调对我来说非常困惑。在我的代码中,我现在忽略传递给回调的IASyncResult,我从不使用传递给BeginReceive的“状态对象”。或许专家可以向我展示编写此回调的“最正确”方式吗?

1 个答案:

答案 0 :(得分:1)

有一些事情我马上就看到了:

在最短时间内获取数据时,您应该只SyncLock您的代码。不处理它。

您似乎也试图在TryCatch子句中重新开始收听。将其移至Finally,它将在两个实例中都有效。

我要做的最后一件事就是每次调用时ParsePacket都在自己的线程上运行。然后,您不会干扰主线程上的数据接收。在MSDN上查看这篇文章:Walkthrough: Authoring a Simple Multithreaded Component with Visual Basic

我会将其修改为:

Private Sub ProcessPacket(ByVal ar As IAsyncResult)
    Dim Data As Byte()

    SyncLock UDPReadLock
        Try
            Data = ListenSocket.EndReceive(ar, _
                New System.Net.IPEndPoint(System.Net.IPAddress.Any, 0))
        Catch ex As Exception
            ' Handle any exceptions here
        Finally
            Try
                UDPSyncResult = ListenSocket.BeginReceive( _
                    AddressOf ProcessPacket, UDPState)
            Catch ex As Exception
                ' Do nothing
            End Try
        End Try
    End SyncLock

    ' Only attempt to process if we received data
    If Data.Length > 0 Then
        ParsePacket(Data)
    End If
End Sub