2010-09-25 20 views
7

Il seguente codice attende i dati su UDP. Ho una funzione di test che invia 1000 pacchetti (datagrammi?) Di 500 byte ciascuno. Ogni volta che eseguo la funzione di test, il ricevitore riceve solo le prime dozzine di pacchetti ma cade il resto. Ho guardato i dati della rete in entrata usando Wireshark e vedo che tutti i 1000 pacchetti sono stati effettivamente ricevuti, ma semplicemente non ce l'hanno con il codice dell'app.Perché Socket.BeginRece perde i pacchetti da UDP?

Ecco alcuni dei VB.NET 3.5 codice rilevante:

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 

sto trasmettendo i dati nel seguente modo (non sono preoccupato per il contenuto del pacchetto, per ora):

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

Se io aggiungi un piccolo ritardo dopo ogni chiamata a Invia, più pacchetti riescono a passare; tuttavia dal momento che Wireshark dice che sono stati tutti ricevuti comunque, sembra che il problema sia nel mio codice di ricezione. Dovrei ricordare che UdpListen è in esecuzione su un thread separato.

Qualche idea del motivo per cui ho interrotto i pacchetti? Ho anche provato UdpClient.BeginReceive/EndReceive ma ho avuto lo stesso problema.

Un secondo problema che mi dà fastidio è la natura globale del buffer di ricezione quando si utilizzano Socket e non sono sicuro se non elaboro i pacchetti in ingresso abbastanza rapidamente da sovrascrivere il buffer. Non sono sicuro di cosa fare al riguardo, ma sono aperto a suggerimenti.

26 settembre: aggiornamento


Sulla base dei vari suggerimenti, un po 'contrastanti da risposte a questo e altri messaggi, ho fatto alcune modifiche al mio codice. Grazie a tutti quelli che suonavano in vari pezzi; Ora prendo tutti i miei pacchetti da dial-up a Fast Ethernet. Come potete vedere, è stato il mio codice in errore e non il fatto che UDP lasci cadere i pacchetti (in effetti non ho visto più di una piccola percentuale di pacchetti rilasciati o fuori servizio dopo le mie correzioni).

Differenze:

1) sostituiti BeginReceive()/EndReceive() con BeginReceiveFrom()/EndReceiveFrom(). Di per sé questo non ha avuto alcun effetto notabile però.

2) Concatenare BeginReceiveFrom() chiama invece di attendere l'impostazione della maniglia asincrona. Non sono sicuro se qualche vantaggio qui.

3) Impostare Socket.ReceiveBufferSize su 500000 in modo esplicito, che è sufficiente per 1 secondo dei miei dati alla velocità Fast Ethernet. Risulta che questo è un buffer diverso da quello passato a BeginReceiveFrom(). Questo ha avuto il più grande beneficio.

4) Ho modificato anche la mia routine di invio per attendere un paio di ms dopo aver inviato un certo numero di byte al throttle in base alla larghezza di banda prevista. Ciò ha avuto un grande vantaggio per il mio codice ricevente, anche se Wireshark ha detto che tutti i miei dati sono riusciti a superarlo anche senza questo ritardo.

NON ho finito per utilizzare un thread di elaborazione separato perché, a quanto ho capito, ogni chiamata a BeginReceiveFrom invocherà il mio callback su un nuovo thread di lavoro. Ciò significa che posso avere più di una callback in esecuzione allo stesso tempo. Significa anche che una volta chiamo BeginReceiveFrom ho tempo per fare le mie cose (a patto che non impieghi troppo tempo ed esaurisca i thread di lavoro disponibili).

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 

Ciò che non è indicato sopra è l'errore di gestione e gestione dei dati UDP che è fuori servizio o mancante.

Penso che questo gestisca il mio problema, ma se qualcuno ancora vede qualcosa di sbagliato con quanto sopra (o qualcosa che potrei fare meglio) mi piacerebbe sentirne parlare.

+0

Cosa intendi con 'Un secondo problema che mi dà fastidio è la natura globale del buffer di ricezione quando si usano Socket'? Ce n'è uno per socket. Niente di globale a riguardo. – EJP

+0

