Guida all'utilizzo del simulatore di rete ns-3. Capitolo 4

Guida all'utilizzo del simulatore di rete ns-3. Capitolo 4
capitoli 1, 2
capitolo 3

4 Panoramica del concetto
4.1 Abstractions chiave
4.1.1 Nodo
4.1.2 Applicazione
4.1.3 Canale
4.1.4 Dispositivo di rete
4.1.5 Assistenti topologici
4.2 Primo script ns-3
4.2.1 Codice boilerplate
4.2.2 Moduli plug-in
4.2.3 Spazio dei nomi ns3
4.2.4 Registrazione
4.2.5 Funzione principale
4.2.6 Uso degli assistenti topologici
4.2.7 Uso dell'applicazione
4.2.8 Simulatore
4.2.9 Costruzione del tuo scenario
4.3 Codice sorgente di ns-3

Capitolo 4

Panoramica del concetto

Prima di iniziare a esplorare o scrivere codice ns-3, è fondamentale chiarire alcuni concetti e astrazioni base nel sistema. Molte di queste informazioni possono risultare ovvie per alcuni, ma si raccomanda di dedicare del tempo alla lettura di questa sezione per assicurarsi di partire su basi solide.

4.1 Abstractions chiave

In questa sezione, esamineremo alcuni termini comunemente usati nelle reti, ma che hanno un significato specifico in ns-3.

4.1.1 Nodo

In gergo informatico, un dispositivo collegato a una rete è chiamato host o talvolta sistema finale. Poiché ns-3 è un simulatore di rete e non un simulatore di Internet, evitiamo intenzionalmente l'uso del termine host, in quanto è strettamente legato a Internet e ai suoi protocolli. Invece, utilizziamo un termine più generale, utilizzato anche da altri simulatori, che ha origine nella teoria dei grafi: nodo (node).

In ns-3, l'astrazione di base di un dispositivo computazionale è chiamata nodo. Questa astrazione è rappresentata dalla classe C++ Node. La classe NodeNode (nodo) fornisce metodi per gestire le rappresentazioni dei dispositivi computazionali nelle simulazioni.

Dovete considerare Node come un computer al quale aggiungerete funzionalità. Aggiungerete elementi come applicazioni, stack di protocolli e schede periferiche con driver che consentono al computer di eseguire un lavoro utile. Utilizziamo lo stesso modello di base in ns-3.

4.1.2 Applicazione

In generale, il software per computer è suddiviso in due ampie categorie. Il software di sistema organizza diverse risorse informatiche come memoria, cicli della CPU, disco, rete, ecc., secondo un certo modello computazionale. Il software di sistema di solito non utilizza queste risorse per svolgere compiti che portano un beneficio diretto all'utente. L'utente avvia un'applicazione per raggiungere un obiettivo specifico, la quale accede e utilizza le risorse controllate dal software di sistema.

Spesso, la linea di demarcazione tra software di sistema e software applicativo è tracciata in corrispondenza del cambiamento del livello di privilegi, che avviene in seguito a chiamate di sistema nel sistema operativo. In ns‑3 non esiste una vera e propria concezione di sistema operativo e, di conseguenza, non ci sono concetti di livelli di privilegi o chiamate di sistema. Tuttavia, abbiamo l'idea dell'applicazione. Proprio come nel 'mondo reale', per svolgere compiti le applicazioni software operano su computer, le applicazioni ns‑3 funzionano sui nodi ns‑3 per gestire simulazioni nel mondo simulato.

In ns-3, l'astrazione di base per un programma utente che genera attività per la simulazione è l'applicazione. Questa astrazione è rappresentata in C++ dalla classe Application (applicazione). La classe Application fornisce metodi per gestire nelle simulazioni le rappresentazioni della nostra versione delle applicazioni a livello utente. Ci si aspetta che gli sviluppatori specializzino la classe Application in termini di programmazione orientata agli oggetti per creare nuove applicazioni. In questo guida, useremo le specializzazioni della classe Application chiamate UdpEchoClientApplication e UdpEchoServerApplication. Come ci si aspetterebbe, queste applicazioni costituiscono un insieme di applicazioni client/server utilizzate per generare e simulare l'eco dei pacchetti di rete.

4.1.3 Canale

Nel mondo reale, è possibile connettere un computer a una rete. Spesso, gli ambienti in cui vengono trasferiti i dati in queste reti sono chiamati canali. Quando colleghi un cavo Ethernet alla presa a muro, stai collegando il computer a un canale di comunicazione Ethernet. Nel mondo simulato di ns-3, un nodo si collega a un oggetto che rappresenta il canale di comunicazione. Qui, l'astrazione principale della sottorete di comunicazione è chiamata canale e viene rappresentata in C++ dalla classe Channel.

Classe ChannelChannel fornisce metodi per gestire l'interazione tra gli oggetti della sottorete e per collegarvi i nodi. I canali possono anche essere specializzati dagli sviluppatori in termini di programmazione orientata agli oggetti. La specializzazione di un canale può modellare qualcosa di semplice come un cavo. Un canale specializzato può anche modellare cose complesse come un grande switch Ethernet o uno spazio tridimensionale pieno di ostacoli nel caso di reti wireless.

In questa guida, utilizzeremo versioni specializzate del canale chiamate CsmaChannelCsmaChannel, PointToPointChannelPointToPointChannel e WifiChannelWifiChannel. CsmaChannel, ad esempio, simula una versione di una rete di comunicazione che realizza un ambiente di comunicazione a accesso multiplo con controllo della portante. Questo ci fornisce funzionalità simili a Ethernet.

4.1.4 Dispositivo di rete

