Visual Basic Simple
Dimostrazione del Nagle Algorithm
Sincronizza Indice
Sincronizza Indice
Scarica il progetto
Scarica il progetto
Scarica il testo dell'articolo
Testo dell'articolo
Stampa l'articolo
Stampa l'articolo
Ricerca personalizzata

Difficoltà: 2 / 5

Questo semplice esempio era stato scritto per dimostrare ad un amico l'azione del Nagle Algorithm, definito nella RFC 896, ma poiché la dimostrazione può interessare più di una persona ho deciso di renderlo pubblico all'interno di VBSimple.
Vedi anche TCP/IP Le risposte alle domande più frequenti.

Il TCP/IP possiede alcuni naturali 'mezzi di difesa' contro l'inondazione della rete con piccoli pacchetti. Tali difese sono maggiormente presenti all'invio dei dati da un punto di rete ad un altro. La principale di queste soluzioni di protezione è il Nagle Algorithm, un codice presente nello stack TCP/IP con lo scopo di ritardare l'invio di un nuovo pacchetto di dati fintanto che il TCP/IP non ha ricevuto l'ACK (Acknowledgment) per il pacchetto precedente.

Ciò determina che quando un programma prova ad inviare due o più pacchetti rapidamente, il primo viene inviato correttamente, qualunque sia la sua dimensione, mentre tutti i rimanenti pacchetti sono conservati e raggruppati in un unico pacchetto che sarà inviato soltanto dopo il ricevimento dell'ACK del primo pacchetto e comunque non prima di 200 millisecondi dall'invio del primo pacchetto.

Se ne deduce che non è possibile inviare ad un solo socket più di 5 pacchetti per secondo. Tutti gli eventuali pacchetti inviati tra una pausa e l'altra saranno raggruppati in uno solo e trasmessi allo scattare del momento giusto. Inoltre non sarà possibile inviare più di un piccolo pacchetto senza ACK.

Tale regola vale però soltanto per i pacchetti piccoli; tutti i pacchetti di dimensione uguale o maggiore a quella trasportabile con un singolo invio (costante API SO_SNDBUF - Vedi anche Trasferimento di files tra Client e Server) saranno inviati immediatamente, senza la normale attesa.

Vediamo un progettino molto semplice che dimostra quanto finora detto; un programma client si collegherà ad un programma server che dopo la connessione invierà una serie di numeri al client. Il client riprodurrà tutti i pacchetti di dati ricevuti in una ListBox e terrà il conto del numero di pacchetti ricevuti.

Figura 1Il server si compone di un form con una ListBoxdi nome Informazioni, un controllo Winsockdi nome Socket con la proprietà LocalPort impostata a 1500 e tre CommandButtondi nome InvioNormale, InvioDoEvents e InvioSleep e riproducono tre test che effettueremo per dimostrare l'azione dell'algoritmo. Il codice è altrettanto semplice:

  1. Option Explicit
  2. Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
  3. Private Sub Form_Load()
  4.     Socket.Listen
  5.     Me.Caption = "Attesa connessione..."
  6. End Sub

Alla riga 2 abbiamo dichiarato la Sub API Sleep che utilizzeremo per effettuare una pausa all'interno del nostro progetto. Sarà vista più avanti.

All'avvio del form il socket sarà messo in ascolto e sarà modificata la Caption del form.

  1. Private Sub Informazioni_DblClick()
  2.     Informazioni.Clear
  3. End Sub

Per ben valutare i dati del nostro esempio abbiamo aggiunto questa funzioncina che cancella il contenuto della ListBox Informazioni al doppio click dell'utente su di essa.

  1. Private Sub Socket_ConnectionRequest(ByVal requestID As Long)
  2.     Socket.Close
  3.     Socket.Accept requestID
  4.     Me.Caption = "Connessione accettata! Inviare i dati."
  5. End Sub

All'arrivo di una richiesta di connessione, essa sarà subito accettata e la Caption del form sarà modificata.

  1. Private Sub Socket_SendProgress(ByVal bytesSent As Long, ByVal bytesRemaining As Long)
  2.     Informazioni.AddItem bytesSent & " bytes inviati"
  3. End Sub

