2010-09-25 12 views
7

El siguiente código espera datos sobre UDP. Tengo una función de prueba que envía 1000 paquetes (datagramas?) De 500 bytes cada uno. Cada vez que ejecuto la función de prueba, el receptor recibe solo las primeras docenas de paquetes, pero descarta el resto. Miré los datos de la red entrante usando Wireshark y veo que los 1000 paquetes realmente se reciben, pero simplemente no lo hacen al código de la aplicación may.¿Por qué Socket.BeginReceive pierde paquetes de UDP?

Aquí tienes lo relevante VB.NET 3.5 código:

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 

estoy enviando los datos de la siguiente manera (no está preocupado por el contenido de los paquetes, por ahora):

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

Si yo agregue un pequeño retraso después de cada llamada a Enviar, más paquetes lo hacen; sin embargo, dado que Wireshark dice que todos fueron recibidos de todos modos, parece que el problema está en mi código de recepción. Debo mencionar que UdpListen se ejecuta en un hilo separado.

¿Alguna idea de por qué estoy dejando caer paquetes? También probé UdpClient.BeginReceive/EndReceive pero tuve el mismo problema.

Un segundo problema que me molesta es la naturaleza global del búfer de recepción al usar Sockets y no estoy seguro si no proceso los paquetes entrantes lo suficientemente rápido como para sobrescribir el búfer. Todavía no estoy seguro de qué hacer con eso, pero estoy abierto a sugerencias.

26 de septiembre: Actualización


Sobre la base de las diversas sugerencias, un tanto contradictorias de las respuestas a este y otros mensajes, he hecho algunos cambios en mi código. Gracias a todos los que intervinieron en varios fragmentos; Ahora obtengo todos mis paquetes de acceso telefónico a Fast Ethernet. Como puede ver, fue mi código el que falló y no el hecho de que UDP omita paquetes (de hecho, no he visto más que un pequeño porcentaje de paquetes caídos o fuera de servicio desde mis correcciones).

Diferencias:

1) Sustituido BeginReceive()/EndReceive() con BeginReceiveFrom()/EndReceiveFrom(). Sin embargo, esto no tuvo ningún efecto notorio.

2) Encadenar llamadas BeginReceiveFrom() en lugar de esperar a que se establezca el async handle. No estoy seguro de si hay algún beneficio aquí.

3) Establecer explícitamente el Socket.ReceiveBufferSize en 500000 que es suficiente para 1 segundo de mis datos a la velocidad de Fast Ethernet. Resulta que este es un buffer diferente al que pasó a BeginReceiveFrom(). Esto tuvo el mayor beneficio.

4) También modifiqué mi rutina de envío para esperar un par de ms después de haber enviado un cierto número de bytes para acelerar según el ancho de banda esperado. Esto tuvo un gran beneficio para mi código de recepción a pesar de que Wireshark dijo que todos mis datos seguían llegando incluso sin este retraso.

NO terminé usando un hilo de procesamiento separado porque, según tengo entendido, cada llamada a BeginReceiveFrom invocará mi devolución de llamada en un nuevo hilo de trabajo. Esto significa que puedo ejecutar más de una devolución de llamada al mismo tiempo. También significa que una vez que llamo a BeginReceiveFrom tengo tiempo para hacer mis cosas (siempre que no me tome demasiado tiempo y agote los hilos de trabajo disponibles).

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 

Lo que no se muestra arriba es el manejo de errores y el manejo de datos UDP que están fuera de servicio o que faltan.

Creo que esto maneja mi problema, pero si alguien todavía ve algo mal con lo de arriba (o algo que podría hacer mejor) Me encantaría saberlo.

+0

¿Qué quiere decir por 'Una segunda cuestión que me molesta es la naturaleza global del búfer de recepción cuando se utiliza sockets'? Hay uno por socket. Nada global al respecto. – EJP

+0