"Non sono sicuro se non elaboro i pacchetti in entrata abbastanza rapidamente da sovrascrivere il buffer". Non verrà sovrascritto. I pacchetti in arrivo verranno * eliminati * se il buffer è pieno. – EJP

+0

@EJP, quel commento applicato al mio bit di codice originale. Dato come stavo usando il buffer, potrebbe essere sovrascritto dal prossimo gruppo di dati mentre lo stavo leggendo. Il nuovo codice non presenta questo problema poiché esegue una copia di blocco del buffer (circa 200 byte) prima di tornare ad ascoltare per ulteriori dati. –

risposta

4

UDP può rilasciare i pacchetti in qualsiasi momento. In questo caso si potrebbe provare a impostare un buffer di ricezione socket molto più grande sul ricevitore per mitigare.

+0

Il problema è che, secondo Wireshark, tutti i pacchetti sono stati inviati al computer remoto, quindi non penso che siano stati effettivamente eliminati da UDP. Inoltre, ogni chiamata a EndReceive ottiene 500 byte e quando guardo il contenuto del buffer, solo 500 dei 10000 byte sono riempiti quindi non penso che stia straripando. Ti riferisci ad un buffer diverso? –

+0

UDP non si ferma sul computer remoto. Include lo stack UDP * in * il computer remoto e il buffer di ricezione socket è parte di quello. Se non c'è spazio, il pacchetto viene eliminato. Quindi rendilo abbastanza grande, o rendi il tuo codice di ricezione abbastanza veloce, o rendi il tuo codice di invio abbastanza lento. E anche allora puoi ancora ottenere pacchetti persi. Quindi la tua applicazione deve solo farcela. Se vuoi affidabilità, usa TCP. – EJP

+0

Il mio codice di esempio sopra invia 500000 byte in circa 1 secondo. Stai dicendo che il buffer che passo a BeginReceive/BeginReceiveFrom deve essere così grande? Sembra piuttosto eccessivo, soprattutto dal momento che ogni volta che chiamo EndReceive/EndReceiveFrom del resto del buffer sembra inutilizzato. C'è un buffer diverso che dovrei inizializzare? –

1

Siamo spiacenti. Non capisco il tuo codice. Perché stai avvolgendo i metodi asincroni in un ciclo? Dovresti iniziare leggendo la gestione asincrona.

UDP garantisce solo che i messaggi completi siano ricevuti, nient'altro. Un messaggio può essere eliminato o inserito in un ordine errato. È necessario applicare il proprio algoritmo per gestirlo. Ce n'è uno chiamato Selective Repeat per esempio.

Avanti, non si dovrebbe elaborare ogni messaggio prima di ricevere una nuova, se vi aspettate di ricevere messaggi di un sacco di messaggi in un breve periodo di tempo. Invece, accodare ogni messaggio in arrivo e disporre di un thread separato che si occupa dell'elaborazione.

Terzo: i messaggi Udp devono essere elaborati con BeginReceiveFrom/EndReceiveFrom per l'elaborazione asincrona o ReceiveFrom per sincrono.

+0