In passato, se volevi collegare un computer a una rete, dovevi acquistare un cavo di rete specifico e un dispositivo hardware chiamato (in terminologia PC) scheda di interfaccia, che doveva essere installata nel computer. Se la scheda di interfaccia implementava alcune funzioni di rete, veniva chiamata scheda di rete. Oggi, la maggior parte dei computer è dotata di hardware di interfaccia di rete integrato, e gli utenti non li vedono come dispositivi separati.

Una scheda di rete non funzionerà senza un driver software che gestisca il suo hardware. In Unix (o Linux), parte dell'hardware periferico è classificata come dispositivo. I dispositivi vengono gestiti tramite driver di dispositivo, mentre i dispositivi di rete (NIC) sono gestiti utilizzando driver di dispositivi di rete (driver di dispositivi di rete) e hanno un nome collettivo di dispositivi di rete (dispositivi di rete). In Unix e Linux, si accede ai dispositivi di rete con nomi come ad esempio eth0.

In ns-3, l'astrazione del dispositivo di rete comprende sia il driver software che l'hardware simulato. Durante la simulazione, il dispositivo di rete è "installato" nel nodo per consentirgli di comunicare con altri nodi attraverso i canali. Come in un computer reale, un nodo può essere connesso a più canali tramite diversi dispositivi. NetDevices.

L'astrazione del dispositivo di rete è rappresentata nel C++ dalla classe NetDevice. La classe NetDevice fornisce metodi per gestire le connessioni con gli oggetti Node e Channel; e può essere specializzata dai programmatori in termini di programmazione orientata agli oggetti. In questa guida utilizzeremo diverse versioni specializzate di NetDevice denominate CsmaNetDevice, PointToPointNetDevice e WifiNetDevice. Proprio come l'adattatore di rete Ethernet è progettato per lavorare con la rete Ethernet, CsmaNetDevice è progettato per lavorare con CsmaChannel, PointToPointNetDevice è progettato per lavorare con PointToPointChannel, e WifiNetDevice — è progettato per lavorare con WifiChannel.

4.1.5 Assistenti topologici

Nella rete reale troverete computer host con schede di rete aggiuntive (o integrate). In ns-3 diremmo che vedrete nodi con NetDevices connessi. In una rete simulata di grande dimensione, sarà necessario organizzare le connessioni tra molti oggetti. Node, NetDevice e Canale.

Poiché la connessione dei NetDevices ai nodi, dei NetDevices ai canali, l'assegnazione degli indirizzi IP, ecc. in ns-3 è un compito comune, per semplificare il processo forniamo quelli che chiamiamo assistenti topologici. Ad esempio, per creare un NetDevice bisogna eseguire numerose operazioni nel kernel di ns-3: aggiungere un indirizzo MAC, configurare questo dispositivo di rete in un nodo, impostare lo stack di protocollo del nodo e poi collegare il NetDevice al Channel. Saranno necessarie ulteriori operazioni per collegare più dispositivi a canali multipoint e poi connettere reti separate in una rete integrata (Internetworks). Forniamo oggetti ausiliari di topologia che, per comodità, uniscono queste numerose operazioni in un modello facile da usare.

4.2 Primo script ns-3

Se hai installato il sistema come suggerito sopra, avrai una release di ns-3 nella directory chiamata repos nella tua home directory. Vai nella directory release

Se non hai questa directory, significa che non hai specificato la directory di output durante la costruzione della versione di rilascio di ns-3. Esegui la costruzione in questo modo:
$ ./waf configure —build-profile=release —out=build/release,
$ ./waf build

dovresti vedere una struttura di directory simile alla seguente:

AUTHORS       examples      scratch       utils       waf.bat*
bindings      LICENSE       src           utils.py    waf-tools
build         ns3           test.py*      utils.pyc   wscript
CHANGES.html  README        testpy-output VERSION     wutils.py
doc           RELEASE_NOTES testpy.supp   waf*        wutils.pyc

Vai nella directory examples/tutorial. Dovresti vedere un file lì denominato first.cc. Questo è uno script che creerà una semplice connessione punto-punto tra due nodi e trasmetterà un pacchetto tra i nodi. Vediamo questo script riga per riga, quindi apriamo first.cc nel tuo editor preferito.

4.2.1 Codice boilerplate
La prima riga nel file è la riga della modalità dell'editor emacs. Essa informa emacs sulle convenzioni di formattazione (stile di codifica) che utilizzeremo nel nostro codice sorgente.

/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */

Questa è sempre una questione piuttosto controversa, quindi dobbiamo fare chiarezza per toglierla subito di mezzo. Il progetto ns-3, come la maggior parte dei grandi progetti, ha adottato uno stile di codifica al quale deve conformarsi tutto il codice fornito. Se desideri contribuire con il tuo codice al progetto, alla fine dovrai rispettare lo standard di codifica ns-3, come descritto nel file doc/codingstd.txt o mostrato nella pagina del progetto: https://www.nsnam.org/develop/contributing-code/coding-style/.

Ti consigliamo di abituarti all'aspetto del codice ns-3 e di applicare questo standard ogni volta che lavori con il nostro codice. L'intero team di sviluppatori e collaboratori ha concordato su questo dopo un po' di lamentele. La riga di modalità emacs riportata sopra semplifica il corretto formattamento se utilizzi l'editor emacs.

Il simulatore ns-3 è rilasciato con la licenza GNU General Public License. Vedrai l'intestazione legale GNU pertinente in ogni file della distribuzione ns-3. Spesso puoi vedere l'avviso di copyright per una delle istituzioni partecipanti al progetto ns-3 sopra al testo GPL e all'autore, come mostrato di seguito.