Tutti i pacchetti inviati tramite Socket saranno registrati nella ListBox Informazioni.

Le funzioni che seguono sono i nostri tre test di dimostrazione del Nagle Algorithm.

  1. Private Sub InvioNormale_Click()
  2.     Dim i As Integer
  3.     For i = 1 To 8192
  4.         Socket.SendData CStr(i)
  5.     Next i
  6. End Sub

Il primo è un invio normale, non controllato e senza alcuna attesa dei numeri compresi tra 1 8192, trasformati in stringa.

  1. Private Sub InvioDoEvents_Click()
  2.     Dim i As Integer
  3.     For i = 1 To 500
  4.         Socket.SendData CStr(i)
  5.         DoEvents
  6.     Next i
  7. End Sub

Il secondo test effettua l'invio dei numeri da 1 a 500 con l'utilizzo dell'istruzione DoEvents, da molti considerata la soluzione a tanti problemi.
Essa in realtà non risolve il problema ma forza il rilascio dei dati del Winsock e rallenta leggermente l'esecuzione del programma.

  1. Private Sub InvioSleep_Click()
  2.     Dim i As Integer
  3.     For i = 1 To 50
  4.         Socket.SendData CStr(i)
  5.         DoEvents
  6.         Sleep 201
  7.     Next i
  8. End Sub

Il terzo test invece è mirato ad evitare il Nagle Algorithm a patto di possedere una linea di trasferimento veloce. Infatti, dopo l'invio, confermato con l'istruzione DoEvents, tra ogni pacchetto forzeremo un'attesa di oltre 200 millisecondi facendo scattare il tempo di raccolta dei dati. L'attesa viene generata tramite chiamata della Sub API Sleep. Vedremo l'applicazione dei tre test più avanti, dopo aver analizzato il semplicissimo client che si collegherà al nostro server.


Figura 2Il programma si compone di un form con soli quattro controlli: una ListBoxdi nome Informazioni, un controllo Winsockdi nome Socket, un CommandButtondi nome Connetti ed una TextBoxdi nome IndirizzoIP.

Il funzionamento è assolutamente semplicissimo: l'utente inserisce l'indirizzo IP nella casella apposita e preme il pulsante Connetti. Il codice è altrettanto semplice:

  1. Option Explicit
  2. Private Sub Connetti_Click()
  3.     Socket.Connect IndirizzoIP.Text, 1500
  4.     Me.Caption = "0"
  5. End Sub

Alla pressione del pulsante Connetti il socket si collegherà all'host specificato nella TextBox IndirizzoIP sulla porta 1500 e la Caption del form segnerà il numero 0.

  1. Private Sub Informazioni_DblClick()
  2.     Informazioni.Clear
  3.     Me.Caption = "0"
  4. End Sub

Anche nel client il doppio click sulla ListBox Informazioni ne azzererà il contenuto e riporterà il conteggio nella Caption del form a 0.

  1. Private Sub Socket_DataArrival(ByVal bytesTotal As Long)
  2.     Dim DATI As String
  3.     Me.Caption = CStr(Val(Me.Caption) + 1)
  4.     Socket.GetData DATI
  5.     Informazioni.AddItem Now & " - " & DATI
  6. End Sub

All'arrivo di dati sul socket, il numero contenuto nella Caption sarà aumentato di 1, i dati in arrivo saranno estratti mediante il metodo GetData e saranno inseriti nella ListBox Informazioni assieme all'orario di arrivo.


Possiamo passare adesso alla dimostrazione pratica dell'azione del Nagle Algorithm.

Figura 3
Figura 3
Figura 4
Figura 4

Avviamo i due progetti (nel nostro caso sullo stesso computer, in modo da evitare tutti gli eventuali ritardi di trasferimento dei dati), inseriamo il numero IP del server nel nostro Client e premiamo il pulsante Connetti.

Se il server ed il client sono in esecuzione sullo stesso computer specificare l'indirizzo di loopback 127.0.0.1. Stabilita la connessione possiamo passare ai tre test.


 

Figura 5
Figura 5
Figura 6
Figura 6

