Почему Socket.BeginReceive теряет пакеты из UDP? - PullRequest
7 голосов
/ 25 сентября 2010

Следующий код ожидает данные по 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, но у меня была та же проблема.

Вторая проблема, которая меня беспокоит, - это глобальный характер приемного буфера при использовании сокетов, и я не уверен, что не обработаю входящие пакеты достаточно быстро, чтобы буфер был перезаписан. Пока не знаю, что с этим делать, но я открыт для предложений.

26 сентября: Обновление


Основываясь на различных, несколько противоречивых предложениях из ответов на этот и другие посты, я внес некоторые изменения в свой код. Спасибо всем, кто звонил в разные части; Теперь я получаю все свои пакеты от коммутируемого доступа к Fast Ethernet. Как вы можете видеть, это был мой код ошибки, а не факт, что UDP отбрасывает пакеты (на самом деле я не видел, чтобы с момента исправления я потерял или вышел из строя более чем небольшой процент пакетов).

Различия:

1) Заменили BeginReceive () / EndReceive () на BeginReceiveFrom () / EndReceiveFrom (). Само по себе это не имело заметного эффекта.

2) Цепочка вызовов BeginReceiveFrom () вместо ожидания установки дескриптора асинхронности. Не уверен, если какая-либо выгода здесь.

3) Явно установите Socket.ReceiveBufferSize в 500000, что достаточно для 1 секунды моих данных на скорости Fast Ethernet. Оказывается, это другой буфер, чем тот, который передан в 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 ]

4 голосов
/ 25 сентября 2010

UDP может отбрасывать пакеты в любое время.В этом случае вы можете попытаться установить гораздо больший буфер приема в сокете на приемнике для смягчения.

1 голос
/ 25 сентября 2010

Как сказано выше, UDP не является надежным протоколом.Это протокол без установления соединения, который накладывает гораздо меньше накладных расходов на IP-пакеты, чем TCP.UDP довольно хорош для многих функций (включая широковещательные и многоадресные сообщения), но его нельзя использовать для надежной доставки сообщений.Если буфер перегружен, сетевой драйвер просто сбросит датаграммы.Если вам нужна коммуникация на основе сообщений с надежной доставкой или вы ожидаете отправки большого количества сообщений, вы можете проверить наш продукт MsgConnect (доступна бесплатная версия с открытым исходным кодом), который обеспечивает передачу данных на основе сообщений через сокеты, кака также по UDP.

1 голос
/ 25 сентября 2010

Извините. Я не понимаю ваш код. Почему вы помещаете асинхронные методы в цикл? Вы должны начать с чтения асинхронной обработки.

UDP гарантирует только получение полных сообщений, и ничего более. Сообщение может быть удалено или прийти в неправильном порядке. Вы должны применить свой собственный алгоритм, чтобы справиться с этим. Например, один называется Выборочное повторение .

Далее, вам не следует обрабатывать каждое сообщение перед получением нового, если вы ожидаете получить много сообщений за короткий промежуток времени. Вместо этого поставьте в очередь каждое входящее сообщение и создайте отдельный поток, отвечающий за обработку.

В-третьих: сообщения Udp должны обрабатываться с помощью BeginReceiveFrom / EndReceiveFrom для асинхронной обработки или ReceiveFrom для синхронной.

0 голосов
/ 27 сентября 2010

Как уже говорили другие, UDP не является гарантированным механизмом доставки.ТАК, хотя wireshark показывает вам, что пакеты были отправлены, это не означает, что пакеты были получены в месте назначения.Стек TCP / IP на принимающем хосте может по-прежнему отбрасывать дейтаграммы.

Вы можете убедиться в этом, наблюдая за следующими счетчиками производительности в perfmon.exe.

Локальный компьютер \IPv4 \ Полученные датаграммы отклонены

или

Локальный компьютер \ IPv6 \ Полученные датаграммы отклонены

, если вы используете протокол IPv6.

Также выможно попробовать уменьшить скорость, с которой вы отправляете дейтаграммы, и посмотреть, снизит ли это скорость сброса.

0 голосов
/ 25 сентября 2010

Попробуйте более простой подход. Пусть получатель работает в отдельном потоке, который будет выглядеть примерно так в псевдокоде:

do while socket_is_open???

  buffer as byte()

  socket receive buffer 'use the blocking version

  add buffer to queue of byte()

loop

В другом цикле потока на размер очереди, чтобы определить, если вы получили пакеты. Если вы получили пакеты, обработайте их, иначе спите.

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