Grazie per i tuoi suggerimenti. È in un ciclo perché voglio leggere più di un pacchetto. Quando si verifica l'evento asincrono, segnala l'handle di attesa per continuare e inizio a controllare il pacchetto successivo mentre elaboro quello corrente. Questa è una delle due tecniche che ho visto per un uso efficiente del processore (l'altra opzione consiste nel concatenare la chiamata BeginReceive nel callback stesso). Inoltre ho provato a sostituire il mio codice con BeginReceiveFrom/EndReceiveFrom ma ho avuto lo stesso problema. Avete suggerimenti sul codice di esempio? –

+0

Devo anche aggiungere che il problema si verifica con il codice del test di callback ridotto a nient'altro che l'incremento di un contatore in modo da elaborare il minimo. –

1

Come detto sopra, UDP non è un protocollo affidabile. È un protocollo senza connessione che appesantisce molto meno i pacchetti IP, rispetto a TCP. UDP è abbastanza buono per molte funzioni (inclusi i messaggi broadcast e multicast) ma non può essere utilizzato per la consegna affidabile dei messaggi. Se il buffer è sovraccarico, il driver di rete farà cadere i datagrammi. Se hai bisogno di comunicazioni basate sui messaggi con consegne affidabili o se ti aspetti di inviare molti messaggi, puoi controllare il nostro prodotto MsgConnect (disponibile versione open-source disponibile), che fornisce il trasferimento di dati basato su messaggi sia su socket che su UDP.

+0

Grazie, si prega di vedere la mia risposta di cui sopra in quanto non mi sembra che sto perdendo pacchetti secondo Wireshark. So che UDP non è affidabile, ma ottenere l'80% dei pacchetti abbandonati su una rete locale quando le altre app di test UDP funzionano correttamente mi dice che ho un problema con il mio codice. –

+0

@DanC: i pacchetti verranno eliminati dal driver di ricezione se non ci sono buffer in attesa di riceverli. Cioè hanno raggiunto il NIC, ma non la tua applicazione. – Richard

+0

Sì, come indicato da jgauffin sopra, non è necessario elaborare i messaggi immediatamente. Passali su un altro thread se hai bisogno di elaborare i messaggi al volo. Infine, ci possono essere alcuni problemi con BeginReceive in particolare, ma non conosco le specifiche delle operazioni UDP in questo scenario: utilizziamo le operazioni sincrone nel trasporto UDP di MsgConnect –

0

Provare un approccio più semplice. Avere il ricevitore eseguito in un thread separato che sarebbe simile a questo in pseudo codice:

do while socket_is_open??? 

    buffer as byte() 

    socket receive buffer 'use the blocking version 

    add buffer to queue of byte() 

loop 

In un altro anello di filo sulla dimensione della coda per determinare se avete ricevuto i pacchetti. Se hai ricevuto pacchetti per elaborarli, altrimenti dormi.

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

Grazie, terrò presente questo quando inizierò effettivamente a fare qualcosa con il buffer (anche se molto probabilmente userò il segnale invece del sonno poiché i dati possono arrivare in qualsiasi momento). Tuttavia, il mio esempio precedente sta letteralmente contando i pacchetti ricevuti, quindi il tempo di elaborazione non dovrebbe essere un fattore, dovrebbe? –

+0

Si noti che sto utilizzando la versione di blocco di ricezione, che blocca quel thread a meno che non ci siano dati. Quando ci sono dati, semplicemente li colloca in una coda che l'altro thread elabora. Il thread che elabora i dati deve avere un modo per abbandonare il controllo quando non ci sono dati da elaborare.Anni fa scrissi un'app UDP che testava la larghezza di banda usando il metodo descritto. Non ho avuto problemi nel testare gli switch FDX a 100 Mbps alla velocità del filo (200 Mbps). – dbasnett

0

Come altri hanno già detto, UDP non è un meccanismo di consegna garantito. COSÌ, anche se wireshark mostra che i pacchetti sono stati inviati, ciò non significa che i pacchetti siano stati ricevuti nella destinazione. Lo stack TCP/IP sull'host ricevente potrebbe ancora scartare i datagrammi.

È possibile confermare questo sta accadendo monitorando i seguenti contatori delle prestazioni in perfmon.exe.

computer locale \ IPv4 \ trasmissione di pacchetti Discarded

o

Computer locale \ IPv6 \ trasmissione di pacchetti Discarded

se si utilizza il protocollo IPv6.

Inoltre, è possibile provare a ridurre la velocità con cui si inviano i datagrammi e vedere se questo riduce la velocità degli scarti.

+0

Ho avuto Wireshark in esecuzione su entrambi i mittente e il destinatario. Mostrava tutti i pacchetti che stavano attraversando il cavo anche con la vecchia versione. Secondo System.Net.NetworkInformation.IPv4InterfaceStatistics, non sono stati eliminati pacchetti da entrambe le estremità. Non so come o da quale livello Wireshark ottiene i suoi dati, ma la mia conclusione è che in qualche modo il mio codice non stava tirando abbastanza velocemente dalla rete (anche se ero in un ciclo piuttosto stretto). Aumentare il Socket.ReceiveBufferSize sembrava darmi più tempo per assicurarmi che tutti i pacchetti fossero processati. Anche la riduzione della velocità di invio ha migliorato le cose. –

Problemi correlati