/* 
* This program is free software; you can redistribute it and/or modify 
* it under the terms of the GNU General Public License version 2 as 
* published by the Free Software Foundation; 
*
* This program is distributed in the hope that it will be useful, 
* but WITHOUT ANY WARRANTY; without even the implied warranty of 
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
* GNU General Public License for more details. 
* 
* You should have received a copy of the GNU General Public License 
* along with this program; if not, write to the Free Software 
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
*/

4.2.2 Moduli plug-in

Il codice stesso inizia con una serie di istruzioni di inclusione (include).

#include "ns3/core-module.h"
#include "ns3/network-module.h"
#include "ns3/internet-module.h"
#include "ns3/point-to-point-module.h"
#include "ns3/applications-module.h"

Per assistere i nostri utenti di script di alto livello nella gestione di un gran numero di file header presenti nel sistema, li raggruppiamo in base al loro utilizzo in moduli più grandi. Forniamo un singolo file header che caricherà ricorsivamente tutti i file header utilizzati in quel modulo. Invece di cercare quale header specifico ti serve e rischiare di ottenere un elenco corretto delle dipendenze, ti diamo la possibilità di caricare un gruppo di file con un alto grado di dettaglio. Questo non è il metodo più efficiente, ma sicuramente rende la scrittura degli script molto più semplice.

Ognuno dei file inclusi di ns-3 viene collocato in una directory chiamata ns3 (sottodirectory di build), per evitare conflitti nei nomi dei file durante il processo di costruzione. Il file ns3/core-module.h corrisponde al modulo ns-3 che troverai nella directory src/core nella versione che hai installato. Nella lista di questa directory troverai un gran numero di file header. Quando esegui la build, Waf inserisce i file di intestazione pubblici nella directory ns3 nella sottodirectory build/debug

Se non hai questa directory, significa che non hai specificato la directory di output durante la costruzione della versione di rilascio di ns-3. Esegui la costruzione in questo modo:
$ ./waf configure —build-profile=debug —out=build/debug
$ ./waf build
o
$ ./waf configure —build-profile=optimized —out=build/optimized
$ ./waf build

o build/optimized, a seconda della tua configurazione. Waf genererà anche automaticamente un file di intestazione del modulo per caricare tutti i file di intestazione pubblici. Poiché segui rigorosamente questa guida, hai già fatto

$ ./waf -d debug --enable-examples --enable-tests configure

per configurare il progetto per eseguire build di debug che includono esempi e test. Hai anche fatto

$ ./waf

per compilare il progetto. Quindi ora, quando guardi nella directory ../..//build/debug/ns3, troverai lì, tra le altre cose, i file di intestazione di quattro moduli mostrati sopra. Puoi dare un’occhiata al contenuto di questi file e scoprire che includono tutti i file pubblici utilizzati dai rispettivi moduli.

4.2.3 Spazio dei nomi ns3

La prossima riga nello script first.cc è l'annuncio dello spazio dei nomi.

using namespace ns3;

Il progetto ns‑3 è implementato nello spazio dei nomi C++, chiamato ns3. Questo raggruppa tutte le dichiarazioni relative a ns‑3 nell'ambito di visibilità al di fuori dello spazio dei nomi globale, il che, speriamo, faciliterà l'integrazione con altro codice. Usare l'operatore C++ introduce lo spazio dei nomi ns‑3 nella regione dichiarativa attuale (globale). In altre parole, dopo questa dichiarazione, non sarà più necessario inserire l'operatore di risoluzione ns3:: prima di ogni codice ns‑3 per utilizzarlo. Se non sei familiare con gli spazi dei nomi, fai riferimento a quasi qualsiasi manuale di C++ e confronta lo spazio dei nomi ns3 con l'uso dello spazio dei nomi std e le dichiarazioni. using namespace std; negli esempi che utilizzano l'operatore di output cout e flussi.

4.2.4 Registrazione

La prossima riga dello script è la seguente:

NS_LOG_COMPONENT_DEFINE ("FirstScriptExample");

Utilizzeremo questa affermazione come un comodo punto di partenza per discutere il nostro sistema di documentazione. Doxygen. Se guardi il sito web del progetto ns‑3, troverai un link alla «Documentazione» (Documentation) nel pannello di navigazione. Se selezioni questo link, verrai indirizzato alla nostra pagina di documentazione. C'è un collegamento alla «Ultima versione», che ti porterà alla documentazione per l'ultima versione stabile di ns‑3. Se selezioni il link «API Documentation», accederai alla pagina della documentazione API di ns‑3.

Sul lato sinistro della pagina troverai una rappresentazione grafica della struttura della documentazione. Un buon punto di partenza è il «libro» Modules ns‑3 nell'albero di navigazione di ns‑3. Se espandi Moduli, vedrai un elenco della documentazione dei moduli ns‑3. Come discusso in precedenza, il concetto di modulo qui è direttamente correlato ai file inclusi nel modulo sopra. Il sottosistema di registrazione (logging) di ns‑3 è trattato nella sezione Uso del modulo di registrazione, quindi ci torneremo più tardi in questa guida, ma puoi scoprire di cosa tratta l'affermazione precedente guardando il modulo Core, quindi aprendo il libro Strumenti di debug, e poi selezionando la pagina Logging. Clicca su Logging.

Ora dovresti visualizzare la documentazione Doxygen per il modulo Logging. Nella lista dei macro nella parte superiore della pagina vedrete un'entrata per NS_LOG_COMPONENT_DEFINE. Prima di seguire il link, assicuratevi di consultare la "Descrizione dettagliata" del modulo di registrazione per comprendere il suo funzionamento nel complesso. Per farlo, potete scorrere verso il basso o selezionare "More…" sotto il diagramma.

