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

.NET:UDPClient.BeginReceive()如果接收的数据包太近,则无法运行回调函数,.net,vb.net,multithreading,sockets,asynchronous,.net,Vb.net,Multithreading,Sockets,Asynchronous,让我首先说,这与UDP作为协议的可靠性无关。我理解数据包可能会被丢弃、无序到达等。此外,严格来说,这不是数据速率的问题(我不想每秒处理太多数据包),也不是数据量的问题(不是客户端底层缓冲区大小的问题) 我的典型UDP侦听器如下所示(VB.NET如下所示): ParsePacket将是处理数据的子包 例如,如果您有一台服务器,它“尽可能快地”发送两个数据包,每个数据包都有120字节的数据,并且每对数据每秒发送一次(因此总数据率很低,但是,两个数据包到达“完全相同的时间”),那么通常可以看到Proc

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

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

ParsePacket
将是处理数据的子包

例如,如果您有一台服务器,它“尽可能快地”发送两个数据包,每个数据包都有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
分配给一个按钮,然后单击“离开”
assembleepacket
将创建要发送的数据数组

编辑

在重写回调以使其更整洁之后,我意识到问题在于检查了
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
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

我读过,但老实说,在那个例子中使用回调对我来说非常混乱。在我的代码中,我现在忽略传递给回调的
IASyncResult
,并且我从不使用传递给
BeginReceive
的“state对象”。也许专家可以告诉我写这个回调的“最正确”的方法?

我马上就看到了一些东西:

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

看起来您正在尝试重新启动
Try
Catch
子句中的侦听。将其移动到
最后
,它将在两种情况下都起作用

我要做的最后一件事是让
ParsePacket
在每次调用它时在它自己的线程上运行。这样就不会干扰主线程上的数据接收。看看这篇关于MSDN的文章:

我会将其修改为如下内容:

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

谢谢你的评论。我认为将
ParsePacket
放在它自己的线程上是多余的,因为回调已经在它自己的线程上被调用了,不是吗?我不这么认为。另外,您希望
ProcessPacket
尽快响应;每次回调运行时,它都在Visual Studio的线程窗口描述的“工作线程”上运行,
System.Threading.Thread.CurrentThread.ManagedThreadId
每次都不同。这意味着每次回调都在它自己的线程上。因此,我不认为使用另一个thread.SyncLock有什么意义。SyncLock可以用于线程,但它的目的是防止多个进程或线程同时访问单个资源。在您的情况下,您不希望任何其他进程在访问套接字时能够使用它
ProcessPacket
将始终位于工作线程上,因为调用
ListenSocket.BeginReceive
会启动另一个线程<默认情况下,代码>解析包将出现在主线程上。为了防止竞争条件,我将在一个单独的线程上执行
ParsePacket
。这将允许您处理太多数据包同时到达的情况。