"No estoy seguro de que si no procesan los paquetes entrantes con suficiente rapidez que el búfer se sobrescribe". No será sobrescrito. Los paquetes entrantes se * soltarán * si el buffer está lleno. – EJP

+0

@EJP, ese comentario se aplicó a mi código de código original. Dado que estaba usando el búfer, podría sobrescribirse con el siguiente grupo de datos mientras lo estaba leyendo. El nuevo código no tiene este problema ya que hace una copia en bloque del búfer (alrededor de 200 bytes) antes de volver para escuchar más datos. –

Respuesta

4

UDP puede soltar paquetes cuando lo desee. En este caso, podría intentar configurar un búfer de recepción de zócalo mucho más grande en el receptor para mitigarlo.

+0

El problema es que, de acuerdo con Wireshark, todos los paquetes llegaron a la computadora remota, así que no creo que en realidad se redujo en UDP. Además, cada llamada a EndReceive recibe 500 bytes y cuando miro el contenido de la memoria intermedia, solamente 500 de los 10.000 bytes están llenos, así que no creo que se desborde. ¿Te refieres a un buffer diferente? –

+0

UDP no se detiene en la computadora remota. Incluye la pila UDP * en * la computadora remota, y el búfer de recepción del zócalo es parte de eso. Si no hay espacio, el paquete se descarta. Así que hazlo lo suficientemente grande, o haz que tu código de recepción sea lo suficientemente rápido, o haz que tu código de envío sea lo suficientemente lento. E incluso entonces todavía puede obtener paquetes caídos. Entonces tu aplicación solo tiene que hacer frente. Si desea confiabilidad, use TCP. – EJP

+0

Mi código de ejemplo anterior envía 500000 bytes en aproximadamente 1 segundo. ¿Estás diciendo que el buffer que paso a BeginReceive/BeginReceiveFrom tiene que ser tan grande? Parece bastante excesivo, especialmente porque cada vez que llamo a EndReceive/EndReceiveFrom el resto de ese búfer parece no utilizado. ¿Hay un buffer diferente que debería inicializar? –

1

Lo siento. No entiendo tu código. ¿Por qué estás envolviendo métodos asíncronos en un bucle? Deberías comenzar leyendo sobre el manejo asíncrono.

UDP solo garantiza que se reciban los mensajes completos, nada más. Un mensaje puede soltarse o aparecer en un orden incorrecto. Necesita aplicar su propio algoritmo para manejar eso. Hay uno llamado Selective Repeat por ejemplo.

A continuación, no debe procesar cada mensaje antes de recibir uno nuevo si espera recibir mensajes con un montón de mensajes en un período de tiempo corto. En su lugar, coloque cada mensaje entrante y tenga un hilo separado que se encargue del procesamiento.

Tercero: los mensajes de Udp se deben procesar con BeginReceiveFrom/EndReceiveFrom para el procesamiento asincrónico o ReceiveFrom para síncrono.

+0

Gracias por sus sugerencias. Está en un bucle porque quiero leer más de un paquete. Cuando se produce el evento asíncrono, indica que el identificador de espera continúa y empiezo a buscar el siguiente paquete mientras proceso el actual. Esta es una de las dos técnicas que he visto para el uso eficiente del procesador (la otra opción es encadenar la llamada BeginReceive en la devolución de llamada en sí). También probé simplemente reemplazar mi código con BeginReceiveFrom/EndReceiveFrom pero aún tenía el mismo problema. ¿Tienes alguna sugerencia de código de muestra? –

+0

También me gustaría añadir que el problema se produce con el código de prueba de devolución de llamada reducido a nada más que incrementar un contador de modo de procesamiento en lo mínimo. –

1

Como se dijo anteriormente, UDP no es un protocolo confiable. Es un protocolo sin conexión que aplica mucha menos sobrecarga en los paquetes IP que TCP. UDP es bastante bueno para muchas funciones (incluidos los mensajes de difusión y multidifusión), pero no se puede utilizar para la entrega confiable de mensajes. Si el búfer está sobrecargado, el controlador de red simplemente soltará datagramas. Si necesita una comunicación basada en mensajes con entregas confiables o espera que se envíen muchos mensajes, puede consultar nuestro producto MsgConnect (versión gratuita de código abierto disponible), que proporciona transferencia de datos basada en mensajes a través de sockets y también a través de UDP.