Una volta che avete una comprensione generale di ciò che sta accadendo, continuate e consultate la documentazione specifica per NS_LOG_COMPONENT_DEFINE. Non ripeterò la documentazione qui, ma per riassumere, dico che questa riga dichiara un componente di registrazione chiamato FirstScriptExample, che consente di attivare e disattivare la registrazione dei messaggi nella console in base al nome.

4.2.5 Funzione principale

Nelle righe successive dello script vedrete,

int 
main (int argc, char *argv[])
{ 

Questo è semplicemente la dichiarazione della funzione principale del vostro programma (script). Come in qualsiasi programma C++, dovete definire la funzione principale, che viene eseguita per prima. Non c'è nulla di speciale qui. Il vostro script ns-3 è semplicemente un programma C++. La seguente riga imposta la risoluzione temporale uguale a 1 nanosecondo, che è il valore predefinito:

Time::SetResolution (Time::NS);

La risoluzione temporale o semplicemente la risoluzione è il valore di tempo minimo che può essere utilizzato (la minima differenza rappresentabile tra due valori di tempo). Puoi modificare la risoluzione solo una volta. Il meccanismo che fornisce questa flessibilità consuma memoria, quindi, una volta che la risoluzione è stata impostata in modo esplicito, liberiamo la memoria, impedendo ulteriori aggiornamenti. (Se non imposti esplicitamente la risoluzione, per impostazione predefinita sarà di un nanosecondo e la memoria sarà liberata all'inizio della simulazione.)

Le seguenti due righe di script vengono utilizzate per abilitare due componenti di logging incorporati nelle applicazioni EchoClient e EchoServer:

LogComponentEnable("UdpEchoClientApplication", LOG_LEVEL_INFO); LogComponentEnable("UdpEchoServerApplication", LOG_LEVEL_INFO);

Se hai letto la documentazione sul componente Logging, vedrai che ci sono diversi livelli di dettaglio della registrazione che puoi abilitare per ciascun componente. Queste due righe di codice abilitano i log di debug a livello INFO per i client e i server Echo. A questo livello, l'applicazione stamperà messaggi durante la simulazione quando invia e riceve pacchetti.

Ora passiamo direttamente alla creazione della topologia e all'avvio della simulazione. Utilizzeremo oggetti di assistenza topologica per semplificare il lavoro.

4.2.6 Uso degli assistenti topologici

Le seguenti due righe di codice nel nostro script creeranno effettivamente oggetti Node ns‑3 che rappresenteranno i computer nella simulazione.

NodeContainer nodes;
nodes.Create(2);

Prima di procedere, troviamo la documentazione per la classe NodeContainer. Un altro modo per accedere alla documentazione per questa classe è attraverso la scheda Classi nelle pagine Doxygen. Se hai già aperto Doxygen, scorri verso l'alto fino alla parte superiore della pagina e seleziona la scheda Classi. Dovresti vedere un nuovo set di schede, una delle quali è l'elenco delle classi. Sotto questa scheda vedrai l'elenco di tutte le classi ns-3. Scorri verso il basso, fino a ns3::NodeContainer. Quando trovi la classe, selezionala per accedere alla documentazione per quella classe.

Come ricordiamo, una delle nostre astrazioni chiave è il nodo. Questo rappresenta un computer a cui aggiungeremo elementi come stack di protocolli, applicazioni e schede periferiche. L'assistente topologico NodeContainer fornisce un modo conveniente per creare, gestire e accedere a qualsiasi oggetto Node, che creiamo per eseguire una simulazione. La prima riga sopra dichiara NodeContainer, che chiamiamo nodi. La seconda riga chiama il metodo Create per l'oggetto nodi e chiede al contenitore di creare due nodi. Come descritto in Doxygen, il contenitore richiede al sistema ns-3 di creare due oggetti Node e memorizza i puntatori a questi oggetti al suo interno.

I nodi creati nello script non fanno niente per ora. Il passo successivo nella costruzione della topologia è collegare i nostri nodi alla rete. La forma più semplice di rete che supportiamo è una connessione punto a punto tra due nodi. Ora creeremo questa connessione.

PointToPointHelper

Stiamo creando una connessione punto a punto seguendo uno schema a noi familiare, utilizzando un oggetto ausiliario topologico per eseguire il lavoro a basso livello necessario per il collegamento. Ricordiamo che le nostre due astrattistiche chiave sono NetDevice e Canale. Nel mondo reale, questi termini corrispondono approssimativamente alle schede periferiche e ai cavi di rete. In genere, queste due cose sono strettamente correlate l'una all'altra, e nessuno può contare su uno scambio, ad esempio, tra dispositivi Ethernet tramite un canale wireless. I nostri helper topologici seguono questo legame stretto e quindi utilizzerai in questo scenario un oggetto PointToPointHelper per configurare e connettere gli oggetti ns-3 PointToPointNetDevice e PointToPointChannel. Le tre righe successive nello script:

PointToPointHelper pointToPoint;
pointToPoint.SetDeviceAttribute ("DataRate", StringValue ("5Mbps")); 
pointToPoint.SetChannelAttribute ("Delay", StringValue ("2ms"));

La prima riga,

PointToPointHelper pointToPoint;

crea un'istanza di un oggetto nello stack PointToPointHelper. Dal punto di vista di alto livello, la riga successiva

pointToPoint.SetDeviceAttribute ("DataRate", StringValue ("5Mbps"));

informa l'oggetto PointToPointHelper di utilizzare il valore "5 Mbps" (cinque megabit al secondo) come "DataRate».

Da un punto di vista più specifico, la riga "DataRate" corrisponde a quello che chiamiamo un attributo PointToPointNetDevice. Se guardi a Doxygen per la classe ns3::PointToPointNetDevice e nella documentazione del metodo GetTypeId troverai un elenco di attributi definiti per il dispositivo. Tra questi ci sarà l'attributo "DataRate". La maggior parte degli oggetti visibili all'utente di ns-3 ha elenchi di attributi simili. Utilizziamo questo meccanismo per una semplice personalizzazione della simulazione senza ricompilazione, come vedrai nella sezione successiva.

Simile a "DataRate" nel PointToPointNetDevice, troverai l'attributo "Delay" associato a PointToPointChannel. La riga finale,

pointToPoint.SetChannelAttribute ("Delay", StringValue ("2ms"));

afferma PointToPointHelper utilizza il valore "2 ms" (due millisecondi) come valore per il ritardo di propagazione attraverso il canale punto-punto che crea successivamente.

NetDeviceContainer

Fino a questo momento, nel nostro scenario abbiamo NodeContainer, che contiene due nodi. Abbiamo PointToPointHelper, che è pronto per creare oggetti DispositiviNetPointToPoint e la loro connessione utilizzando l'oggetto PointToPointChannel. Proprio come abbiamo utilizzato l'oggetto ausiliario di topologia NodeContainer per creare i nodi, chiederemo PointToPointHelper di svolgere per noi il lavoro relativo alla creazione, configurazione e installazione dei nostri dispositivi. Avremo bisogno di un elenco di tutti gli oggetti creati NetDevice, quindi utilizziamo NetDeviceContainer per la loro conservazione così come abbiamo utilizzato NodeContainer per conservare i nodi creati da noi. Le seguenti due righe di codice,

NetDeviceContainer dispositivi;
dispositivi = pointToPoint.Install (nodi);

concludono la configurazione dei dispositivi e del canale. La prima riga dichiara il contenitore dei dispositivi, menzionato sopra, mentre la seconda esegue il lavoro principale. Il metodo Installa dell'area PointToPointHelper accetta NodeContainer come parametro. All'interno NetDeviceContainer per ogni nodo presente in NodeContainer viene creato (per la connessione punto-punto devono essercene esattamente due) PointToPointNetDevice creato e conservato nel contenitore dei dispositivi. PointToPointChannel creato, e a esso si uniscono due DispositiviNetPointToPoint. Dopo la creazione degli oggetti, gli attributi conservati in PointToPointHelper, vengono utilizzati per inizializzare gli attributi corrispondenti negli oggetti creati.

Dopo aver eseguito la chiamata pointToPoint.Install (nodi) avremo due nodi, ciascuno con un dispositivo di rete "point-to-point" installato e un canale "point-to-point" tra di loro. Entrambi i dispositivi saranno configurati per trasmettere dati a una velocità di cinque megabit al secondo con un ritardo di due millisecondi nel canale.

InternetStackHelper

Ora abbiamo configurato i nodi e i dispositivi, ma sugli operatori non sono stati installati gli stack di protocollo. Le seguenti due righe di codice si occuperanno di questo.

InternetStackHelper stack;
stack.Install(nodes);

InternetStackHelper è un helper topologico per gli stack Internet, simile a PointToPointHelper per dispositivi di rete a punto a punto. Il metodo Installa accetta NodeContainer come parametro. Quando viene eseguito, installerà lo stack Internet (TCP, UDP, IP, ecc.) su ogni nodo del contenitore.

Ipv4AddressHelper

Successivamente, dobbiamo associare i nostri dispositivi agli indirizzi IP. Forniamo un assistente topologico per gestire la distribuzione degli indirizzi IP. L'unica API visibile all'utente è la configurazione dell'indirizzo IP di base e della maschera di rete da utilizzare durante la distribuzione effettiva degli indirizzi (questo viene effettuato a un livello più basso all'interno dell'assistente). Le seguenti due righe di codice nel nostro esempio di script first.cc,

Ipv4AddressHelper address;
address.SetBase ("10.1.1.0", "255.255.255.0");

dichiarano un oggetto ausiliario dell'indirizzo e indicano che deve iniziare ad allocare indirizzi IP dalla rete 10.1.1.0, utilizzando la maschera 255.255.255.0 per la definizione. Per impostazione predefinita, gli indirizzi allocati inizieranno da uno e aumenteranno monotonamente, quindi il primo indirizzo allocato da questa base sarà 10.1.1.1, poi 10.1.1.2 e così via. In realtà, a un livello più basso, il sistema ns-3 memorizza tutti gli indirizzi IP allocati e genera un errore fatale se si crea accidentalmente una situazione in cui lo stesso indirizzo viene generato due volte (tra l'altro, questo errore è difficile da debugare).

La riga di codice successiva,

Ipv4InterfaceContainer interfaces = address.Assign (devices);

esegue l'assegnazione effettiva dell'indirizzo. In ns‑3 colleghiamo l'indirizzo IP al dispositivo utilizzando l'oggetto Ipv4Interface. Così come a volte abbiamo bisogno di un elenco di dispositivi di rete creati dall'assistente per un uso successivo, a volte abbiamo bisogno di un elenco di oggetti Ipv4Interface. Ipv4InterfaceContainer che fornisce questa funzionalità.

Abbiamo costruito una rete punto-punto, con stack impostati e indirizzi IP assegnati. Ora abbiamo bisogno in ciascun nodo di un'applicazione per generare traffico.

4.2.7 Uso dell'applicazione

Un'altra delle principali astrazioni del sistema ns‑3 è Application (applicazione). In questo scenario utilizziamo due specializzazioni della classe base Application ns‑3 chiamate UdpEchoServerApplication e UdpEchoClientApplication. Come nei casi precedenti, utilizziamo oggetti helper per configurare e gestire gli oggetti di base. Qui utilizziamo UdpEchoServerHelper e UdpEchoClientHelperper semplificare la nostra vita.

UdpEchoServerHelper

Le seguenti righe di codice nel nostro esempio di script first.cc vengono utilizzate per configurare l'applicazione UDP echo server su uno dei nodi che abbiamo creato in precedenza.

UdpEchoServerHelper echoServer (9);

ApplicationContainer serverApps = echoServer.Install (nodes.Get (1));
serverApps.Start (Seconds (1.0));
serverApps.Stop (Seconds (10.0));

La prima riga di codice nel frammento sopra crea UdpEchoServerHelper. Come al solito, non è un'applicazione autonoma, è un oggetto che ci aiuta a costruire applicazioni reali. Uno dei nostri accordi è di passare gli attributi necessari al costruttore dell'oggetto ausiliario (helper). In questo caso, l'helper non può fare nulla di utile se non gli viene fornito il numero della porta su cui il server attenderà i pacchetti, questo numero deve essere noto anche al cliente. In questo caso passiamo al costruttore dell'helper il numero della porta. Il costruttore, a sua volta, esegue semplicemente SetAttribute con il valore fornito. Più tardi, se lo desideri, usando SetAttribute potrai impostare un altro valore per l'attributo "Port".

Come molti altri oggetti ausiliari, l'oggetto UdpEchoServerHelper ha un metodo Installa. L'esecuzione di questo metodo crea effettivamente un'applicazione di base per un echo server e la lega a un nodo. È interessante notare che il metodo Installa accetta NodeContainter come parametro, così come altri Installa metodi che abbiamo visto.

Una conversione implicita in C++ che funziona qui accetta il risultato del metodo node.Get (1) (che restituisce un puntatore intelligente all'oggetto nodo — Ptr ) e lo utilizza nel costruttore per un oggetto anonimo NodeContainer, che poi viene passato al metodo Installa. Se non riesci a determinare nel codice C++ quale metodo con quale firma viene compilato e eseguito, cerca tra le conversioni implicite.

Ora vediamo che echoServer.Install si appresta a installare l'applicazione UdpEchoServerApplication sul nodo trovato in NodeContainer, che usiamo per gestire i nostri nodi, nodo con indice 1. Il metodo Installa restituirà un contenitore che contiene puntatori a tutte le applicazioni (in questo caso una, poiché abbiamo passato un anonimo NodeContainer, contenente un nodo) creato dall'assistente.

Le applicazioni devono specificare il momento di avvio della generazione del traffico «start» e potrebbe essere necessario specificare anche quando fermarlo «stop». Forniamo entrambi i parametri. Questi momenti vengono impostati tramite i metodi ApplicationContainer Inizia e Stop. Questi metodi accettano parametri di tipo Tempo. In questo caso utilizziamo una sequenza esplicita di conversioni C++, per prendere C++ double 1.0 e convertirlo in un oggetto tns‑3 Time, utilizzando l'oggetto Seconds per la conversione in secondi. Ricorda che le regole di conversione possono essere controllate dall'autore del modello e che C++ ha le sue regole, quindi non puoi sempre contare sul fatto che i parametri verranno convertiti come ti aspetti. Due righe,

serverApps.Start (Seconds (1.0));
serverApps.Stop (Seconds (10.0));

farà sì che l'applicazione del server echo venga avviata (si accenda automaticamente) un secondo dopo l'inizio della simulazione e si fermi (si spenga) dopo dieci secondi di simulazione. Poiché abbiamo dichiarato un evento di simulazione (evento di arresto dell'applicazione), che verrà eseguito dopo dieci secondi, verranno simulati almeno dieci secondi di funzionamento della rete.

UdpEchoClientHelper

L'applicazione client echo è configurata in modo praticamente analogo al server. Esiste un oggetto di base UdpEchoClientApplication, gestito da
UdpEchoClientHelper.

UdpEchoClientHelper echoClient (interfaces.GetAddress (1), 9);
echoClient.SetAttribute ("MaxPackets", UintegerValue (1));
echoClient.SetAttribute ("Interval", TimeValue (Seconds (1.0)));
echoClient.SetAttribute ("PacketSize", UintegerValue (1024));

ApplicationContainer clientApps = echoClient.Install (nodes.Get (0));
clientApps.Start (Seconds (2.0));
clientApps.Stop (Seconds (10.0));

Tuttavia, per il client echo abbiamo bisogno di impostare cinque diversi attributi. I primi due attributi vengono impostati al momento della creazione. UdpEchoClientHelperTrasmettiamo parametri che vengono utilizzati (all'interno dell'assistente) per impostare gli attributi. «RemoteAddress» e «RemotePort» in conformità con il nostro accordo sulla trasmissione dei parametri necessari al costruttore dell'assistente.

Ricordiamo che abbiamo utilizzato Ipv4InterfaceContainer per monitorare gli indirizzi IP che abbiamo assegnato ai nostri dispositivi. L'interfaccia zero nel contenitore delle interfacce corrisponderà all'indirizzo IP del nodo zero nel contenitore dei nodi. La prima interfaccia nel contenitore delle interfacce corrisponde all'indirizzo IP del primo nodo nel contenitore dei nodi. Quindi, nella prima riga di codice (in cima) creiamo l'assistente e gli diciamo che l'indirizzo remoto del client sarà l'indirizzo IP assegnato al nodo su cui si trova il server. Diciamo anche che i pacchetti devono essere inviati alla nona porta.

L'attributo «MaxPackets» informa il cliente sul numero massimo di pacchetti che possiamo inviare durante la simulazione. L'attributo «Interval» informa il cliente su quanto tempo aspettare tra i pacchetti, e l'attributo «PacketSize» indica la dimensione del carico utile del pacchetto. Con questa combinazione di attributi, informiamo il cliente di inviare un pacchetto di 1024 byte.

Come nel caso del server echo, impostiamo gli attributi per il client echo Inizia e Stop, ma qui avviamo il client un secondo dopo l'accensione del server (due secondi dopo l'inizio della simulazione).

4.2.8 Simulatore

A questo punto dobbiamo avviare la simulazione. Questo si realizza attraverso una funzione globale Simulator::Run.

Simulator::Run ();

Quando in precedenza abbiamo chiamato i metodi,

serverApps.Start (Seconds (1.0));
serverApps.Stop (Seconds (10.0));
...
clientApps.Start (Seconds (2.0));
clientApps.Stop (Seconds (10.0));

abbiamo effettivamente programmato eventi nel simulatore per 1,0 secondi, 2,0 secondi e due eventi per 10,0 secondi. Dopo la chiamata Simulator::Run, il sistema inizierà a esaminare l'elenco degli eventi programmati e a eseguirli. Prima eseguirà l'evento dopo 1,0 secondi, attivando l'applicazione del server echo (questo evento può a sua volta pianificare molti altri eventi). Successivamente, eseguirà l'evento programmato per t = 2,0 secondi, che avvierà l'applicazione del client echo. Ancora una volta, questo evento può pianificare ulteriori eventi. L'implementazione dell'evento di avvio nel client echo inizierà la fase di trasferimento dei dati di simulazione, inviando un pacchetto al server.

L'atto di invio del pacchetto al server triggerà una catena di eventi, che verranno automaticamente programmati dietro le quinte e realizzeranno la meccanica di invio dei pacchetti di echo in base ai parametri di sincronizzazione che abbiamo impostato nello script.

Di conseguenza, poiché inviamo solo un pacchetto (ricordiamo che l'attributo MaxPackets è stato impostato su uno), la catena di eventi iniziata da questa unica richiesta echo del client si concluderà e la simulazione entrerà in modalità attesa. Una volta accaduto, gli eventi rimanenti programmati saranno eventi Stop per il server e il client. Quando questi eventi si verificano, non ci saranno eventi da elaborare ulteriormente e Simulator::Run restituisce il controllo. La simulazione è completata.

Resta solo da riordinare. Questo viene fatto chiamando la funzione globale Simulator::Destroy. Poiché sono state chiamate funzioni di supporto (o codice a basso livello di ns‑3), organizzate in modo tale che nel simulatore venga inserito un gancio per distruggere tutti gli oggetti creati. Non è necessario tenere traccia di nessuno di questi oggetti da solo: tutto ciò che dovevi fare era chiamare Simulator::Destroy e uscire. Il sistema ns‑3 farà questo lavoro difficile per te. Le rimanenti righe del nostro primo script ns‑3, first.cc, fanno proprio questo:

Simulator::Destroy ();
return 0;
}

Quando si ferma il simulatore?

ns‑3 è un simulatore di eventi discreti (DE). In un tale simulatore, ogni evento è associato al momento della sua esecuzione, e la simulazione continua elaborando gli eventi nell'ordine in cui si verificano durante la simulazione. Gli eventi possono causare la pianificazione di eventi futuri (ad esempio, un timer può ripianificare se stesso per completare il conteggio nel prossimo intervallo).

Gli eventi iniziali sono generalmente attivati da un oggetto, ad esempio, IPv6 pianificherà la scoperta dei servizi nella rete, le richieste dei vicini, ecc. L'applicazione pianifica il primo evento di invio del pacchetto, ecc. Quando l'evento viene elaborato, può generare zero, uno o più eventi. Man mano che la simulazione procede, gli eventi accadono, terminando semplicemente o generando nuovi eventi. La simulazione si fermerà automaticamente se la coda degli eventi risulta vuota o se viene rilevato un evento speciale. StopL'evento Stop è generato dalla funzione Simulator::Stop (ferma il tempo).

C'è un caso tipico in cui Simulator::Stop è assolutamente necessario per fermare la simulazione: quando ci sono eventi auto-sostenuti. Gli eventi auto-sostenuti (o ripetuti) sono eventi che vengono sempre riprogrammati. Di conseguenza, mantengono sempre la coda degli eventi non vuota. Ci sono molti protocolli e moduli che includono eventi ripetuti, ad esempio:

• FlowMonitor — controllo periodico dei pacchetti persi;

• RIPng — trasmissione periodica dell'aggiornamento delle tabelle di routing;

• ecc.

In tali casi Simulator::Stop è necessario per fermare correttamente la simulazione. Inoltre, quando ns-3 è in modalità emulazione, RealtimeSimulator viene utilizzato per sincronizzare gli orologi della simulazione con quelli della macchina, e Simulator::Stop è necessario per fermare il processo.

Molti dei programmi di simulazione nel manuale non invocano Simulator::Stop esplicitamente, poiché terminano automaticamente con l'esaurimento degli eventi in coda. Tuttavia, questi programmi accetteranno anche una chiamata a Simulator::Stop. Ad esempio, il seguente operatore aggiuntivo nel primo esempio di programma pianificherà una fermata esplicita all'undicesimo secondo:

+ Simulator::Stop (Seconds (11.0));
  Simulator::Run ();
  Simulator::Destroy ();
  return 0;
}

Quanto sopra non modificherà effettivamente il comportamento di questo programma, poiché questa specifica simulazione termina naturalmente dopo 10 secondi. Ma se avessi modificato il tempo di fermata nell'operatore sopra da 11 secondi a 1 secondo, noteresti che la simulazione si ferma prima che qualsiasi output venga visualizzato (poiché l'output avviene circa dopo 2 secondi di tempo di simulazione).