Il primo test consiste nell'inviare i primi 8192 numeri senza effettuare alcuna pausa.
Possiamo notare che l'invio dei dati è fatto in un solo pacchetto; infatti sono inviati in un solo pacchetto 31661 bytes, eccedendo quindi il limite massimo di dati in un pacchetto.

Sul client infatti i dati non arrivano in un solo pacchetto, ma arrivano 4 pacchetti (vedi Caption del form) molto grossi nello stesso momento.

Il Nagle Algorithm non ha avuto applicazione poiché i dati sono stati inviati in un solo grande pacchetto, che poi il TCP/IP ha frammentato nel trasporto.


 

Figura 7
Figura 7
Figura 8
Figura 8

Il secondo test consiste nell'inviare al client i primi 500 numeri e richiamare dopo ogni invio l'istruzione DoEvents che permette al controllo Winsock di svolgere la sua azione di invio, che non ha potuto effettuare nel test precedente.

Così infatti accade che i dati dal server sono inviati in maniera regolare, ovvero byte per byte a secondo dell'ampiezza del numero (1 byte per i numeri compresi tra 1 e 9, 2 bytes per i numeri compresi tra 10 e 99, etc..).

Tuttavia, i dati ricevuti dal client sono totalmente differenti da quelli inviati. Possiamo notare un primo pacchetto di un solo byte e tanti altri pacchetti di più bytes. I 31661 bytes inviati sono stati separati in 28 pacchetti, di cui il primo costituito da un solo byte.

Tutto questo perché il Nagle Algorithm è entrato in azione. Esso infatti stabilisce che può essere inviato soltanto 1 pacchetto (di qualsiasi dimensione) senza che sia ritornato l'ACK di tale pacchetto. Pertanto, nell'attesa dell'ACK e del trascorrere dei 200 millisecondi, il buffer di dati da inviare si ingrandisce concatenando i numeri da inviare.

Possiamo anche notare, scrollando la lista, che non saranno inviati più di 5 pacchetti per secondo, proprio a causa della necessaria attesa di 200 millisecondi tra ogni pacchetto.


 

Figura 9
Figura 9
Figura 10
Figura 10

L'ultimo test servirà invece per evitare l'azione del Nagle Algorithm (almeno su linee veloci). Infatti tra un invio e l'altro sarà eseguita l'istruzione DoEvents che permette al socket di inviare i dati (come abbiamo visto nel test precedente) ed effettueremo anche una pausa di 201 millisecondi, per far scattare il limite di attesa minimo per ogni pacchetto.
Poiché il test è ovviamente più lento a causa delle pause forzate, i numeri che saranno inviati andranno da 1 a 50.

Il test, nel nostro esempio, ha inviato i dati nella maniera corretta. Il server ha inviato i dati byte per byte a seconda della dimensione del numero da trasferire. Il client ha ricevuto i dati nella maniera corretta. Sono arrivati infatti 50 pacchetti ed i numeri sono rappresentati nel formato originale, senza alcun raggruppamento.

Infatti nei 201 millisecondi di pausa l'ACK ha avuto il tempo di arrivare e non sono stati inviati più di 5 pacchetti per secondo; pertanto tutti i dati inviati sono arrivati nella maniera originale.

I tre test dimostrano le tre modalità di invio dei dati: consecutivamente, mediante DoEvents e con forzatura d'attesa.

Sebbene in molti casi l'utilizzo di un semplice DoEvents può bastare a regolare il flusso di dati tra client e server, in questo caso è palese che non è assolutamente sufficiente inserire un DoEvents per risolvere tutti i problemi di sincronizzazione.

È possibile disabilitare l'azione del Nagle Algorithm mediante l'utilizzo della funzione API setsockopt, combinata con la costante API TCP_NODELAY.

Questo esempio serviva soltanto a dimostrare l'esistenza ed il funzionamento dell'algoritmo di protezione dall'inondamento di dati nella rete e non a fornire un'efficiente soluzione per evitarlo.

Fibia FBI
4 Giugno 2001

Scarica il progetto
Scarica il progetto
Scarica il testo dell'articolo
Scarica il testo dell'articolo
Stampa l'articolo
Stampa l'articolo
Torna all'indice Client/Server