+0

Gracias, por favor, consulte mi respuesta anterior, ya que no me parece que estoy lanzando paquetes de acuerdo con Wireshark. Sé que UDP no es confiable, pero obtener 80% de los paquetes caídos en una red de área local cuando otras aplicaciones de prueba UDP funcionan bien me dice que tengo un problema con mi código. –

+0

@DanC: el controlador de recepción eliminará los paquetes si no hay memoria intermedia esperando para recibirlos. Es decir. alcanzaron la NIC, pero no su aplicación. – Richard

+0

Sí, como señaló jgauffin arriba, no debe procesar los mensajes inmediatamente. Páselos a otro hilo si necesita procesar mensajes sobre la marcha. Finalmente, puede haber algún problema con BeginReceive en particular, pero no estoy al tanto de las operaciones UDP en este escenario - usamos operaciones sincrónicas en el transporte UDP de MsgConnect –

0

Pruebe con un enfoque más simple. Hacer que el receptor ejecute en un hilo separado que sería algo como esto en pseudo código:

do while socket_is_open??? 

    buffer as byte() 

    socket receive buffer 'use the blocking version 

    add buffer to queue of byte() 

loop 

En otro bucle de hilo en el tamaño de la cola para determinar si ha recibido paquetes. Si ha recibido paquetes, trátelos, si no, duerma.

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??? 
+0

Gracias, lo tendré en cuenta cuando empiece a hacer algo con el búfer (aunque es más probable que use señal en lugar de dormir ya que los datos pueden aparecer en cualquier momento). Sin embargo, mi ejemplo anterior está literalmente contando los paquetes recibidos, por lo que el tiempo de procesamiento no debería ser un factor, ¿o sí? –

+0

Observe que estoy usando la versión de bloqueo de receive, que bloquea esa hebra a menos que haya datos. Cuando hay datos, simplemente los coloca en una cola que procesa el otro subproceso. El hilo que procesa los datos tiene que tener una forma de ceder el control cuando no hay datos para procesar.Hace años, escribí una aplicación UDP que probaba el ancho de banda usando el método descrito. No tuve problemas para probar conmutadores FDX de 100 Mbps a velocidad de cable (200 Mbps). – dbasnett

0

Como ya se ha dicho, UDP no es un mecanismo de entrega garantizado. ASÍ, aunque wireshark te muestra que los paquetes fueron enviados, no significa que los paquetes fueron recibidos en el destino. La pila TCP/IP en el host receptor aún podría descartar los datagramas.

Puede confirmar que esto está sucediendo mediante la supervisión de los siguientes contadores de rendimiento en perfmon.exe.

equipo local \ \ IPv4 datagramas recibidos desechados

o

local IPv6 del equipo \ \ datagramas recibidos desechados

si está utilizando el protocolo IPv6.

También usted puede intentar reducir la velocidad a la que enviará los datagramas y ver si se reduce la tasa de descarte.

+0

Tenía Wireshark ejecutándose tanto en el emisor como en el receptor. Mostró que todos los paquetes estaban cruzando el cable, incluso con la versión anterior. De acuerdo con System.Net.NetworkInformation.IPv4InterfaceStatistics, no se han eliminado paquetes de ninguno de los extremos. No estoy seguro de cómo ni a qué capa Wireshark obtiene sus datos, pero mi conclusión es que, de alguna manera, mi código no se estaba extrayendo de la red lo suficientemente rápido (a pesar de que estaba en un círculo bastante cerrado). Aumentar el Socket.ReceiveBufferSize pareció darme más tiempo para asegurarme de tener todos los paquetes procesados. Reducir la tasa de envío también mejoró las cosas. –

Cuestiones relacionadas