È importante chiamare Simulator::Stop prima di chiamare Simulator::Run; altrimenti, Simulator::Run potrebbe non restituire mai il controllo al programma principale per eseguire l'arresto!

4.2.9 Costruzione del tuo scenario

Abbiamo reso la creazione dei tuoi semplici script banale. Tutto ciò che devi fare è posizionare il tuo script nella cartella scratch e verrà automaticamente compilato quando lo esegui. Waf. Proviamo. Torna nella cartella principale e copia examples/tutorial/first.cc nella directory scratch

$ cd ../.. 
$ cp examples/tutorial/first.cc scratch/myfirst.cc

Ora compila il tuo primo esempio di script usando waf:

$ ./waf

Dovresti vedere messaggi che indicano che il tuo primo esempio è stato creato con successo.

Waf: Entrando nella directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
[614/708] cxx: scratch/myfirst.cc -> build/debug/scratch/myfirst_3.o
[706/708] cxx_link: build/debug/scratch/myfirst_3.o -> build/debug/scratch/myfirst
Waf: Uscendo dalla directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
'build' completato con successo (2.357s)

Ora puoi eseguire l'esempio (nota che se compili il tuo programma nella directory scratch, dovrai eseguirlo anche da scratch):

$ ./waf --run scratch/myfirst

