以下代码等待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,但遇到了同样的问题 .
困扰我的第二个问题是使用套接字时接收缓冲区的全局特性,我不确定我是否不会足够快地处理传入的数据包,以至于缓冲区将被覆盖 . 不知道该怎么办,但我愿意接受建议 .
Sep 26: Update
基于对此帖和其他帖子的回复中的各种有些冲突的建议,我对我的代码进行了一些更改 . 感谢所有以各种方式发声的人;我现在将所有数据包从拨号到快速以太网 . 正如你所看到的那样,这是我的错误代码,而不是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 回答
正如其他人已经说过的那样,UDP并不是一种有保障的传输机制 . 因此,即使wireshark向您显示数据包已发送,但这并不意味着数据包是在目的地接收的 . 接收主机上的TCP / IP堆栈仍然可以丢弃数据报 .
您可以通过监视perfmon.exe中的以下性能计数器来确认这种情况 .
如果您使用的是IPv6协议 .
您也可以尝试降低发送数据报的速率,看看是否会降低丢弃率 .
尝试一种更简单的方法 . 让接收器在一个单独的线程中运行,在伪代码中看起来像这样:
在队列大小的另一个线程循环中,以确定您是否已收到数据包 . 如果你已收到数据包处理它们,否则睡觉 .
UDP可以随时丢弃数据包 . 在这种情况下,您可以尝试在接收器处设置更大的套接字接收缓冲区以减轻负担 .
如上所述,UDP不是可靠的协议 . 它's a connectionless protocol which puts much less overhead on IP packets, than TCP does. UDP is quite good for many functions (including broadcast and multicast messages) but it can'用于可靠的消息传递 . 如果缓冲区过载,网络驱动程序将丢弃数据报 . 如果您需要基于消息的通信和可靠的交付,或者您希望发送许多消息,您可以查看我们的产品(免费的开源版本),该产品通过套接字和UDP提供基于消息的数据传输 .
抱歉 . 我不明白你的代码 . 为什么要在循环中包装异步方法?您应该首先阅读异步处理 .
UDP仅保证接收完整的消息,没有别的 . 消息可能被删除或者顺序不正确 . 您需要应用自己的算法来处理它 . 例如,有一个叫做Selective Repeat .
接下来,如果您希望在短时间内收到大量消息,则不应在收到新消息之前处理每条消息 . 相反,将每个传入的消息排入队列并具有一个负责处理的单独线程 .
第三:Udp消息应该使用BeginReceiveFrom / EndReceiveFrom进行异步处理,或者使用ReceiveFrom进行同步处理 .