views:

145

answers:

5

The following code waits for data over UDP. I have a test function that sends 1000 packets (datagrams?) of 500 bytes each. Each time I run the test function, the receiver gets only the first few dozen packets but drops the rest. I looked at the incoming network data using Wireshark and I see all 1000 packets are actually received, but just don't make it to may app's code.

Here is some of the relevant VB.NET 3.5 code:

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

I am sending the data as follows (not worried about the packet content for now):

For i as UShort = 0 to 999
   Dim b(500) as Byte
   _UdpClient.Send(b, b.Length)       
Next

If I add a small delay after each call to Send, more packets make it through; however since Wireshark says that they were all received anyways, it seems that the problem is in my receive code. I should mention that UdpListen is running on a separate thread.

Any idea why I am dropping packets? I also tried UdpClient.BeginReceive/EndReceive but had the same problem.

A second issue that bothers me is the global nature of the receive buffer when using Sockets and I am not sure if I don't process incoming packets quickly enough that the buffer will be overwritten. Not sure what to do about that just yet but I am open to suggestions.

Sep 26: Update


Based on the various, somewhat conflicting suggestions from replies to this and other posts, I made some changes to my code. Thanks to all who chimed in various bits; I now get all my packets from dial-up to Fast Ethernet. As you can see, it was my code at fault and not the fact that UDP drops packets (in fact I have not seen more than a tiny percentage of packets being dropped or out of order since my fixes).

Differences:

1) Replaced BeginReceive()/EndReceive() with BeginReceiveFrom()/EndReceiveFrom(). By itself this had no notible effect though.

2) Chaining BeginReceiveFrom() calls instead of waiting for the async handle to set. Not sure if any benefit here.

3) Explicitly set the Socket.ReceiveBufferSize to 500000 which is enough for 1 second worth of my data at Fast Ethernet speed. Turns out this is a different buffer than the one passed to BeginReceiveFrom(). This had the biggest benefit.

4) I also modified my send routine to wait a couple of ms after having sent a certain number of bytes to throttle based on expected bandwidth. This had a big benefit for my receiving code even though Wireshark said all my data still made it across even without this delay.