Dovresti vedere un output simile:

Waf: Entrando nella directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
Waf: Uscendo dalla directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
'build' completato con successo (0.418s) Inviati 1024 byte a 10.1.1.2
Ricevuti 1024 byte da 10.1.1.1
Ricevuti 1024 byte da 10.1.1.2

Qui puoi vedere che il sistema di build controlla che il file sia stato compilato e poi lo esegue. Puoi vedere una registrazione del componente sul client di echo che indica che ha inviato un pacchetto di 1024 byte al server di echo 10.1.1.2. Puoi anche vedere la registrazione del componente sul server di echo che indica di aver ricevuto 1024 byte da 10.1.1.1. Il server di echo restituisce silenziosamente il pacchetto, e puoi vedere nel registro del client di echo che ha ricevuto il suo pacchetto indietro dal server.

4.3 Codice sorgente di ns-3

Ora che hai utilizzato alcuni degli assistenti ns-3, puoi dare un'occhiata ad alcuni codici sorgente che implementano questa funzionalità. Il codice più recente può essere visualizzato sul nostro server web al seguente link: https://gitlab.com/nsnam/ns-3-dev.git. Qui vedrai una pagina riassuntiva di Mercurial per il nostro albero di sviluppo ns-3. Nella parte superiore della pagina vedrai diversi link,

