
4 Panoramica della concezione
4.1 Astrazioni 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 di base
4.2.2 Moduli collegabili
4.2.3 Spazio dei nomi ns3
4.2.4 Registrazione
4.2.5 Funzione principale
4.2.6 Utilizzo degli assistenti topologici
4.2.7 Utilizzo dell'applicazione
4.2.8 Simulatore
4.2.9 Assemblaggio del tuo scenario
4.3 Codice sorgente ns-3
Capitolo 4
Panoramica della concezione
La prima cosa che dobbiamo fare prima di iniziare a studiare o scrivere codice ns-3 è spiegare qualche concetto e astrazione fondamentale nel sistema. Molto di questo, per alcuni, può sembrare ovvio, ma consigliamo di dedicare del tempo alla lettura di questa sezione per assicurarvi di partire su una base solida.
4.1 Astrazioni chiave
In questa sezione vedremo alcuni termini comuni utilizzati in rete, ma che hanno un significato specifico in ns-3.
4.1.1 Nodo
Nel gergo di Internet, un dispositivo informatico connesso alla rete è chiamato host o, talvolta, sistema finale. Poiché ns-3 è un simulatore di rete e non un simulatore di Internet, evitiamo deliberatamente il termine host, poiché è 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 nel C++ dalla classe Node. La classe NodeNode (nodo) fornisce metodi per gestire le rappresentazioni dei dispositivi computazionali nelle simulazioni.
Devi considerare Node come un computer al quale aggiungerai funzionalità. Aggiungerai elementi come applicazioni, stack di protocolli e schede periferiche con driver che consentono al computer di eseguire lavori utili. Utilizziamo lo stesso modello di base in ns-3.
4.1.2 Applicazione
In genere, il software per computer è suddiviso in due ampie categorie. Il software di sistema organizza diverse risorse informatiche come la memoria, i cicli della CPU, il disco, la rete, ecc., secondo un certo modello computazionale. Il software di sistema di solito non utilizza queste risorse per svolgere compiti che portano un immediato beneficio all'utente. L'utente di solito avvia un'applicazione per raggiungere un obiettivo specifico, che acquisisce e utilizza risorse controllate dal software di sistema.
Spesso, la linea di demarcazione tra software di sistema e software applicativo è tracciata in base al cambiamento del livello di privilegi, che avviene nelle interruzioni del sistema operativo. In ns-3 non esiste una vera e propria concettualizzazione del sistema operativo e quindi 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 funzionano 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 un'attività da modellare è l'applicazione. Questa astrazione è rappresentata nel C++ dalla classe Application (applicazione). La classe Application fornisce metodi per gestire nelle simulazioni le rappresentazioni della nostra versione delle applicazioni a livello utente. Si prevede che gli sviluppatori specializzino la classe Application nel senso della programmazione orientata agli oggetti per creare nuove applicazioni. In questa guida utilizzeremo le specializzazioni della classe Application, chiamate UdpEchoClientApplication e UdpEchoServerApplication. Come ci si aspetterebbe, queste applicazioni formano un insieme di applicazioni client/server utilizzate per generare e simulare in eco i pacchetti di rete.
4.1.3 Canale
Nel mondo reale è possibile collegare un computer a una rete. Spesso, gli ambienti attraverso cui i dati vengono trasmessi in queste reti sono chiamati canali. Quando colleghi un cavo Ethernet a una presa sulla parete, stai collegando il computer a un canale di comunicazione Ethernet. Nel mondo simulato di ns‑3, un nodo è collegato a un oggetto che rappresenta un canale di comunicazione. Qui, l'astrazione principale della sottorete di comunicazione è chiamata canale ed è rappresentata in C++ dalla classe Channel (canale).
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 un contesto 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 delle reti wireless.
Utilizzeremo in questa guida versioni specializzate del canale chiamate CsmaChannelCsmaChannel, PointToPointChannelPointToPointChannel e WifiChannelWifiChannel. CsmaChannel, ad esempio, modella una versione della sottorete di comunicazione che implementa un ambiente di comunicazione multiaccesso con controllo del portante. Questo ci dà funzionalità simili a Ethernet.
4.1.4 Dispositivo di rete
In passato, se volevi collegare un computer a una rete, dovevi acquistare un determinato cavo di rete e un dispositivo hardware chiamato (in terminologia PC) scheda periferica, da installare nel computer. Se sulla scheda periferica erano implementate alcune funzioni di rete, queste venivano chiamate schede di interfaccia di rete o schede di rete. Oggi la maggior parte dei computer è fornita di hardware di interfaccia di rete integrato e gli utenti non lo vedono come un dispositivo separato.
Una scheda di rete non funzionerà senza un driver software che gestisca il suo hardware. In Unix (o Linux), parte dell'hardware periferico viene classificata come device. I dispositivi vengono gestiti mediante driver di dispositivo (device drivers), e i dispositivi di rete (NIC) sono gestiti utilizzando driver di dispositivi di rete (network device drivers) e hanno un nome collettivo di dispositivi di rete (net devices). 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 connettersi ad altri nodi tramite canali. Come in un computer reale, un nodo può essere collegato a più canali tramite più dispositivi Dispositivi di rete.
L'astrazione della rete del dispositivo è rappresentata nella classe C++ NetDevice. La classe NetDevice fornisce metodi per gestire le connessioni con oggetti Node e Channel; e può essere specializzata dagli sviluppatori in termini di programmazione orientata agli oggetti. In questa guida utilizzeremo diverse versioni specializzate di NetDevice chiamate CsmaNetDevice, PointToPointNetDevice e WifiNetDevice. Proprio come l'adattatore di rete Ethernet è progettato per funzionare con la rete Ethernet, CsmaNetDevice è progettato per lavorare con CsmaChannel, PointToPointNetDevice è progettato per lavorare con PointToPointChannel, ma WifiNetDevice è progettato per lavorare con WifiChannel.
4.1.5 Assistenti topologici
In una rete reale troverete computer host con schede di rete aggiuntive (o integrate). In ns‑3 diremmo che vedrete nodi con dispositivi di rete connessi. In una rete simulata complessa, sarà necessario organizzare le connessioni tra molti oggetti Node, NetDevice e Channel.
Poiché collegare i dispositivi di rete ai nodi, i dispositivi di rete ai canali, assegnare indirizzi IP, ecc. in ns‑3 è un compito comune, per semplificare il processo, forniamo quelli che chiamiamo assistenti topologici. Ad esempio, per creare un dispositivo di rete sono necessarie molte operazioni di base di ns‑3, aggiungere un indirizzo MAC, installare questo dispositivo di rete nel nodo, configurare lo stack dei protocolli del nodo e quindi collegare il dispositivo di rete al canale. Saranno necessarie ulteriori operazioni per collegare più dispositivi ai canali multipunto e quindi connettere reti separate in una rete unificata (Internetworks). Forniamo oggetti di assistenza topologica che riuniscono queste numerose operazioni in un modello facile da usare.
4.2 Primo script ns-3
Se hai installato il sistema come suggerito sopra, avrai un rilascio di ns‑3 nella directory chiamata repos nella tua home directory. Vai alla directory release
Se non hai questa directory, significa che durante la compilazione della versione di rilascio di ns‑3 non hai specificato la directory di output, compila così:
$ . / waf configure —build-profile=release —out=build / release,
$ . / waf build
lì dovreste 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.pycPassa alla directory examples / tutorial. Dovresti vedere un file lì chiamato first.cc. Questo è uno script che creerà una semplice connessione punto a punto tra due nodi e trasmetterà un pacchetto tra di essi. Vediamo questo script riga per riga, per farlo apriamo first.cc nel tuo editor preferito.
4.2.1 Codice di base
La prima riga nel file è la riga della modalità dell'editor emacs. Essa informa emacs sulle convenzioni di formattazione (stile di codifica) che utilizziamo nel nostro codice sorgente.
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */Questa è sempre una questione piuttosto controversa, quindi dobbiamo chiarire questo punto per liberarcene subito. Il progetto ns-3, come la maggior parte dei grandi progetti, ha adottato uno stile di codifica a cui tutto il codice fornito deve aderire. Se desideri contribuire con il tuo codice al progetto, alla fine dovrai conformarti allo standard di codifica ns-3, come descritto nel file doc / codingstd.txt o mostrato sulla pagina web del progetto: .
Ti consigliamo di familiarizzare con l'aspetto del codice ns-3 e di applicare questo standard ogni volta che lavori con il nostro codice. Tutti i membri del team di sviluppo e i collaboratori hanno concordato su questo dopo un certo lamento. La riga della modalità emacs sopra riportata semplifica la formattazione corretta se utilizzi l'editor emacs.
Il simulatore ns-3 è concesso in licenza utilizzando GNU General Public License. Vedrai l'intestazione legale GNU appropriata in ogni file della distribuzione ns-3. Spesso puoi vedere un avviso di copyright per uno degli enti partecipanti al progetto ns-3 sopra il testo GPL e l'autore, 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 collegabili
Il codice stesso inizia con una serie di direttive 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 aiutare i nostri utenti di script di alto livello a gestire grandi quantità di file header presenti nel sistema, li raggruppiamo in base al loro utilizzo in moduli più grandi. Forniamo un file header che caricherà ricorsivamente tutti i file header utilizzati in quel modulo. Invece di cercare quale specifico header ti serve e forse ottenere l'elenco corretto delle dipendenze, ti diamo la possibilità di caricare un gruppo di file con un alto grado di dettaglio. Questo non è l'approccio più efficiente, ma sicuramente rende la scrittura degli script molto più semplice.
Ogni file incluso in ns-3 viene posizionato in una directory di nome ns3 (sotto-directory di build), per evitare conflitti nei nomi dei file durante il processo di build. 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 posiziona i file header pubblici nella directory ns3 nella sotto-directory build/debug
Se non hai questa directory, significa che durante la compilazione della versione di rilascio di ns‑3 non hai specificato la directory di output, compila così:
$ ./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 header del modulo per caricare tutti i file header pubblici. Poiché, ovviamente, segui rigorosamente questa guida, hai già eseguito
$ ./waf -d debug --enable-examples --enable-tests configureper configurare il progetto per effettuare build di debug, incluso esempi e test. Hai anche eseguito
$ ./wafper costruire il progetto. Quindi ora, quando guardi nella directory ../../build/debug/ns3, troverai, tra le altre cose, i file header di quattro moduli, come mostrato sopra. Puoi dare un'occhiata al contenuto di questi file e scoprire che includono tutti i file pubblici utilizzati dai relativi moduli.
4.2.3 Spazio dei nomi ns3
La riga successiva nello script first.cc è la dichiarazione dello spazio dei nomi.
using namespace ns3;Il progetto ns-3 è implementato nello spazio dei nomi C++ chiamato ns3. Ciò raggruppa tutte le dichiarazioni relative a ns-3 in uno spazio di visibilità al di fuori dello spazio dei nomi globale, che speriamo possa aiutare nell'integrazione con altri codici. L'uso dello spazio dei nomi C++ introduce il namespace ns-3 nella regione dichiarativa attuale (globale). È un modo stravagante per dire che dopo questa dichiarazione non sarà necessario inserire l'operatore di risoluzione ns3::scope prima di tutto il codice ns-3 per utilizzarlo. Se non sei familiare con gli spazi dei nomi, consulta quasi qualsiasi manuale di C++ e confronta lo spazio dei nomi ns3 con l'uso dello spazio dei nomi std e delle dichiarazioni using namespace std; nei casi di utilizzo dell'operatore di output cout e flussi.
4.2.4 Registrazione
La seguente riga dello script è
NS_LOG_COMPONENT_DEFINE ("FirstScriptExample");Utilizzeremo questa affermazione come punto comodo per discutere il nostro sistema di documentazione Doxygen. Se dai un'occhiata al sito web del progetto ns-3, troverai un link "Documentazione" (Documentation) nella barra di navigazione. Se scegli questo link, verrai alla nostra pagina di documentazione. C'è un link per il "Ultimo rilascio", che ti porterà alla documentazione per l'ultima versione stabile di ns-3. Se scegli il link "Documentazione API", verrai alla pagina di 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" Moduli ns-3 nell'albero di navigazione ns-3. Se espandi Modules, vedrai un elenco della documentazione dei moduli ns-3. Come discusso in precedenza, il concetto di modulo è qui direttamente collegato ai file inclusi nel modulo sopra. Il sottosistema di registrazione di ns-3 è discusso nella sezione Uso del modulo di registrazione, pertanto torneremo su di esso più avanti in questa guida, ma puoi apprendere riguardo all'affermazione qui sopra guardando il modulo Core, e poi aprendo il libro Strumenti di debug, e poi selezionando la pagina Registrazione. Fai clic su Registrazione.
Ora dovresti esaminare la documentazione Doxygen per il modulo Registrazione. Nella lista dei macro nella parte superiore della pagina vedrete una voce per NS_LOG_COMPONENT_DEFINE. Prima di fare clic sul collegamento, assicuratevi di controllare la "Descrizione dettagliata" del modulo di registrazione per comprendere il suo funzionamento complessivo. Per farlo, potete scorrere in basso o selezionare "Di più..." sotto il diagramma.
Una volta che avrete una visione generale di cosa sta succedendo, continuate e consultate la documentazione specifica su NS_LOG_COMPONENT_DEFINE. Non duplicherò qui la documentazione, ma per riassumere, dico che questa riga dichiara un componente di registrazione chiamato FirstScriptExample, che consente di abilitare e disabilitare la registrazione dei messaggi della console tramite un collegamento al nome.
4.2.5 Funzione principale
Nelle righe successive dello script vedrete,
int
main (int argc, char *argv[])
{ Questa è semplicemente la dichiarazione della funzione principale del vostro programma (script). Come in qualsiasi programma C++, è necessario 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 riga successiva imposta la risoluzione temporale a 1 nanosecondo, che è il valore predefinito:
Time::SetResolution (Time::NS);La risoluzione temporale o semplicemente la risoluzione è il valore di tempo più piccolo che può essere utilizzato (la più piccola differenza rappresentabile tra due valori di tempo). Potete modificare la risoluzione solo una volta. Il meccanismo che fornisce questa flessibilità consuma memoria, quindi, una volta che la risoluzione è stata impostata esplicitamente, liberiamo la memoria per prevenire ulteriori aggiornamenti. (Se non impostate esplicitamente la risoluzione, per impostazione predefinita sarà di un nanosecondo, e la memoria verrà liberata all'inizio della simulazione.)
Le seguenti due righe dello script vengono utilizzate per abilitare due componenti di registrazione integrati nelle applicazioni EchoClient e EchoServer:
LogComponentEnable("UdpEchoClientApplication", LOG_LEVEL_INFO); LogComponentEnable("UdpEchoServerApplication", LOG_LEVEL_INFO);Se hai letto la documentazione del componente Logging, vedrai che ci sono diversi livelli di dettaglio del logging che puoi abilitare per ciascun componente. Queste due righe di codice abilitano il logging di debug al 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 passeremo direttamente all'argomento della creazione della topologia e dell'avvio della simulazione. Utilizzeremo oggetti di assistenza topologica per semplificare questo processo.
4.2.6 Utilizzo degli assistenti topologici
Le due righe di codice seguenti 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 è tramite la scheda Classes nelle pagine Doxygen. Se hai già aperto Doxygen, basta scorrere verso l'alto nella parte superiore della pagina e selezionare la scheda Classi. Dovresti vedere un nuovo insieme di schede, una delle quali è un elenco di 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 della classe.
Come ricordiamo, una delle nostre astrazioni chiave è il nodo. Rappresenta un computer al quale intendiamo aggiungere 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 avviare la simulazione. La prima riga sopra dichiara semplicemente NodeContainer, che chiamiamo nodes. La seconda riga chiama il metodo Create per l'oggetto nodes 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 conserva i puntatori a questi oggetti al suo interno.
I nodi creati nello script, per ora non fanno nulla. Il passo successivo nella costruzione della topologia è collegare i nostri nodi alla rete. La forma più semplice di rete che supportiamo è un collegamento punto a punto tra due nodi. Ora creeremo tale connessione.
PointToPointHelper
Stiamo creando una connessione punto-punto seguendo uno schema a noi familiare, utilizzando un oggetto ausiliario topologico per svolgere il lavoro a basso livello necessario per la connessione. Ricordiamo che le nostre due astrazioni chiave NetDevice e Channel. Nel mondo reale questi termini corrispondono approssimativamente alle schede periferiche e ai cavi di rete. In genere, queste due cose sono strettamente correlate tra loro, e nessuno può contare su uno scambio, ad esempio, di dispositivi Ethernet tramite canali wireless. I nostri assistenti topologici seguono questa stretta connessione e quindi in questo scenario utilizzerete un oggetto PointToPointHelper per configurare e collegare gli oggetti ns‑3 PointToPointNetDevice e PointToPointChannel. Le seguenti tre righe nello scenario:
PointToPointHelper pointToPoint;
pointToPoint.SetDeviceAttribute ("DataRate", StringValue ("5Mbps"));
pointToPoint.SetChannelAttribute ("Delay", StringValue ("2ms"));La prima riga,
PointToPointHelper pointToPoint;crea un'istanza dell'oggetto nello stack PointToPointHelper. Dal punto di vista di alto livello, la riga successiva,
pointToPoint.SetDeviceAttribute ("DataRate", StringValue ("5Mbps"));dice all'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 attributo PointToPointNetDevice. Se controllate Doxygen per la classe ns3::PointToPointNetDevice e nella documentazione del metodo GetTypeId troverete 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 simili di attributi. Utilizziamo questo meccanismo per una facile configurazione della simulazione senza ricompilazione, come vedrete nella sezione successiva.
Simile a "DataRate" nel PointToPointNetDevice, troverete l'attributo "Delay" associato al PointToPointChannel. L'ultima riga,
pointToPoint.SetChannelAttribute ("Delay", StringValue ("2ms"));sottolinea PointToPointHelper utilizza il valore "2 ms" (due millisecondi) come valore di ritardo di propagazione attraverso il canale punto-punto che crea successivamente.
NetDeviceContainer
Finora abbiamo nello scenario NodeContainer, che contiene due nodi. Abbiamo PointToPointHelper, che è preparato per creare oggetti PointToPointNetDevices e collegarli tramite un oggetto PointToPointChannel. Così come abbiamo utilizzato un oggetto ausiliario di topologia NodeContainer per creare i nodi, chiederemo a 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 il loro stoccaggio proprio come abbiamo usato NodeContainer per memorizzare i nodi che abbiamo creato. Le seguenti due righe di codice
NetDeviceContainer dispositivi;
dispositivi = pointToPoint.Install (nodi);completano la configurazione dei dispositivi e del canale. La prima riga dichiara il contenitore del dispositivo menzionato sopra, mentre la seconda svolge il lavoro principale. Il metodo Installa dell'oggetto PointToPointHelper accettano NodeContainer come parametro. All'interno NetDeviceContainer per ogni nodo presente in NodeContainer viene creato (per la connessione punto-a-punto devono essercene esattamente due) PointToPointNetDevice viene creato e salvato nel contenitore dei dispositivi. PointToPointChannel viene creato e a esso si collegano due PointToPointNetDevices. Dopo la creazione degli oggetti, gli attributi memorizzati in PointToPointHelper, sono 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 "punto-a-punto" installato e un canale "punto-a-punto" tra di loro. Entrambi i dispositivi saranno configurati per trasmettere dati a una velocità di cinque megabit al secondo con un ritardo di trasmissione nel canale di due millisecondi.
InternetStackHelper
Ora abbiamo configurato nodi e dispositivi, ma i nostri nodi non hanno installato stack di protocolli. Le seguenti due righe di codice si occuperanno di questo.
InternetStackHelper stack;
stack.Install (nodi);InternetStackHelper è un helper topologico per stack Internet, simile a PointToPointHelper per dispositivi di rete punto-a-punto. Il metodo Installa prende NodeContainer come parametro. Durante l'esecuzione installerà lo stack Internet (TCP, UDP, IP, ecc.) su ciascun nodo del contenitore.
Ipv4AddressHelper
Ora dobbiamo associare i nostri dispositivi agli indirizzi IP. Forniamo un helper topologico per gestire la distribuzione degli indirizzi IP. L'unico API visibile per l'utente è impostare l'indirizzo IP di base e la maschera di rete da utilizzare durante l'effettiva distribuzione degli indirizzi (questo avviene a un livello più basso all'interno dell'helper). 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 gli dicono di iniziare a distribuire indirizzi IP dalla rete 10.1.1.0, utilizzando per la determinazione la maschera di rete 255.255.255.0. Per impostazione predefinita, gli indirizzi distribuiti inizieranno da uno e aumenteranno in modo monotono, quindi il primo indirizzo distribuito da questo pool sarà 10.1.1.1, poi 10.1.1.2, e così via. In realtà, a un livello inferiore, il sistema ns-3 memorizza tutti gli indirizzi IP assegnati e genera un errore fatale se crei accidentalmente una situazione in cui lo stesso indirizzo viene generato due volte (tra l'altro, questo errore è difficile da debuggar).
La riga di codice successiva,
Ipv4InterfaceContainer interfaces = address.Assign (devices);esegue l'assegnazione effettiva dell'indirizzo. In ns-3, stabiliremo un legame tra l'indirizzo IP e il dispositivo, utilizzando l'oggetto Ipv4Interface. Così come a volte abbiamo bisogno di un elenco di dispositivi di rete creati dal helper per un uso successivo, a volte abbiamo bisogno di un elenco di oggetti Ipv4Interface. Ipv4InterfaceContainer fornisce questa funzionalità.
Abbiamo costruito una rete punto-punto, con stack impostati e indirizzi IP assegnati. Ora abbiamo bisogno di applicazioni su ciascun nodo per generare traffico.
4.2.7 Utilizzo 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 chiamata UdpEchoServerApplication e UdpEchoClientApplication. Come nei casi precedenti, utilizziamo oggetti ausiliari per configurare e gestire gli oggetti di base. Qui utilizziamo UdpEchoServerHelper e UdpEchoClientHelperoggetti, per semplificare la nostra vita.
UdpEchoServerHelper
Le righe di codice seguenti 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 si tratta di un'applicazione in sé, ma di un oggetto che ci aiuta a creare applicazioni reali. Una delle nostre convenzioni è di passare gli attributi necessari al costruttore dell'oggetto ausiliario (assistente). In questo caso, l'assistente non può fare nulla di utile se non gli viene fornito il numero di porta su cui il server attenderà i pacchetti, questo numero deve essere noto anche al client. In questo caso passiamo al costruttore dell'assistente il numero di porta. Il costruttore, a sua volta, esegue semplicemente SetAttribute con il valore fornito. In seguito, se lo si desidera, utilizzando SetAttribute sarà possibile 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 porta effettivamente alla creazione di un'applicazione di echo server di base e viene legata a un nodo. È interessante notare che il metodo Installa accettano NodeContainter come parametro, proprio come gli altri Installa metodi che abbiamo visto.
La conversione implicita in C++, che qui viene eseguita, prende 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 viene poi passato al metodo Installa. Se non è possibile determinare nel codice C++ quale firma del metodo viene compilata e eseguita, cercate tra le conversioni implicite.
Ora vediamo che echoServer.Install si appresta a installare l'applicazione UdpEchoServerApplication sull'elemento trovato in NodeContainer, che utilizziamo per gestire i nostri nodi, il 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 di traffico 'start' e potrebbe essere necessario specificare anche quando fermarlo 'stop'.Forniamo entrambi i parametri. Questi orari vengono impostati tramite i metodi ApplicationContainer Avvia e Ferma. Questi metodi accettano parametri di tipo Tempo. In questo caso utilizziamo una sequenza esplicita di conversioni in C++ per prendere il 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 proprie regole, quindi non puoi sempre fare affidamento sul fatto che i parametri vengano convertiti come ti aspetti. Due righe,
serverApps.Start (Seconds (1.0));
serverApps.Stop (Seconds (10.0));porterà al fatto che l'applicazione del server echo si avvierà (si accenderà automaticamente) dopo un secondo dall'inizio della simulazione e si fermerà (si spegnerà) dopo dieci secondi di simulazione. Poiché abbiamo dichiarato un evento di simulazione (evento di arresto dell'applicazione) che verrà eseguito dopo dieci secondi, verrà simulato un minimo di dieci secondi di attività 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 dobbiamo impostare cinque attributi diversi. I primi due attributi vengono impostati al momento della creazione UdpEchoClientHelper. Passiamo i parametri utilizzati (all'interno dell'assistente) per impostare gli attributi "RemoteAddress" e "RemotePort" secondo il nostro accordo, per la trasmissione dei parametri necessari al costruttore dell'assistente.
Ricordiamo che abbiamo utilizzato Ipv4InterfaceContainer per tenere traccia degli indirizzi IP che abbiamo assegnato ai nostri dispositivi. L'interfaccia zero nel contenitore interfacce corrisponderà all'indirizzo IP del nodo zero nel contenitore nodi. La prima interfaccia nel contenitore interfacce corrisponde all'indirizzo IP del primo nodo nel contenitore nodi. Quindi, nella prima riga di codice (in alto), creiamo l'assistente e gli diciamo che l'indirizzo remoto del client sarà l'indirizzo IP assegnato al nodo in cui si trova il server. Diciamo anche che i pacchetti devono essere inviati alla porta nove.
L'attributo «MaxPackets» informa il client sul numero massimo di pacchetti che possiamo inviare durante la simulazione. L'attributo «Interval» indica al client quanto tempo attendere tra i pacchetti e l'attributo «PacketSize» comunica al client quanto grande deve essere il payload del pacchetto. Con questa combinazione di attributi, diciamo al client di inviare un pacchetto di 1024 byte.
Come nel caso del server echo, impostiamo gli attributi per l'echo client Avvia e Ferma, ma qui avviamo il client un secondo dopo l'attivazione del server (due secondi dopo l'inizio della simulazione).
4.2.8 Simulatore
A questo punto dobbiamo avviare la simulazione. Questo viene fatto utilizzando la 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 a 1,0 secondi, 2,0 secondi e due eventi a 10,0 secondi. Dopo la chiamata Simulator::Run, il sistema inizierà a scorrere l'elenco degli eventi programmati ed eseguirli. In primo luogo, attiverà l'evento a 1,0 secondi, attivando l'applicazione del server echo (questo evento può, a sua volta, programmare molti altri eventi). Poi avvierà l'evento programmato per t = 2,0 secondi, che avvierà l'applicazione del client echo. Anche questo evento può programmare molti altri eventi. L'implementazione dell'evento di avvio nell'echo client darà inizio alla fase di trasmissione dei dati della simulazione, inviando un pacchetto al server.
L'atto di inviare un pacchetto al server innescherà una serie di eventi che saranno automaticamente programmati dietro le quinte e che implementeranno 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, l'attributo MaxPackets è stato impostato a uno), la serie di eventi innescata da questa singola richiesta echo del client terminerà e la simulazione passerà in stato di attesa. Una volta che ciò accade, gli eventi rimanenti programmati saranno gli eventi Ferma per il server e per il client. Quando questi eventi verranno eseguiti, non ci saranno più eventi da elaborare e Simulator::Run restituirà il controllo. La simulazione è completata.
Resta solo da pulire. Questo avviene chiamando la funzione globale Simulator::Destroy. Poiché sono state chiamate le funzioni dei helper (o codice di basso livello ns-3), che sono organizzate in modo tale da inserire nel simulatore ganci per distruggere tutti gli oggetti creati. Non è necessario tenere traccia di nessuno di questi oggetti da solo: tutto ciò che devi fare è chiamare Simulator::Destroy e uscire. Il sistema ns-3 farà questo lavoro difficile per te. Le righe rimanenti 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 eventi nell'ordine in cui si verificano nel corso della simulazione. Gli eventi possono causare la pianificazione di eventi futuri (ad esempio, un timer può ripianificarsi per completare il conteggio nel successivo intervallo).
Gli eventi iniziali sono solitamente avviati da un oggetto, ad esempio, IPv6 pianificherà la scoperta dei servizi nella rete, le richieste ai vicini, ecc. L'applicazione pianifica il primo evento di invio di pacchetti, ecc. Quando un evento viene elaborato, può generare zero, uno o più eventi. Man mano che la simulazione si svolge, gli eventi possono semplicemente terminare o generare nuovi eventi. La simulazione si fermerà automaticamente se la coda degli eventi risulta vuota o viene rilevato un evento speciale Ferma. L'evento Ferma è generato dalla funzione Simulator::Stop (fermare il tempo).
C'è un caso tipico in cui Simulator::Stop è assolutamente necessario per fermare la simulazione: quando ci sono eventi autoconservanti. Gli eventi autoconservanti (o ripetitivi) sono eventi che vengono sempre riprogrammati. Di conseguenza, mantengono sempre la coda degli eventi non vuota. Ci sono molti protocolli e moduli contenenti eventi ripetitivi, ad esempio:
• FlowMonitor — controllo periodico sui pacchetti persi;
• RIPng — trasmissione periodica degli aggiornamenti delle tabelle di routing;
• ecc.
In tali casi Simulator::Stop è necessario per fermare correttamente la simulazione. Inoltre, quando ns-3 è in modalità di 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 chiamano Simulator::Stop Chiaramente, poiché si completano automaticamente con l'esaurimento degli eventi in coda. Tuttavia, questi programmi risponderanno anche alla chiamata Simulator::Stop. Per esempio, il seguente operatore aggiuntivo nel primo esempio del programma programmerà un arresto esplicito all'undicesimo secondo:
+ Simulator::Stop (Seconds (11.0));
Simulator::Run ();
Simulator::Destroy ();
return 0;
}L'affermazione sopra non cambierà effettivamente il comportamento di questo programma, poiché questa specifica simulazione si conclude naturalmente dopo 10 secondi. Ma se avessi modificato il tempo di arresto nell'operatore sopra da 11 secondi a 1 secondo, noteresti che la simulazione si interrompe prima che qualsiasi output venga visualizzato (poiché l'output si verifica circa 2 secondi dopo l'orario della 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 Assemblaggio del tuo scenario
Abbiamo reso la creazione dei tuoi semplici script banale. Tutto ciò che devi fare è posizionare il tuo script nella directory scratch, e verrà automaticamente compilato quando verrà eseguito Waf. Proviamo. Torna nella directory di livello superiore e copia examples/tutorial/first.cc nella cartella scratch
$ cd ..
$ cp examples/tutorial/first.cc scratch/myfirst.ccOra compila il tuo primo esempio di script, utilizzando waf:
$ ./wafDovresti vedere dei messaggi che indicano che il tuo primo esempio è stato creato con successo.
Waf: In entrata 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: In uscita dalla directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
'build' concluso 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/myfirstDovresti vedere un output simile:
Waf: In entrata nella directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
Waf: In uscita dalla directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
'build' concluso 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.2Qui puoi vedere che il sistema di assemblaggio verifica che il file sia stato assemblato e poi lo avvia. Vedi che la registrazione del componente sul client echo indica che ha inviato un pacchetto di 1024 byte al server echo 10.1.1.2. Vedi anche la registrazione del componente sul server echo, che indica di aver ricevuto 1024 byte da 10.1.1.1. Il server echo ripete silenziosamente il pacchetto e vedi nel registro del client echo che ha ricevuto il suo pacchetto indietro dal server.
4.3 Codice sorgente ns-3
Ora che hai usato 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: . Qui troverai una pagina di riepilogo Mercurial per il nostro albero di sviluppo ns‑3. Nella parte superiore della pagina vedrai alcuni collegamenti,
summary | shortlog | changelog | graph | tags | filesProsegui e seleziona il collegamento 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 | annotateI nostri esempi di scenari si trovano nella directory examples. Se fai clic sugli esempi, vedrai un elenco di sottodirectory. Uno dei file nella sottodirectory tutorial — first.cc. Se fai clic 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 dei file a destra del nome della directory. Se fai clic sulla directory src, otterrai un elenco delle sotto-directory di src. Se poi fai clic sulla sotto-directory 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 in caso di condizioni anomale. Il codice sorgente per gli helper che abbiamo utilizzato in questo capitolo può essere trovato nella directory src/Applications/helper. Non esitare a esplorare l'albero delle directory per capire cosa c'è e orientarti nello stile di programmazione di ns‑3.
Fonte: habr.com