I did NOT end up using a separate processing thread because, as I understand it, each call to BeginReceiveFrom will invoke my callback on a new worker thread. This means that I can have more than one callback running at the same time. It also means that once I call BeginReceiveFrom I have time to do my stuff (as long as I don't take too long and exaust the available worker threads).

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

What is not shown above is the error handling and dealing with UDP data being out of order or missing.

I think this handles my issue, but if anybody still sees anything wrong with the above (or something I could do better) I would love to hear about it.

+2  A: 

UDP can drop packets whenever it likes. In this case you could try setting a much larger socket receive buffer at the receiver to mitigate.

EJP
The problem is that, according to Wireshark, all the packets made it to the remote computer so I don't think they are actually dropped by UDP. Also, each call to EndReceive gets 500 bytes and when I look at the contents of the buffer, only 500 of the 10000 bytes are filled so I don't think it's overflowing. Are you referring to a different buffer?
Dan C
UDP doesn't stop at the remote computer. It includes the UDP stack *in* the remote computer, and the socket receive buffer is part of that. If there is no room there the packet is dropped. So make it big enough, or make your receiving code fast enough, or make your sending code slow enough. And even then you can still get dropped packets. So your application just has to cope. If you want reliability, use TCP.
EJP
My sample code above sends 500000 bytes in about 1 second. Are you saying that the buffer that I pass to BeginReceive/BeginReceiveFrom has to be that big? Seem rather excessive, especially since each time I call EndReceive/EndReceiveFrom the rest of that buffer seems unused. Is there a different buffer I should be initializing?
Dan C
I am talking about the socket receive buffer. This is a data structure in the Kernel whose size you can control via the API. By default it is 8k in Windows, absurdly small.
EJP
Thanks, that's the buffer that I ended up finding and increasing to 500000 in my laste udpate.
Dan C
+1  A: 

Sorry. I do not understand your code. Why are you wrapping asynchronous methods in a loop? You should begin with reading up on asynchronous handling.

UDP only guarantee that complete messages are received, nothing else. A message can be dropped or come in incorrect order. You need to apply your own algorithm to handle that. There are one called Selective Repeat for instance.

Next, You should not process each message before receiving a new one if you are expecting to receive messages a lot of messages in a short period of time. Instead, enqueue each incoming message and have a separate thread that takes care of the processing.

Third: Udp messages should be processed with BeginReceiveFrom/EndReceiveFrom for asynchronous processing or ReceiveFrom for synchronous.

jgauffin
Thanks for your suggestions. It is in a loop because I want to read more than one packet. When the async event occurs, it signals the wait handle to continue and I start checking for the next packet while I process the current one. This is one of two techniques I have seen for efficient use of the processor (the other option is to chain the call BeginReceive in the callback itself). Also I tried just replacing my code with BeginReceiveFrom/EndReceiveFrom but still had the same problem. Do you have any sample code suggestions?
Dan C
I should also add that the problem occurs with the callback test code cut down to nothing more than incrementing a counter so processing in very minimum.
Dan C
+1  A: 

As said above, UDP is not a reliable protocol. It'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't be used for reliable message delivery. If the buffer is overloaded, network driver will just drop datagrams. If you need message-based communication with reliable delivery or you expect many messages to be sent, you can check our MsgConnect product (free open-source version available), which provides message-based data transfer over sockets as well as over UDP.

Eugene Mayevski 'EldoS Corp
Thanks, please see my above reply as it does not appear to me that I am dropping packets according to Wireshark. I know that UDP is not reliable, but getting 80% of packets dropped on a local area network when other UDP test apps work fine tells me I have a problem with my code.
Dan C
@DanC: Packets will be dropped by the receiving driver if there is no buffer waiting to receive them. I.e. they reached the NIC, but not your application.
Richard
Yes, as jgauffin pointed above, you should not process messages immediately. Pass them to other thread if you need to process messages on-the-fly. Finally, there can be some issue with BeginReceive in particular, but I am not aware of specifics of UDP operations in this scenario - we use synchronous operations in UDP transport of MsgConnect
Eugene Mayevski 'EldoS Corp
A: 

Try a simpler approach. Have the receiver run in a separate thread that would look something like this in pseudo code:

do while socket_is_open???

  buffer as byte()

  socket receive buffer 'use the blocking version

  add buffer to queue of byte()

loop

In another thread loop on the queue size to determine if you have received packets. If you have received packets process them, else sleep.

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???
dbasnett
Thanks, I will keep this in mind for when I actually start doing something with the buffer (though i will more likely use signal instead of sleep since the data can come in any time). However, my example above is literally just counting the received packets so the processing time should not be a factor, should it?
Dan C
Notice that I am using the blocking version of receive, which blocks that thread unless there is data. When there is data it simply places it in a queue that the other thread processes. The thread that processes the data has to have a way to relinquish control when there is no data to process. Years ago I wrote a UDP app that tested bandwidth using the method described. I did not have a problem testing 100Mbps FDX switches at wire speed (200 Mbps).
dbasnett
A: 

As others have already said, UDP is not a guaranteed delivery mechanism. SO, even though wireshark is showing you that the packets were sent, it does not mean that the packets were received at the destination. The TCP/IP stack on the receiving host could still discard the datagrams.

You can confirm this is happening by monitoring the following performance counters in perfmon.exe.

Local Computer\IPv4\Datagrams Received Discarded

or

Local Computer\IPv6\Datagrams Received Discarded

if you are using IPv6 protocol.

Also you can try reducing the rate at which you send datagrams and see if that reduces the discard rate.

feroze
I had Wireshark running on both sender and receiver. It showed all packets were making it across the wire even with the old version. According to System.Net.NetworkInformation.IPv4InterfaceStatistics, there were no packets dropped from either end. Not sure how or at what layer Wireshark gets its data from, but my conclusion is that somehow my code was not pulling from the network fast enough (even though I was in a pretty tight loop). Increasing the Socket.ReceiveBufferSize seemed to give me more time to ensure I had all the packets processed. Reducing the send rate also improved things.
Dan C