summary | shortlog | changelog | graph | tags | files

Procedi e seleziona il link ai file. Ecco come apparirà il livello superiore della maggior parte dei nostri repository:

drwxr-xr-x                               [up]
drwxr-xr-x                               bindings python  files
drwxr-xr-x                               doc              files
drwxr-xr-x                               examples         files
drwxr-xr-x                               ns3              files
drwxr-xr-x                               scratch          files
drwxr-xr-x                               src              files
drwxr-xr-x                               utils            files
-rw-r--r-- 2009-07-01 12:47 +0200 560    .hgignore        file | revisions | annotate
-rw-r--r-- 2009-07-01 12:47 +0200 1886   .hgtags          file | revisions | annotate
-rw-r--r-- 2009-07-01 12:47 +0200 1276   AUTHORS          file | revisions | annotate
-rw-r--r-- 2009-07-01 12:47 +0200 30961  CHANGES.html     file | revisions | annotate
-rw-r--r-- 2009-07-01 12:47 +0200 17987  LICENSE          file | revisions | annotate
-rw-r--r-- 2009-07-01 12:47 +0200 3742   README           file | revisions | annotate
-rw-r--r-- 2009-07-01 12:47 +0200 16171  RELEASE_NOTES    file | revisions | annotate
-rw-r--r-- 2009-07-01 12:47 +0200 6      VERSION          file | revisions | annotate
-rwxr-xr-x 2009-07-01 12:47 +0200 88110  waf              file | revisions | annotate
-rwxr-xr-x 2009-07-01 12:47 +0200 28     waf.bat          file | revisions | annotate
-rw-r--r-- 2009-07-01 12:47 +0200 35395  wscript          file | revisions | annotate
-rw-r--r-- 2009-07-01 12:47 +0200 7673   wutils.py        file | revisions | annotate

I nostri esempi di script si trovano nella directory examples. Se fai clic sugli esempi, vedrai un elenco di sottodirectory. Uno dei file nella sottodirectory tutorial — first.cc. Se clicchi su first.cc vedrai il codice che hai appena studiato.

Il codice sorgente si trova principalmente nella directory src. Puoi visualizzare il codice sorgente facendo clic sul nome della directory o sul link file a destra del nome della directory. Se clicchi sulla directory src, otterrai un elenco di sottodirectory src. Se poi clicchi sulla sottodirectory core, troverai un elenco di file. Il primo file che vedrai (al momento della scrittura di questa guida) è abort.h. Se fai clic sul link abort.h, verrai reindirizzato al file sorgente per abort.h, che contiene macro utili per uscire dagli script se vengono rilevate condizioni anomale. Il codice sorgente per gli helper che abbiamo usato in questo capitolo si trova nella directory src/Applications/helper. Non esitare a esplorare l'albero delle directory per capire cosa c'è dove e familiarizzare con lo stile del programma ns-3.

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster