首先我要说这与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
的“状态对象”。或许专家可以向我展示编写此回调的“最正确”方式吗?
答案 0 :(得分:1)
有一些事情我马上就看到了:
在最短时间内获取数据时,您应该只SyncLock
您的代码。不处理它。
您似乎也试图在Try
和Catch
子句中重新开始收听。将其移至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