Questo è il comando ns-3-manual che può essere eseguito nel provider di hosting gratuito OnWorks utilizzando una delle nostre numerose workstation online gratuite come Ubuntu Online, Fedora Online, emulatore online di Windows o emulatore online di MAC OS
PROGRAMMA:
NOME
ns-3-manual - Manuale ns-3
Questa è la NS-3 Manuale. La documentazione principale per il progetto ns-3 è disponibile in cinque
le forme:
· NS-3 Doxygen: Documentazione delle API pubbliche del simulatore
· Tutorial, Manuale (Questo documento)e libreria di modelli per il con i più recenti rilasciare e
sviluppo albero
· NS-3 wiki
INDICE
Organizzazione
Questo capitolo descrive l'insieme NS-3 organizzazione del software e corrispondente
organizzazione di questo manuale.
NS-3 è un simulatore di rete ad eventi discreti in cui il nucleo di simulazione e i modelli sono
implementato in C++. NS-3 è costruito come una libreria che può essere statica o dinamica
collegato a un programma principale C++ che definisce la topologia della simulazione e avvia la
simulatore. NS-3 esporta anche quasi tutta la sua API in Python, consentendo ai programmi Python di
importare un modulo "ns3" più o meno nello stesso modo del NS-3 la libreria è collegata tramite eseguibili
in C ++.
[immagine] Organizzazione del software di NS-3.INDENTATO
Il codice sorgente per NS-3 è organizzato principalmente nel src directory e può essere descritto
dal diagramma in Software organizzazione of NS-3Lavoreremo dal basso
in alto; in generale, i moduli hanno dipendenze solo dai moduli sottostanti nella figura.
Descriviamo innanzitutto il nucleo del simulatore, ovvero quei componenti comuni a tutti
protocollo, hardware e modelli ambientali. Il nucleo di simulazione è implementato in
sorgente/nucleoI pacchetti sono oggetti fondamentali in un simulatore di rete e sono implementati in
src/reteQuesti due moduli di simulazione da soli sono destinati a comprendere un
nucleo di simulazione generico che può essere utilizzato da diversi tipi di reti, non solo
Reti basate su Internet. I moduli di cui sopra NS-3 sono indipendenti dalla rete specifica
e modelli di dispositivi, trattati nelle parti successive di questo manuale.
In aggiunta a quanto sopra NS-3 nucleo, introduciamo, anche nella porzione iniziale del
manuale, altri due moduli che integrano l'API principale basata su C++. NS-3 i programmi possono
accedere direttamente a tutte le API oppure può utilizzare un cosiddetto aiutante API quello fornisce
wrapper convenienti o incapsulamento di chiamate API di basso livello. Il fatto che NS-3 programmi
può essere scritto su due API (o una combinazione di esse) è un aspetto fondamentale del
simulatore. Descriviamo anche come Python è supportato in NS-3 prima di passare a qualcosa di specifico
modelli rilevanti per la simulazione di rete.
Il resto del manuale è focalizzato sulla documentazione dei modelli e sul supporto
capacità. La parte successiva si concentra su due oggetti fondamentali in NS-3: il Nodo e
NetDeviceDue tipi speciali di NetDevice sono progettati per supportare l'uso dell'emulazione di rete
casi, e l'emulazione è descritta di seguito. Il capitolo seguente è dedicato a
Modelli correlati a Internet, tra cui l'API socket utilizzata dalle applicazioni Internet.
il capitolo successivo riguarda le applicazioni e il capitolo seguente descrive il supporto aggiuntivo
per la simulazione, come animatori e statistiche.
Il progetto mantiene un manuale separato dedicato al test e alla convalida di NS-3 codice
(Vedi NS-3 Testing e Convalida Manuale).
Random Variabili
NS-3 contiene un generatore di numeri pseudo-casuali (PRNG) integrato. È importante per
utenti seri del simulatore per comprendere la funzionalità, la configurazione e l'utilizzo
di questo PRNG e decidere se è sufficiente per il suo uso di ricerca.
Presto Panoramica
NS-3 i numeri casuali vengono forniti tramite istanze di ns3::RandomVariableStream.
· per impostazione predefinita, NS-3 le simulazioni utilizzano un seme fisso; se c'è qualche casualità nel
simulazione, ogni esecuzione del programma produrrà risultati identici a meno che il seme e/o
il numero di esecuzione è cambiato.
· In NS-3.3 e prima, NS-3 le simulazioni hanno utilizzato un seme casuale per impostazione predefinita; questo segna un
cambiamento di politica a partire da NS-3.4.
· In NS-3.14 e prima, NS-3 le simulazioni utilizzavano una classe wrapper diversa chiamata
ns3::VariabileCasuale. Come di NS-3.15, questa classe è stata sostituita da
ns3::RandomVariableStream; il generatore di numeri pseudo-casuali sottostante non ha
cambiato.
· per ottenere casualità su più esecuzioni di simulazione, è necessario impostare il seme
in modo diverso o imposta il numero di run in modo diverso. Per impostare un seed, chiama
ns3::RngSeedManager::SetSeed() all'inizio del programma; per impostare un numero di esecuzione con
lo stesso seme, chiama ns3::RngSeedManager::SetRun() all'inizio del programma; vedere
Creazione con variabili.
· ogni RandomVariableStream utilizzato in NS-3 ha un generatore di numeri casuali virtuali associato
con esso; tutte le variabili casuali utilizzano un seme fisso o casuale in base all'uso del
seme globale (punto precedente);
· se si intende eseguire più esecuzioni dello stesso scenario, con diversi valori casuali
numeri, assicurati di leggere la sezione su come eseguire repliche indipendenti:
Creazione con variabili.
Continua a leggere per maggiori spiegazioni sulla funzione dei numeri casuali per NS-3.
sfondo
Le simulazioni utilizzano molti numeri casuali; uno studio ha scoperto che la maggior parte delle simulazioni di rete
impiegare fino al 50% della CPU per generare numeri casuali. Gli utenti della simulazione devono essere
preoccupato della qualità dei numeri (pseudo) casuali e dell'indipendenza tra
diversi flussi di numeri casuali.
Gli utenti devono tenere conto di alcuni aspetti, quali:
· la semina del generatore di numeri casuali e se un risultato della simulazione è
deterministico o no,
· come acquisire diversi flussi di numeri casuali indipendenti l'uno dall'altro
un altro, e
· quanto tempo impiegano i flussi a ciclare
Introdurremo qui alcuni termini: un RNG fornisce una lunga sequenza di (pseudo) casuali
numeri. La lunghezza di questa sequenza è chiamata ciclo lunghezza or periodo, dopo il quale
l'RNG si ripeterà. Questa sequenza può essere suddivisa in disgiunti flussi. UN
Il flusso di un RNG è un sottoinsieme o blocco contiguo della sequenza RNG. Ad esempio, se
Il periodo RNG è di lunghezza N e da questo RNG vengono forniti due flussi, quindi il primo
il flusso potrebbe utilizzare i primi valori N/2 e il secondo flusso potrebbe produrre il secondo N/2
valori. Una proprietà importante qui è che i due flussi non sono correlati. Allo stesso modo,
ogni flusso può essere suddiviso in modo disgiunto in un numero di non correlati sottoflussi.
Si spera che l'RNG sottostante produca una sequenza pseudo-casuale di numeri con una lunghezza molto lunga
lunghezza del ciclo e lo suddivide in flussi e sottoflussi in modo efficiente.
NS-3 utilizza lo stesso generatore di numeri casuali sottostante di NS-2: il MRG32k3a
generatore di Pierre L'Ecuyer. Una descrizione dettagliata può essere trovata in
http://www.iro.umontreal.ca/~lecuyer/myftp/papers/streams00.pdfIl generatore MRG32k3a
fornisce 1.8x10^{19} flussi indipendenti di numeri casuali, ognuno dei quali è costituito da
2.3x10^{15} sottoflussi. Ogni sottoflusso ha un periodo (vale a dire, il numero di numeri casuali
prima della sovrapposizione) di 7.6x10^{22}. Il periodo dell'intero generatore è 3.1x10^{57}.
Classe ns3::RandomVariableStream è l'interfaccia pubblica a questo numero casuale sottostante
generatore. Quando gli utenti creano nuove variabili casuali (come ns3::UniformRandomVariable,
ns3::VariabileCasualeEsponenziale, ecc.), creano un oggetto che utilizza uno dei
flussi distinti e indipendenti del generatore di numeri casuali. Pertanto, ogni oggetto di
Digitare ns3::RandomVariableStream ha, concettualmente, il suo RNG "virtuale". Inoltre,
ogni ns3::RandomVariableStream può essere configurato per utilizzare uno dei set di sottoflussi disegnati
dal flusso principale.
Un'implementazione alternativa sarebbe quella di consentire a ciascuna variabile casuale di avere la propria
(RNG con seeding diverso). Tuttavia, non possiamo garantire con la stessa forza che i diversi
le sequenze sarebbero non correlate in tal caso; quindi, preferiamo usare un singolo RNG e
flussi e sottoflussi da esso.
Creazione con variabili
NS-3 supporta un numero di oggetti variabili casuali dalla classe base
Flusso variabile casualeQuesti oggetti derivano da ns3::Oggetto e sono gestiti in modo intelligente
puntatori.
Il modo corretto per creare questi oggetti è utilizzare il modello CreaOggetto<> Metodo,
per esempio:
Ptr x = CreaOggetto ();
quindi è possibile accedere ai valori chiamando metodi sull'oggetto come:
myRandomNo = x->GetInteger ();
Se invece provi a fare qualcosa del genere:
myRandomNo = UniformRandomVariable().GetInteger ();
il tuo programma incontrerà un errore di segmentazione, perché l'implementazione si basa su
una costruzione di attributi che si verifica solo quando CreateObject è chiamato.
Gran parte del resto di questo capitolo ora discute le proprietà del flusso di
numeri pseudo-casuali generati da tali oggetti e come controllare la semina di tali
oggetti.
semina e studente indipendente repliche
NS-3 le simulazioni possono essere configurate per produrre risultati deterministici o casuali. Se il
NS-3 la simulazione è configurata per utilizzare un seme fisso e deterministico con lo stesso numero di esecuzione,
dovrebbe fornire lo stesso output ogni volta che viene eseguito.
Per impostazione predefinita, NS-3 le simulazioni utilizzano un seed fisso e un numero di esecuzione. Questi valori vengono memorizzati in
Tutto ns3::GlobalValue istanze: g_rngSeed e g_rngRun.
Un caso d'uso tipico è quello di eseguire una simulazione come una sequenza di prove indipendenti, in modo da
calcolare statistiche su un gran numero di esecuzioni indipendenti. L'utente può modificare
seme globale e rieseguire la simulazione, oppure può far avanzare lo stato del substream dell'RNG, che
si parla di incremento del numero di esecuzioni.
Una classe ns3::RngSeedManager fornisce un'API per controllare il seeding e il numero di run
comportamento. Questa impostazione di seeding e stato del substream deve essere chiamata prima di qualsiasi casuale
vengono create variabili; ad esempio:
RngSeedManager::SetSeed (3); // Cambia il seed dal valore predefinito 1 a 3
RngSeedManager::SetRun (7); // Modifica il numero di esecuzione dal valore predefinito 1 a 7
// Ora, crea variabili casuali
Ptr x = CreaOggetto ();
Ptr y = CreaOggetto ();
...
Cosa è meglio, impostare un nuovo seed o far avanzare lo stato del substream? Non c'è
garantire che i flussi prodotti da due semi casuali non si sovrappongano. L'unico modo per
garantire che due flussi non si sovrappongano è utilizzare la capacità di substream fornita da
l'implementazione RNG. Perciò, uso , il sottoflusso capacità a produrre multiplo
studente indipendente corre of , il stesso simulazione. In altre parole, più statisticamente rigorosi
Un modo per configurare più repliche indipendenti è utilizzare un seme fisso e avanzare
il numero di esecuzione. Questa implementazione consente un massimo di 2.3x10^{15} indipendenti
repliche utilizzando i sottoflussi.
Per facilità d'uso, non è necessario controllare il numero di seed e run dall'interno
programma; l'utente può impostare il NS_GLOBAL_VALUE variabile d'ambiente come segue:
$ NS_GLOBAL_VALUE="RngRun=3" ./waf --run nome-programma
Un altro modo per controllare questo è passare un argomento della riga di comando; poiché questo è un NS-3
Nell'istanza GlobalValue, la procedura viene eseguita in modo equivalente nel modo seguente:
$ ./waf --command-template="%s --RngRun=3" --run nome-programma
oppure, se stai eseguendo programmi direttamente al di fuori di waf:
$ ./build/optimized/scratch/nome-programma --RngRun=3
Le varianti della riga di comando sopra riportate semplificano l'esecuzione di numerose esecuzioni diverse da una shell
script semplicemente passando un indice RngRun diverso.
Classe Flusso variabile casuale
Tutte le variabili casuali dovrebbero derivare dalla classe Variabile casualeQuesta classe base fornisce un
pochi metodi per configurare globalmente il comportamento del generatore di numeri casuali. Derivato
le classi forniscono API per disegnare varianti casuali dalla particolare distribuzione in corso
supportato.
A ogni RandomVariableStream creato nella simulazione viene assegnato un generatore che è un nuovo
RNGStream dal PRNG sottostante. Utilizzato in questo modo, l'implementazione di L'Ecuyer
consente un massimo di 1.8x10^19 variabili casuali. Ogni variabile casuale in un singolo
la replicazione può produrre fino a 7.6x10^22 numeri casuali prima di sovrapporsi.
Tavola XY classe la percezione API
Di seguito sono riportati alcuni metodi pubblici della classe Flusso variabile casuale che accedono al
valore successivo nel sottoflusso.
/ **
* \brief Restituisce un valore double casuale dalla distribuzione sottostante
* \return Un valore casuale in virgola mobile
*/
doppio GetValue (void) const;
/ **
* \brief Restituisce un numero intero casuale dalla distribuzione sottostante
* \return Cast intero di ::GetValue()
*/
uint32_t GetInteger (void) const;
Abbiamo già descritto la configurazione di seeding sopra. Diverse variabili casuali
le sottoclassi possono avere API aggiuntive.
Tipi of Variabili casuali
Sono forniti i seguenti tipi di variabili casuali e sono documentati nel NS-3
Doxygen o leggendo src/core/model/random-variable-stream.h. Gli utenti possono anche creare
le proprie variabili casuali personalizzate derivandole dalla classe Flusso variabile casuale.
· classe VariabileCasualeUniforme
· classe VariabileCasualeCostante
· classe Variabile casuale sequenziale
· classe VariabileCasualeEsponenziale
· classe ParetoRandomVariable
· classe WeibullRandomVariable
· classe VariabileCasualeNormale
· classe Variabile casuale normale logaritmica
· classe VariabileCasualeGamma
· classe ErlangRandomVariable
· classe Variabile casuale triangolare
· classe ZipfRandomVariable
· classe ZetaRandomVariable
· classe VariabileCasualeDeterministica
· classe Variabile casuale empirica
Semantica of Flusso variabile casuale oggetti
Gli oggetti RandomVariableStream derivano da ns3::Oggetto e sono gestiti da puntatori intelligenti.
Le istanze RandomVariableStream possono essere utilizzate anche in NS-3 attributi, il che significa che
i valori possono essere impostati per loro tramite NS-3 sistema di attributi. Un esempio è nel
modelli di propagazione per WifiNetDevice:
ID tipo
RandomPropagationDelayModel::GetTypeId (vuoto)
{
TypeId statico tid = TypeId ("ns3::RandomPropagationDelayModel")
.ImpostaParente ()
.AddConstructor ()
.AddAttribute ("Variabile",
"La variabile casuale che genera ritardi casuali.",
StringValue ("ns3::UniformRandomVariable"),
MakePointerAccessor (&RandomPropagationDelayModel::m_variabile),
MakePointerChecker ())
;
ritorno a mare;
}
Qui, il NS-3 l'utente può modificare la variabile casuale predefinita per questo modello di ritardo (che è
una UniformRandomVariable che va da 0 a 1) tramite il sistema di attributi.
utilizzando Altri PRNG
Al momento non è disponibile alcun supporto per la sostituzione di un diverso numero casuale sottostante
generatore (ad esempio, la GNU Scientific Library o il pacchetto Akaroa). Le patch sono benvenute.
Configurazione , il ruscello numero
Il generatore MRG32k3a sottostante fornisce 2^64 flussi indipendenti. In ns-3, questi sono
assegnati in sequenza a partire dal primo flusso come nuove istanze RandomVariableStream
effettuano la loro prima chiamata a GetValue().
Come risultato del modo in cui questi oggetti RandomVariableStream vengono assegnati ai flussi sottostanti,
l'assegnazione è sensibile alle perturbazioni della configurazione della simulazione.
La conseguenza è che se un qualsiasi aspetto della configurazione della simulazione viene modificato, la mappatura
delle variabili casuali nei flussi possono (o non possono) cambiare.
Come esempio concreto, un utente che esegue uno studio comparativo tra protocolli di routing può
scoprire che l'atto di cambiare un protocollo di routing per un altro noterà che il
anche il modello di mobilità di base è cambiato.
A partire da ns-3.15, è stato fornito agli utenti un certo controllo per consentire loro di
facoltativamente, correggi l'assegnazione degli oggetti RandomVariableStream selezionati a quelli sottostanti
flussi. Questo è il Bacheca attributo, parte della classe base RandomVariableStream.
Suddividendo la sequenza esistente di flussi precedenti:
<-------------------------------------------------------------------------->
flusso 0 flusso (2^64 - 1)
in due insiemi di uguali dimensioni:
<-------------------------------------------------------------------------->
^ ^^ ^
| || |
flusso 0 flusso (2^63 - 1) flusso 2^63 flusso (2^64 - 1)
<- assegnato automaticamente -----------><- assegnato dall'utente ----------------->
I primi 2^63 flussi continuano ad essere assegnati automaticamente, mentre gli ultimi 2^63 sono
dati gli indici di flusso a partire da zero fino a 2^63-1.
L'assegnazione dei flussi a un numero di flusso fisso è facoltativa; istanze di
A RandomVariableStream a cui non è assegnato un valore di flusso verrà assegnato il successivo
uno dal pool di flussi automatici.
Per fissare un RandomVariableStream a un particolare flusso sottostante, assegnargli Bacheca
attributo a un numero intero non negativo (il valore predefinito di -1 significa che un valore sarà
assegnato automaticamente).
Edizioni il tuo sul risultato
Quando pubblichi i risultati della simulazione, un elemento chiave delle informazioni di configurazione che
dovrebbe sempre indicare come hai utilizzato il generatore di numeri casuali.
· quali semi hai usato,
· quale RNG hai utilizzato se non quello predefinito,
· come sono state eseguite le prove indipendenti,
· per le simulazioni di grandi dimensioni, come hai verificato di non aver eseguito alcun ciclo.
Spetta al ricercatore che pubblica i risultati includere informazioni sufficienti per
consentire ad altri di riprodurre i propri risultati. Spetta inoltre al ricercatore
convincersi che i numeri casuali utilizzati erano statisticamente validi, e affermare in
il documento spiega perché si dà per scontata tale fiducia.
Sintesi
Vediamo quali sono le cose da fare quando si crea una simulazione.
· Decidi se stai eseguendo un seed fisso o un seed casuale; un seed fisso è il
predefinito
· Decidere come gestire le repliche indipendenti, se applicabile,
· Convinciti che non stai estraendo più valori casuali della lunghezza del ciclo, se
stai eseguendo una simulazione molto lunga e
· Quando pubblichi, segui le linee guida sopra riportate per documentare l'uso del casuale
generatore di numeri
Hash funzioni
NS-3 fornisce un'interfaccia generica per funzioni hash di uso generale. Nella forma più semplice
utilizzo, la funzione hash restituisce l'hash a 32 o 64 bit di un buffer di dati o di una stringa.
La funzione hash sottostante predefinita è mormorio3, scelto perché ha una buona funzione hash
proprietà e offre una versione a 64 bit. Il venerabile FNV1a è disponibile anche l'hashish.
Esiste un meccanismo semplice per aggiungere (o fornire in fase di esecuzione) un hash alternativo
implementazioni di funzioni.
Basic Impiego
Il modo più semplice per ottenere un valore hash di un buffer di dati o di una stringa è semplicemente:
#include "ns3/hash.h"
usando lo spazio dei nomi ns3;
carattere * buffer = ...
dimensione_t dimensione_buffer = ...
uint32_t buffer_hash = Hash32 (buffer, dimensione_buffer);
std::stringa s;
uint32_t string_hash = Hash32 (s);
Per i valori hash a 64 bit sono definite funzioni equivalenti.
Incrementale hashing
In alcune situazioni è utile calcolare l'hash di più buffer, come se avessero
sono stati uniti insieme. (Ad esempio, potresti volere l'hash di un flusso di pacchetti, ma non
desidera assemblare un singolo buffer con il contenuto combinato di tutti i pacchetti.)
Questo è quasi altrettanto semplice del primo esempio:
#include "ns3/hash.h"
usando lo spazio dei nomi ns3;
carattere * buffer;
dimensione_t dimensione_buffer;
Hasher hash; // Utilizza la funzione hash predefinita
per ( )
{
buffer = ottieni_buffer_successivo ();
hasher (buffer, dimensione_buffer);
}
uint32_t hash_combinato = hasher.GetHash32 ();
Di default hasher conserva lo stato interno per abilitare l'hashing incrementale. Se vuoi
riutilizzare un hasher oggetto (ad esempio perché è configurato con un hash non predefinito
funzione), ma non si desidera aggiungere all'hash calcolato in precedenza, è necessario chiaro()
primo:
hasher.clear ().GetHash32 (buffer, dimensione_buffer);
In questo modo si reinizializza lo stato interno prima di eseguire l'hashing del buffer.
utilizzando an Alternative Hash Funzione
La funzione hash predefinita è mormorio3. FNV1a è anche disponibile. Per specificare l'hash
funzione in modo esplicito, utilizzare questo costruttore:
Hasher hasher = Hasher (Crea () );
Aggiunta New Hash Funzione implementazioni
Per aggiungere la funzione hash foo, Segui il hash-murmur3.h/. Cc modello:
· Creare una dichiarazione di classe (.h) e definizione (. Cc) ereditando da
Hash::Implementazione.
· includere la dichiarazione in hash.h (nel punto in cui hash-murmur3.h è incluso.
· Nel tuo codice, crea un'istanza di hasher oggetto tramite il costruttore hasher
(Parte ())
Se la tua funzione hash è una singola funzione, ad esempio hash, non hai nemmeno bisogno di creare un
nuova classe derivata da HashImplementation:
Hasher hasher =
Hasher (Crea (&hashf) );
Per compilare questo, il tuo hash deve corrispondere a una delle firme del puntatore di funzione:
typedef uint32_t (*Hash32Function_ptr) (const char *, const size_t);
typedef uint64_t (*Hash64Function_ptr) (const char *, const size_t);
fonti da Hash funzioni
Le fonti per altre implementazioni della funzione hash includono:
· Peter Kankowski: http://www.strchr.com
· Arash Partow: http://www.partow.net/programming/hashfunctions/index.html
· SMHasher: http://code.google.com/p/smhasher/
· Sanmayce: http://www.sanmayce.com/Fastest_Hash/index.html
Eventi e Simulatore
NS-3 è un simulatore di rete ad eventi discreti. Concettualmente, il simulatore tiene traccia di un
numero di eventi programmati per essere eseguiti in un momento di simulazione specificato. Il lavoro di
il simulatore deve eseguire gli eventi in ordine temporale sequenziale. Una volta completato
si verifica un evento, il simulatore passerà all'evento successivo (o uscirà se non ci sono
più eventi nella coda degli eventi). Se, ad esempio, un evento programmato per il tempo di simulazione
"100 secondi" viene eseguito e l'evento successivo non viene pianificato fino a "200 secondi", il
il simulatore passerà immediatamente da 100 secondi a 200 secondi (di tempo di simulazione) per
eseguire l'evento successivo. Questo è ciò che si intende per simulatore "a eventi discreti".
Per far sì che tutto ciò accada, il simulatore ha bisogno di alcune cose:
1. un oggetto simulatore che può accedere a una coda di eventi in cui sono memorizzati gli eventi e che può
gestire l'esecuzione degli eventi
2. uno scheduler responsabile dell'inserimento e della rimozione degli eventi dalla coda
3. un modo per rappresentare il tempo di simulazione
4. gli eventi stessi
In questo capitolo del manuale vengono descritti questi oggetti fondamentali (simulatore, scheduler,
tempo, evento) e come vengono utilizzati.
Evento
A be completato
Simulatore
La classe Simulator è il punto di ingresso pubblico per accedere alle strutture di pianificazione degli eventi. Una volta
sono stati programmati un paio di eventi per avviare la simulazione, l'utente può iniziare a
eseguirli entrando nel ciclo principale del simulatore (chiamata Simulatore::Esegui). Una volta che il ciclo principale
inizia a funzionare, eseguirà in sequenza tutti gli eventi pianificati in ordine dal più vecchio al
più recenti finché non ci sono più eventi rimasti nella coda degli eventi o
È stato chiamato Simulator::Stop.
Per pianificare gli eventi per l'esecuzione da parte del ciclo principale del simulatore, la classe Simulator fornisce
la famiglia di funzioni Simulator::Schedule*.
1. Gestione dei gestori di eventi con firme diverse
Queste funzioni sono dichiarate e implementate come modelli C++ per gestire automaticamente il
Ampia varietà di firme di gestori di eventi C++ utilizzate in natura. Ad esempio, per pianificare un
evento da eseguire 10 secondi in futuro e richiamare un metodo o una funzione C++ con
argomenti specifici, potresti scrivere questo:
gestore void (int arg0, int arg1)
{
std::cout << "gestore chiamato con argomento arg0=" << arg0 << " e
arg1=" << arg1 << std::endl;
}
Simulatore::Pianificazione(secondi(10), &gestore, 10, 5);
Che produrrà:
gestore chiamato con argomento arg0=10 e arg1=5
Naturalmente, questi modelli C++ possono anche gestire in modo trasparente i metodi membro su C++
oggetti:
A be completato: membro metodo esempio
Note:
· i metodi Schedule ns-3 riconoscono automaticamente funzioni e metodi solo se
accettare meno di 5 argomenti. Se hai bisogno di supportarne altri, per favore, presenta una
segnalazione di bug.
· I lettori che hanno familiarità con il termine "funtori completamente vincolati" riconosceranno il
Metodi Simulator::Schedule come metodo per costruire automaticamente tali oggetti.
2. Operazioni di pianificazione comuni
L'API del simulatore è stata progettata per semplificare al massimo la pianificazione della maggior parte degli eventi.
fornisce tre varianti per farlo (ordinate dalla più comunemente usata alla meno comunemente usata):
· Metodi di pianificazione che consentono di pianificare un evento in futuro fornendo il
ritardo tra l'ora di simulazione corrente e la data di scadenza dell'evento target.
· Metodi ScheduleNow che consentono di pianificare un evento per la simulazione corrente
tempo: verranno eseguiti _dopo_ che l'evento corrente avrà terminato l'esecuzione ma _prima_ del
l'orario della simulazione viene modificato per l'evento successivo.
· Metodi ScheduleDestroy che consentono di agganciarsi al processo di spegnimento del simulatore
per ripulire le risorse di simulazione: ogni evento 'destroy' viene eseguito quando l'utente chiama
il metodo Simulator::Destroy.
3. Mantenere il contesto della simulazione
Esistono due modi fondamentali per programmare gli eventi, con e senza contesto. Cosa fa questo
significa?
Simulator::Schedule (Time const &time, MEM mem_ptr, OBJ obj);
vs.
Simulator::ScheduleWithContext (uint32_t contesto, Tempo const &tempo, MEM mem_ptr, OBJ obj);
Lettori che investono tempo e impegno nello sviluppo o nell'utilizzo di un modello di simulazione non banale
conoscerà il valore del framework di registrazione ns-3 per eseguire il debug di simulazioni semplici e complesse
simili. Una delle caratteristiche importanti fornite da questo framework di registrazione è la
visualizzazione automatica dell'ID del nodo di rete associato all'evento 'attualmente' in esecuzione.
L'ID del nodo del nodo di rete attualmente in esecuzione viene infatti tracciato dal simulatore
classe. È possibile accedervi con il metodo Simulator::GetContext che restituisce
'contesto' (un intero a 32 bit) associato e memorizzato nell'evento attualmente in esecuzione. In
in alcuni rari casi, quando un evento non è associato a un nodo di rete specifico, il suo
'context' è impostato su 0xffffffff.
Per associare automaticamente un contesto a ciascun evento, i metodi Schedule e ScheduleNow
riutilizzare il contesto dell'evento attualmente in esecuzione come contesto dell'evento pianificato
per l'esecuzione successiva.
In alcuni casi, in particolare quando si simula la trasmissione di un pacchetto da un nodo a
un altro, questo comportamento è indesiderabile poiché il contesto previsto dell'evento di ricezione è
quello del nodo ricevente, non del nodo mittente. Per evitare questo problema, il simulatore
La classe fornisce un metodo di pianificazione specifico: ScheduleWithContext che consente di fornire
esplicitamente l'ID del nodo ricevente associato all'evento di ricezione.
Xxx: codice esempio
In alcuni casi molto rari, gli sviluppatori potrebbero aver bisogno di modificare o comprendere il contesto
(ID nodo) del primo evento viene impostato su quello del nodo associato. Ciò si ottiene
dalla classe NodeList: ogni volta che viene creato un nuovo nodo, la classe NodeList utilizza
ScheduleWithContext per pianificare un evento di inizializzazione per questo nodo. L'evento di inizializzazione
quindi viene eseguito con un contesto impostato su quello dell'ID del nodo e può utilizzare la normale varietà di
Metodi di pianificazione. Invoca il metodo Node::Initialize che propaga l'inizializzazione
evento chiamando il metodo DoInitialize per ogni oggetto associato al nodo. L'
Metodo DoInitialize sovrascritto in alcuni di questi oggetti (in particolare nell'applicazione
classe base) pianificherà alcuni eventi (in particolare Application::StartApplication) che
a sua volta programmerà eventi di generazione del traffico che a loro volta programmeranno
eventi a livello di rete.
Note:
· Gli utenti devono fare attenzione a propagare i metodi DoInitialize tra gli oggetti chiamando
Inizializzare esplicitamente sui loro oggetti membro
· L'ID contesto associato a ciascun metodo ScheduleWithContext ha altri usi oltre a
registrazione: viene utilizzato da un ramo sperimentale di ns-3 per eseguire simulazioni parallele su
sistemi multicore che utilizzano il multithreading.
Le funzioni Simulator::* non sanno qual è il contesto: si limitano a verificare che
qualunque contesto specificato con ScheduleWithContext è disponibile quando il corrispondente
l'evento viene eseguito con ::GetContext.
Spetta ai modelli implementati su Simulator::* interpretare il valore del contesto.
In ns-3, i modelli di rete interpretano il contesto come l'ID del nodo che
generato un evento. Ecco perché è importante chiamare ScheduleWithContext in
Sottoclassi ns3::Channel perché stiamo generando un evento dal nodo i al nodo j e noi
si vuole essere certi che l'evento che verrà eseguito sul nodo j abbia il contesto corretto.
Ora
A be completato
Scheduler
A be completato
callback
Alcuni nuovi utenti a NS-3 non hanno familiarità con un idioma di programmazione ampiamente utilizzato
in tutto il codice: il NS-3 richiamaQuesto capitolo fornisce alcune motivazioni su
callback, istruzioni su come utilizzarlo e dettagli sulla sua implementazione.
callback Motivazione
Considera di avere due modelli di simulazione A e B e desideri che passino
informazioni tra loro durante la simulazione. Un modo per farlo è che
può rendere A e B esplicitamente consapevoli l'uno dell'altro, in modo che possano invocare
metodi l'uno sull'altro:
classe A {
pubblico:
void ReceiveInput ( // parametri );
...
}
(in un altro file sorgente:)
classe B {
pubblico:
void FaiQualcosa (void);
...
privato:
A* a_instance; // puntatore a un A
}
nulla
B::FaiQualcosa()
{
// Comunica a a_instance che è successo qualcosa
a_instance->ReceiveInput ( // parametri);
...
}
Questo funziona sicuramente, ma ha lo svantaggio di introdurre una dipendenza da A e B
per conoscere l'altro in fase di compilazione (ciò rende più difficile avere indipendente
unità di compilazione nel simulatore) e non è generalizzato; se in uno scenario di utilizzo successivo,
B deve comunicare con un oggetto C completamente diverso, il codice sorgente per B deve essere
modificato per aggiungere un c_istanza e così via. È facile vedere che questa è una forza bruta
meccanismo di comunicazione che può portare a errori di programmazione nei modelli.
Ciò non significa che gli oggetti non debbano conoscersi a vicenda se esiste un legame rigido.
dipendenza tra loro, ma che spesso il modello può essere reso più flessibile se il suo
le interazioni sono meno vincolate in fase di compilazione.
Questo non è un problema astratto per la ricerca sulla simulazione di rete, ma piuttosto è stato un
fonte di problemi nei simulatori precedenti, quando i ricercatori vogliono estendere o modificare il
sistema per fare cose diverse (come sono soliti fare nella ricerca). Si consideri, ad esempio,
un utente che desidera aggiungere un sottolivello del protocollo di sicurezza IPsec tra TCP e IP:
------------ -----------
| TCP | | TCP |
------------ -----------
| diventa -> |
----------- -----------
| IP | | IPsec |
----------- -----------
|
-----------
| Indirizzo IP |
-----------
Se il simulatore ha fatto delle ipotesi e le ha codificate nel codice, quell'IP parla sempre
a un protocollo di trasporto di cui sopra, l'utente potrebbe essere costretto a hackerare il sistema per ottenere il
interconnessioni desiderate. Questo non è chiaramente un modo ottimale per progettare un generico
simulatore.
callback sfondo
NOTA:
I lettori che hanno familiarità con la programmazione delle callback possono saltare questa sezione del tutorial.
Il meccanismo di base che consente di affrontare il problema di cui sopra è noto come richiama.
L'obiettivo finale è consentire a un pezzo di codice di chiamare una funzione (o un metodo in C++)
senza alcuna dipendenza specifica tra i moduli.
Ciò significa che in definitiva è necessario un qualche tipo di indirezione: si tratta l'indirizzo del
chiamata funzione come variabile. Questa variabile è detta variabile puntatore a funzione.
La relazione tra funzione e puntatore-a-funzione non è in realtà diversa
che quello di oggetto e puntatore-a-oggetto.
In C l'esempio canonico di puntatore a funzione è a
puntatore-a-funzione-che-restituisce-un-intero (PFI). Per un PFI che accetta un parametro int, questo
potrebbe essere dichiarato come:
int(*pfi)(int arg) = 0;
Ciò che si ottiene da questo è una variabile denominata semplicemente PFI che viene inizializzato al valore 0.
Se vuoi inizializzare questo puntatore a qualcosa di significativo, devi avere un
funzione con una firma corrispondente. In questo caso:
int MiaFunzione (int arg) {}
Se hai questo target, puoi inizializzare la variabile in modo che punti alla tua funzione in questo modo:
pfi = MiaFunzione;
Puoi quindi chiamare MyFunction indirettamente utilizzando la forma più suggestiva della chiamata:
int risultato = (*pfi) (1234);
Questo è suggestivo poiché sembra che tu stia dereferenziando solo il puntatore alla funzione
come dereferenziare qualsiasi puntatore. In genere, tuttavia, le persone approfittano del
fatto che il compilatore sa cosa sta succedendo e utilizzerà solo una forma più breve:
int risultato = pfi (1234);
Si noti che il puntatore di funzione obbedisce alla semantica del valore, quindi è possibile passarlo in giro come qualsiasi
altro valore. In genere, quando si utilizza un'interfaccia asincrona, si passerà un'entità
in questo modo a una funzione che eseguirà un'azione e chiamata precedente per fartelo sapere
completato. Richiama seguendo l'indirezione ed eseguendo la funzione fornita.
In C++ hai la complessità aggiuntiva degli oggetti. L'analogia con il PFI di cui sopra significa che
avere un puntatore a una funzione membro che restituisce un int (PMI) invece del puntatore a
funzione che restituisce un int (PFI).
La dichiarazione della variabile che fornisce l'indirezione appare solo leggermente diversa:
int (MyClass::*pmi) (int arg) = 0;
Questo dichiara una variabile denominata pmi proprio come l'esempio precedente ha dichiarato una variabile denominata
PFI. Poiché si tratterà di chiamare un metodo di un'istanza di una classe particolare, è necessario
dichiarare quel metodo in una classe:
classe MyClass {
pubblico:
int MyMethod (int arg);
};
Data questa dichiarazione di classe, si inizializzerebbe la variabile in questo modo:
pmi = &MyClass::MyMethod;
Questo assegna l'indirizzo del codice che implementa il metodo alla variabile, completando
l'indirezione. Per chiamare un metodo, il codice ha bisogno di un questo puntatore. Questo, a sua volta,
significa che deve esserci un oggetto di MyClass a cui fare riferimento. Un esempio semplicistico di questo è semplicemente
chiamare un metodo indirettamente (pensa a una funzione virtuale):
int (MyClass::*pmi) (int arg) = 0; // Dichiara un PMI
pmi = &MyClass::MyMethod; // Indica il codice di implementazione
MyClass myClass; // Serve un'istanza della classe
(myClass.*pmi) (1234); // Chiama il metodo con un oggetto ptr
Proprio come nell'esempio C, puoi usarlo in una chiamata asincrona a un altro modulo
che sarà chiamata precedente utilizzando un metodo e un puntatore a un oggetto. L'estensione semplice
si potrebbe considerare di passare un puntatore all'oggetto e alla variabile PMI. Il modulo
farebbe semplicemente:
(*objectPtr.*pmi) (1234);
per eseguire il callback sull'oggetto desiderato.
A questo punto ci si potrebbe chiedere: ciò che è , il punto? Il modulo chiamato dovrà capire
il tipo concreto dell'oggetto chiamante per effettuare correttamente il callback. Perché no
accetta semplicemente questo, passa il puntatore all'oggetto digitato correttamente e fai oggetto->Metodo(1234) in
il codice invece del callback? Questo è esattamente il problema descritto sopra. Che cosa è
necessario è un modo per disaccoppiare completamente la funzione chiamante dalla classe chiamata. Questo
requisito ha portato allo sviluppo del funtore.
Un funtore è il risultato di qualcosa inventato negli anni '1960 chiamato chiusura. È
in pratica si tratta semplicemente di una chiamata di funzione preconfezionata, eventualmente con qualche stato.
Un funtore è costituito da due parti, una parte specifica e una parte generica, collegate tramite ereditarietà.
Il codice chiamante (il codice che esegue il callback) eseguirà un sovraccarico generico
operatore () di un funtore generico per causare la chiamata del callback. Il codice chiamato (il
il codice che vuole essere richiamato) dovrà fornire un'implementazione specializzata di
, il operatore () che esegue il lavoro specifico della classe che ha causato l'accoppiamento stretto
problema di cui sopra.
Con il funtore specifico e il suo sovraccarico operatore () creato, il codice chiamato quindi
fornisce il codice specializzato al modulo che eseguirà il callback (il chiamante
codice).
Il codice chiamante accetterà un funtore generico come parametro, quindi viene eseguito un cast implicito
nella chiamata di funzione per convertire il funtore specifico in un funtore generico. Ciò significa
che il modulo chiamante deve solo comprendere il tipo di funtore generico. È disaccoppiato
completamente dal codice chiamante.
Le informazioni necessarie per creare un funtore specifico sono il puntatore dell'oggetto e il
indirizzo puntatore-a-metodo.
L'essenza di ciò che deve accadere è che il sistema dichiari una parte generica del
funtore:
modello
classe Functor
{
pubblico:
operatore int virtuale() (T arg) = 0;
};
Il chiamante definisce una parte specifica del funtore che in realtà è lì solo per implementare
lo specifico operatore() Metodo:
modello
class SpecificFunctor: Funtore pubblico
{
pubblico:
SpecificFunctor(T* p, int (T::*_pmi)(ARG arg))
{
m_p = p;
m_pmi = _pmi;
}
operatore int virtuale() (ARG arg)
{
(*m_p.*m_pmi)(argomento);
}
privato:
int (T::*m_pmi)(ARG arg);
T* m_p;
};
Ecco un esempio di utilizzo:
classe A
{
pubblico:
A (int a0) : a (a0) {}
int Ciao (int b0)
{
std::cout << "Ciao da A, a = " << a << " b0 = " << b0 << std::endl;
}
int a;
};
int main ()
{
A a(10);
SpecificFunctor sf(&a, &A::Ciao);
sf(5);
}
NOTA:
Il codice precedente non è un vero codice ns-3. È un codice di esempio semplicistico utilizzato solo per
illustrare i concetti coinvolti e per aiutarti a comprendere meglio il sistema. Non
aspettatevi di trovare questo codice ovunque nell'albero ns-3.
Si noti che nella classe sopra sono definite due variabili. La variabile m_p è la
puntatore all'oggetto e m_pmi è la variabile contenente l'indirizzo della funzione da
eseguire.
Nota che quando operatore() viene chiamato, a sua volta chiama il metodo fornito con il
puntatore all'oggetto utilizzando la sintassi PMI C++.
Per utilizzare questo, si potrebbe quindi dichiarare un codice modello che accetta un funtore generico come
parametro:
void LibraryFunction (Functor functor);
Il codice che parlerà con il modello costruirà un funtore specifico e lo passerà a
Funzione di libreria:
La mia classe la mia classe;
SpecificFunctor functor (&myclass, MyClass::MyMethod);
Quando Funzione di libreria è fatto, esegue il callback utilizzando il operatore() sul generico
functor che è stato passato e, in questo caso particolare, fornisce l'argomento intero:
nulla
LibraryFunction (Functor functor)
{
// Esegui la funzione di libreria
funtore(1234);
}
Notare che Funzione di libreria è completamente disaccoppiato dalla tipologia specifica del cliente.
La connessione avviene tramite il polimorfismo del funtore.
L'API di callback in NS-3 implementa callback orientati agli oggetti utilizzando il meccanismo functor.
Questa API di callback, essendo basata su modelli C++, è sicura per i tipi; ovvero esegue operazioni statiche
controlli di tipo per garantire la corretta compatibilità delle firme tra chiamanti e chiamati. È
quindi più sicuro da usare rispetto ai puntatori di funzione tradizionali, ma la sintassi può
sembrano imponenti a prima vista. Questa sezione è progettata per guidarti attraverso il sistema di Callback
in modo che tu possa sentirti a tuo agio nell'usarlo NS-3.
utilizzando , il Richiamata API
L'API Callback è piuttosto minimale e fornisce solo due servizi:
1. dichiarazione del tipo di callback: un modo per dichiarare un tipo di callback con una data firma,
e,
2. istanziazione di callback: un modo per istanziare una callback di inoltro generata da un modello
che può inoltrare qualsiasi chiamata a un altro metodo membro della classe C++ o a una funzione C++.
Ciò si osserva meglio attraverso un esempio, basato su campioni/main-callback.cc.
utilizzando , il Richiamata API con statico funzioni
Consideriamo una funzione:
doppio statico
CbOne (doppia a, doppia b)
{
std::cout << "richiama cbOne a=" << a << ", b=" << b << std::endl;
restituisci un;
}
Si consideri anche il seguente frammento di programma principale:
int main (int argc, char * argv [])
{
// tipo di ritorno: doppio
// primo tipo di argomento: double
// secondo tipo di argomento: double
Richiamare uno;
}
Questo è un esempio di callback in stile C, che non include o non necessita di un questo
puntatore. Il modello di funzione Richiamata è essenzialmente la dichiarazione della variabile
contenente il puntatore alla funzione. Nell'esempio precedente, abbiamo mostrato esplicitamente un puntatore
a una funzione che restituiva un intero e accettava un singolo intero come parametro, The
Richiamata la funzione template è una versione generica di quella: viene utilizzata per dichiarare il tipo
di una richiamata.
NOTA:
I lettori che non hanno familiarità con i template C++ possono consultare
http://www.cplusplus.com/doc/tutorial/templates/.
. Richiamata il modello richiede un argomento obbligatorio (il tipo di ritorno della funzione a
essere assegnato a questo callback) e fino a cinque argomenti opzionali, ognuno dei quali specifica il
tipo degli argomenti (se la tua particolare funzione di callback ha più di cinque argomenti,
quindi questo può essere gestito estendendo l'implementazione del callback).
Quindi nell'esempio sopra, abbiamo dichiarato una callback denominata "uno" che alla fine
contenere un puntatore a funzione. La firma della funzione che conterrà deve restituire
double e deve supportare due argomenti double. Se si tenta di passare una funzione il cui
la firma non corrisponde alla callback dichiarata, si verificherà un errore di compilazione. Inoltre, se
si prova ad assegnare a una callback una incompatibile, la compilazione avrà successo ma un
Verrà generato un errore NS_FATAL_ERROR in fase di esecuzione. Il programma di esempio
src/core/examples/main-callback.cc dimostra entrambi questi casi di errore alla fine di
, il principale() .
Ora, dobbiamo collegare questa istanza di callback e la funzione di destinazione effettiva
(CbOne). Notare sopra che CbOne ha gli stessi tipi di firma della funzione del callback:
Questo è importante. Possiamo passare qualsiasi funzione correttamente tipizzata a questa callback.
Diamo un'occhiata più da vicino:
statico doppio CbUno (doppio a, doppio b) {}
^ ^ ^
| | |
| | |
Richiamare uno;
È possibile associare una funzione a una callback solo se hanno la firma corrispondente. Il primo
l'argomento template è il tipo restituito e gli argomenti template aggiuntivi sono i tipi
degli argomenti della firma della funzione.
Ora, colleghiamo la nostra callback "one" alla funzione che corrisponde alla sua firma:
// crea un'istanza di callback che punta alla funzione cbOne
uno = MakeCallback (&CbOne);
Questa chiamata a Effettua una richiamata è, in sostanza, la creazione di uno dei funtori specializzati
menzionato sopra. La variabile dichiarata utilizzando Richiamata la funzione template sta per
interpretare la parte del funtore generico. L'assegnazione prima = Effettua una richiamata (&CbOne) is
il cast che converte il funtore specializzato noto al chiamato in un funtore generico
noto al chiamante.
Successivamente, più avanti nel programma, se è necessario il callback, è possibile utilizzarlo come segue:
NS_ASSERT (!one.IsNull ());
// richiama la funzione cbOne tramite l'istanza di callback
doppio retUno;
retUno = uno (10.0, 20.0);
Il controllo per IsNull() assicura che il callback non sia nullo, ovvero che ci sia una funzione
per chiamare dietro questa callback. Quindi, uno() esegue il generico operatore() che è veramente
sovraccarico con una specifica implementazione di operatore() e restituisce lo stesso risultato come se
CbUno() era stato chiamato direttamente.
utilizzando , il Richiamata API con membro funzioni
In genere, non chiamerai funzioni statiche ma invece funzioni membro pubbliche di
un oggetto. In questo caso, è necessario un argomento aggiuntivo per la funzione MakeCallback, per
indicare al sistema su quale oggetto la funzione deve essere invocata. Consideriamo questo esempio,
anche da main-callback.cc:
classe MyCb {
pubblico:
int CbTwo (doppia a) {
std::cout << "invoca cbTwo a=" << a << std::endl;
return -5;
}
};
intero principale ()
{
...
// tipo di ritorno: int
// primo tipo di argomento: double
Richiamare due;
Il mio Cb cb;
// crea un'istanza di callback che punta a MyCb::cbTwo
due = MakeCallback (&MyCb::CbTwo, &cb);
...
}
Qui passiamo un puntatore oggetto aggiuntivo al MakeCallback<> funzione. Richiama da
la sezione di sfondo sopra quella Operatore() utilizzerà il puntatore alla sintassi del membro quando
viene eseguito su un oggetto:
operatore int virtuale() (ARG arg)
{
(*m_p.*m_pmi)(argomento);
}
E quindi abbiamo dovuto fornire le due variabili (m_p e m_pmi) quando abbiamo fatto lo specifico
funtore. La linea:
due = MakeCallback (&MyCb::CbTwo, &cb);
fa esattamente questo. In questo caso, quando Tutto () viene invocato:
int risultato = due (1.0);
si tradurrà in una chiamata a CbTwo funzione membro (metodo) sull'oggetto puntato da
&cb.
Costruzione Nullo callback
È possibile che i callback siano nulli; pertanto potrebbe essere opportuno verificarli prima di utilizzarli.
Esiste una struttura speciale per un callback nullo, che è preferibile al semplice passaggio
"0" come argomento; è il MakeNullCallback<> costruire:
due = MakeNullCallback ();
NS_ASSERT (due.IsNull ());
Invocare un callback nullo è come invocare un puntatore a funzione nullo: si bloccherà a
tempo di esecuzione.
Bound callback
Un'estensione molto utile al concetto di funtore è quella di un Bound Callback. In precedenza
è stato menzionato che le chiusure erano originariamente chiamate di funzione impacchettate per dopo
esecuzione. Si noti che in tutte le descrizioni di Callback sopra riportate, non c'è modo di
impacchettare tutti i parametri per un utilizzo successivo, quando Richiamata è chiamato tramite operatore().
Tutti i parametri vengono forniti dalla funzione chiamante.
Cosa succede se si desidera consentire alla funzione client (quella che fornisce il callback) di
fornire alcuni parametri? Alexandrescu chiama il processo di consentire a un cliente di
specificare uno dei parametri "legame"Uno dei parametri di operatore() è stata
vincolato (fissato) dal cliente.
Parte del nostro codice di tracciamento pcap ne fornisce un buon esempio. C'è una funzione che
deve essere chiamata ogni volta che viene ricevuto un pacchetto. Questa funzione chiama un oggetto che
scrive effettivamente il pacchetto su disco nel formato di file pcap. La firma di uno di questi
le funzioni saranno:
static void DefaultSink (Ptr file, Ptr P);
La parola chiave static significa che questa è una funzione statica che non necessita di un questo puntatore, quindi
utilizzerà callback in stile C. Non vogliamo che il codice chiamante debba saperlo
Tutto tranne il pacchetto. Quello che vogliamo nel codice chiamante è semplicemente una chiamata simile a questa:
m_promiscSnifferTrace (m_currentPkt);
Ciò che vogliamo fare è legare , il Ptr filetto alla callback specifica
implementazione quando viene creato e predisporre il operatore() del richiamo a
fornire gratuitamente quel parametro.
Forniamo il MakeBoundCallback funzione template per questo scopo. Ci vuole lo stesso
parametri come il Effettua una richiamata funzione template ma accetta anche i parametri da
vincolato. Nel caso dell'esempio precedente:
MakeBoundCallback (&DefaultSink, file);
creerà un'implementazione di callback specifica che sa aggiungere il limite extra
argomenti. Concettualmente, estende il funtore specifico descritto sopra con uno o più
argomenti vincolati:
modello
class SpecificFunctor: Funtore pubblico
{
pubblico:
SpecificFunctor(T* p, int (T::*_pmi)(ARG arg), BOUND_ARG boundArg)
{
m_p = p;
m_pmi = pmi;
m_boundArg = boundArg;
}
operatore int virtuale() (ARG arg)
{
(*m_p.*m_pmi)(m_boundArg, arg);
}
privato:
void (T::*m_pmi)(ARG arg);
T* m_p;
BOUND_ARG m_boundArg;
};
Si può vedere che quando viene creato il funtore specifico, l'argomento vincolato viene salvato nel
oggetto functor/callback stesso. Quando il operatore() viene invocato con il singolo
parametro, come in:
m_promiscSnifferTrace (m_currentPkt);
l'attuazione di operatore() aggiunge il parametro vincolato nella chiamata di funzione effettiva:
(*m_p.*m_pmi)(m_boundArg, arg);
È possibile anche associare due o tre argomenti. Supponiamo di avere una funzione con
firma:
static void NotifyEvent (Ptr a, Ptr b, MyEventType e);
È possibile creare un callback vincolato che vincola i primi due argomenti come:
MakeBoundCallback (&NotifyEvent, a1, b1);
supponendo a1 e b1 sono oggetti di tipo A e B rispettivamente. Allo stesso modo per tre
argomenti che si potrebbero avere in funzione con una firma:
static void NotifyEvent (Ptr a, Ptr b, MyEventType e);
L'associazione di tre argomenti è completata con:
MakeBoundCallback (&NotifyEvent, a1, b1, c1);
di nuovo assumendo a1, b1 e c1 sono oggetti di tipo A, B e C rispettivamente.
Questo tipo di legame può essere utilizzato per lo scambio di informazioni tra oggetti nella simulazione;
in particolare, i callback vincolati possono essere utilizzati come callback tracciati, che saranno descritti in
la sezione successiva.
Tracciato callback
segnaposto sottosezione
Richiamata posizioni in NS-3
Dove vengono utilizzati frequentemente i callback NS-3? Ecco alcuni dei più visibili
utenti tipici:
· API socket
· API di livello 2/livello 3
· Sottosistema di tracciamento
· API tra sottosistemi IP e di routing
Implementazione/Attuazione dettagli
I frammenti di codice sopra sono semplicistici e progettati solo per illustrare il meccanismo
stesso. Il codice di callback effettivo è piuttosto complicato e molto intenso nei template e un
Non è richiesta una conoscenza approfondita del codice. Se interessati, gli utenti esperti potrebbero trovare
seguente utile.
Il codice è stato originariamente scritto sulla base delle tecniche descritte in
http://www.codeproject.com/cpp/TTLFunction.aspSuccessivamente è stato riscritto per seguire
l'architettura delineata in Moderno C++ Design Generico Programmazione e Progettazione Modelli
Applicato, Alexandrescu, capitolo 5, generalizzato Funtori.
Questo codice utilizza:
· parametri del modello predefiniti per evitare agli utenti di dover specificare parametri vuoti quando
il numero di parametri è inferiore al numero massimo supportato
· l'idioma pimpl: la classe Callback viene passata in giro per valore e delega il fulcro di
il lavoro al suo puntatore brufoloso.
· possono essere utilizzate due implementazioni pimpl che derivano da CallbackImpl FunctorCallbackImpl
con qualsiasi tipo di funtore mentre MemPtrCallbackImpl può essere utilizzato con puntatori al membro
funzioni.
· un'implementazione dell'elenco di riferimento per implementare la semantica del valore del Callback.
Questo codice si discosta in modo notevole dall'implementazione di Alexandrescu in quanto non
utilizzare elenchi di tipi per specificare e passare i tipi degli argomenti di callback. Naturalmente,
inoltre non utilizza la semantica di distruzione della copia e si basa su un elenco di riferimento piuttosto che
autoPtr per mantenere il puntatore.
Oggetto modello
NS-3 è fondamentalmente un sistema di oggetti C++. Gli oggetti possono essere dichiarati e istanziati come
di solito, secondo le regole del C++. NS-3 aggiunge anche alcune funzionalità agli oggetti C++ tradizionali, come
descritto di seguito, per fornire maggiori funzionalità e caratteristiche. Questo capitolo del manuale è
destinato a introdurre il lettore alla NS-3 modello di oggetto.
Questa sezione descrive la progettazione della classe C++ per NS-3 oggetti. In breve, diversi design
I modelli in uso includono il design classico orientato agli oggetti (interfacce polimorfiche e
implementazioni), separazione di interfaccia e implementazione, il pubblico non virtuale
modello di progettazione dell'interfaccia, una funzione di aggregazione di oggetti e conteggio dei riferimenti per
gestione della memoria. Chi ha familiarità con modelli di componenti come COM o Bonobo
riconoscere gli elementi del design nel NS-3 modello di aggregazione degli oggetti, sebbene il NS-3
il design non è strettamente conforme a nessuno dei due.
Orientato agli oggetti comportamento
Gli oggetti C++, in generale, forniscono funzionalità comuni orientate agli oggetti (astrazione,
incapsulamento, ereditarietà e polimorfismo) che fanno parte della classica programmazione orientata agli oggetti
progettazione. NS-3 gli oggetti sfruttano queste proprietà; ad esempio:
indirizzo di classe
{
pubblico:
Indirizzo ();
Indirizzo (tipo uint8_t, const uint8_t *buffer, uint8_t len);
Indirizzo (const Indirizzo & indirizzo);
Indirizzo &operatore = (const Indirizzo &indirizzo);
...
privato:
uint8_t m_type;
uint8_t m_len;
...
};
Oggetto base classi
Ci sono tre classi di base speciali utilizzate in NS-3. Classi che ereditano da queste basi
Le classi possono istanziare oggetti con proprietà speciali. Queste classi base sono:
· classe Oggetto
· classe BaseOggetto
· classe SimpleRefCount
Non è richiesto che NS-3 gli oggetti ereditano da queste classi, ma quelli che lo fanno ottengono
proprietà speciali. Classi derivanti dalla classe Oggetto ottenere le seguenti proprietà.
· il NS-3 sistema di tipi e attributi (vedi Attributi)
· un sistema di aggregazione di oggetti
· un sistema di conteggio dei riferimenti a puntatore intelligente (classe Ptr)
Classi che derivano dalla classe BaseOggetto ottenere le prime due proprietà sopra, ma non
ottenere puntatori intelligenti. Classi che derivano dalla classe SimpleRefCount: ottieni solo il
sistema di conteggio dei riferimenti smart-pointer.
In pratica, la classe Oggetto è la variante delle tre sopra che il NS-3 lo sviluppatore lo farà
che si incontrano più comunemente.
Memorie e classe Ptr
La gestione della memoria in un programma C++ è un processo complesso e spesso viene eseguito in modo errato o
in modo incoerente. Abbiamo optato per un modello di conteggio dei riferimenti descritto come segue.
Tutti gli oggetti che utilizzano il conteggio dei riferimenti mantengono un conteggio dei riferimenti interno per determinare
quando un oggetto può eliminarsi in sicurezza. Ogni volta che si ottiene un puntatore a un
interfaccia, il conteggio dei riferimenti dell'oggetto viene incrementato chiamando Rif(). È il
obbligo dell'utilizzatore del puntatore di indicare esplicitamente Unref() il puntatore quando hai finito. Quando
se il conteggio dei riferimenti scende a zero, l'oggetto viene eliminato.
· Quando il codice client ottiene un puntatore dall'oggetto stesso tramite la creazione dell'oggetto,
oppure tramite GetObject, non è necessario incrementare il conteggio dei riferimenti.
· Quando il codice client ottiene un puntatore da un'altra fonte (ad esempio, copiando un puntatore) deve
chiamata Rif() per incrementare il conteggio dei riferimenti.
· Tutti gli utenti del puntatore dell'oggetto devono chiamare Unref() per rilasciare il riferimento.
L'onere della chiamata Unref() è in qualche modo alleviato dall'uso del conteggio dei riferimenti
classe di puntatori intelligenti descritta di seguito.
Utenti che utilizzano un'API di basso livello che desiderano allocare esplicitamente oggetti non conteggiati per riferimento
sull'heap, utilizzando l'operatore new, sono responsabili dell'eliminazione di tali oggetti.
Referenze conteggio smart pointer (Parte)
chiamata Rif() e Unref() tutto il tempo sarebbe ingombrante, quindi NS-3 fornisce una smart
classe puntatore Ptr simile a Boost::intrusive_ptrQuesta classe di puntatori intelligenti presuppone che
il tipo sottostante fornisce una coppia di arbitro e Non rif. metodi che ci si aspetta
incrementa e decrementa il refcount interno dell'istanza dell'oggetto.
Questa implementazione consente di manipolare il puntatore intelligente come se fosse un normale
puntatore: puoi confrontarlo con zero, confrontarlo con altri puntatori, assegnare zero a
esso, ecc.
È possibile estrarre il puntatore grezzo da questo puntatore intelligente con il OttieniPuntatore()
e Puntatore di osservazione() metodi.
Se si desidera memorizzare un nuovo oggetto in un puntatore intelligente, si consiglia di utilizzare
Funzioni modello CreateObject per creare l'oggetto e memorizzarlo in un puntatore intelligente a
evitare perdite di memoria. Queste funzioni sono davvero piccole funzioni di comodità e il loro obiettivo
serve solo a risparmiarti un po' di tempo di digitazione.
CreateObject e Creare
Gli oggetti in C++ possono essere creati staticamente, dinamicamente o automaticamente. Questo vale
da NS-3 inoltre, alcuni oggetti nel sistema hanno a disposizione alcuni framework aggiuntivi.
In particolare, gli oggetti conteggiati per riferimento vengono solitamente assegnati utilizzando un modello Crea o
Metodo CreateObject, come segue.
Per gli oggetti derivanti dalla classe Oggetto:
Ptr dispositivo = CreaOggetto ();
Si prega di non creare tali oggetti utilizzando operatore nuovi; crearli usando CreaOggetto()
anziché.
Per gli oggetti derivanti dalla classe SimpleRefCount, o altri oggetti che supportano l'utilizzo del
classe puntatore intelligente, è disponibile una funzione di supporto basata su modelli e se ne consiglia l'uso:
Ptr b = Crea ();
Questo è semplicemente un wrapper attorno all'operatore new che gestisce correttamente il conteggio dei riferimenti
.
In sintesi, utilizzare Creare se B non è un oggetto ma utilizza solo il conteggio dei riferimenti (ad esempio
Pacchetto) e utilizzare CreaOggetto se B deriva da ns3::Oggetto.
Aggregazione
. NS-3 Il sistema di aggregazione degli oggetti è motivato in gran parte dal riconoscimento che un
caso d'uso comune per NS-2 è stato l'uso dell'ereditarietà e del polimorfismo per estendere
modelli di protocollo. Ad esempio, versioni specializzate di TCP come RenoTcpAgent derivano
dalla classe TcpAgent (e sovrascrivere le funzioni dalla classe TcpAgent).
Tuttavia, due problemi sono emersi nel NS-2 modello sono downcast e "base debole"
classe." Il downcasting si riferisce alla procedura di utilizzo di un puntatore di classe base a un oggetto e
interrogandolo in fase di esecuzione per scoprire informazioni sul tipo, utilizzato per eseguire il cast esplicito del puntatore
a un puntatore di sottoclasse in modo che l'API della sottoclasse possa essere utilizzata. La classe base debole si riferisce a
problemi che sorgono quando una classe non può essere riutilizzata efficacemente (derivata da) perché
manca delle funzionalità necessarie, portando lo sviluppatore a dover modificare la classe base e
causando la proliferazione di chiamate API di classe base, alcune delle quali potrebbero non essere semanticamente
corretto per tutte le sottoclassi.
NS-3 utilizza una versione del modello di progettazione dell'interfaccia di query per evitare questi problemi.
Questo design si basa su elementi del Componente Oggetto Modello e GNOME Bonobo sebbene
la compatibilità completa a livello binario dei componenti sostituibili non è supportata e abbiamo
ha cercato di semplificare la sintassi e l'impatto sugli sviluppatori di modelli.
Esempi
Aggregazione esempio
Nodo è un buon esempio dell'uso dell'aggregazione in NS-3. Nota che non ci sono derivati
classi di nodi in NS-3 come la classe Nodo Internet. Invece, i componenti (protocolli) sono
aggregati a un nodo. Vediamo come alcuni protocolli IPv4 vengono aggiunti a un nodo:
vuoto statico
AggiungiIpv4Stack(Ptr nodo)
{
Ptr ipv4 = CreaOggetto ();
ipv4->SetNode (nodo);
nodo->AggregateObject (ipv4);
Ptr ipv4Impl = CreaOggetto ();
ipv4Impl->SetIpv4 (ipv4);
nodo->AggregateObject (ipv4Impl);
}
Si noti che i protocolli IPv4 vengono creati utilizzando CreaOggetto(). Quindi, vengono aggregati
al nodo. In questo modo, la classe base del nodo non deve essere modificata per consentire agli utenti
con un puntatore Node di classe base per accedere all'interfaccia IPv4; gli utenti possono chiedere al nodo un
puntatore alla sua interfaccia IPv4 in fase di esecuzione. Il modo in cui l'utente chiede al nodo è descritto nel
sottosezione successiva.
Si noti che è un errore di programmazione aggregare più di un oggetto dello stesso tipo per
an ns3::OggettoQuindi, ad esempio, l'aggregazione non è un'opzione per archiviare tutti i
socket attivi di un nodo.
OttieniOggetto esempio
GetObject è un modo sicuro per ottenere un downcasting sicuro e per consentire alle interfacce di essere
trovato su un oggetto.
Consideriamo un puntatore a nodo m_nodo che punta a un oggetto Node che ha un'implementazione di
IPv4 precedentemente aggregato ad esso. Il codice client desidera configurare una rotta predefinita. Per
per farlo, deve accedere a un oggetto all'interno del nodo che ha un'interfaccia per l'inoltro IP
configurazione. Esegue le seguenti operazioni:
Ptr ipv4 = m_node->GetObject ();
Se il nodo in effetti non ha un oggetto Ipv4 aggregato ad esso, allora il metodo
restituisce null. Pertanto, è buona norma controllare il valore di ritorno da tale funzione
chiamata. In caso di successo, l'utente può ora utilizzare il Ptr per l'oggetto Ipv4 che era precedentemente
aggregati al nodo.
Un altro esempio di come si potrebbe usare l'aggregazione è quello di aggiungere modelli opzionali agli oggetti. Per
Ad esempio, un oggetto Nodo esistente può avere un oggetto "Modello energetico" aggregato ad esso
tempo di esecuzione (senza modificare e ricompilare la classe del nodo). Un modello esistente (come un
dispositivo di rete wireless) può quindi successivamente "GetObject" per il modello energetico e agire di conseguenza
se l'interfaccia è stata integrata nell'oggetto Node sottostante o aggregata a
in fase di esecuzione. Tuttavia, gli altri nodi non hanno bisogno di sapere nulla sui modelli energetici.
Ci auguriamo che questa modalità di programmazione richieda molto meno modifiche da parte degli sviluppatori
le classi di base.
Oggetto fabbriche
Un caso d'uso comune è quello di creare molti oggetti configurati in modo simile. Si può ripetutamente
chiamata CreaOggetto() ma esiste anche un modello di progettazione di fabbrica in uso nel NS-3 .
È ampiamente utilizzato nell'API "helper".
Classe Fabbrica di oggetti può essere utilizzato per istanziare oggetti e per configurare gli attributi su
quegli oggetti:
void SetTypeId (TypeId tid);
void Set (std::string nome, const AttributeValue &valore);
Ptr Crea (void) const;
Il primo metodo consente di utilizzare il NS-3 Sistema TypeId per specificare il tipo di oggetti
creato. Il secondo consente di impostare gli attributi sugli oggetti da creare, e il
il terzo consente di creare gli oggetti stessi.
Per esempio:
Fabbrica ObjectFactory;
// Fai in modo che questa fabbrica crei oggetti di tipo FriisPropagationLossModel
factory.SetTypeId ("ns3::FriisPropagationLossModel")
// Fai in modo che questo oggetto di fabbrica modifichi il valore predefinito di un attributo, per
// oggetti creati successivamente
factory.Set ("SystemLoss", DoubleValue (2.0));
// Crea uno di questi oggetti
Ptr oggetto = fabbrica.Crea ();
factory.Set ("SystemLoss", DoubleValue (3.0));
// Crea un altro oggetto con un SystemLoss diverso
Ptr oggetto = fabbrica.Crea ();
Abbassamento
Una domanda che è sorta più volte è: "Se ho un puntatore di classe base (Ptr) a un
oggetto e voglio il puntatore alla classe derivata, dovrei effettuare il downcast (tramite cast dinamico C++) in
ottenere il puntatore derivato, oppure dovrei usare il sistema di aggregazione degli oggetti per OttieniOggetto<> ()
per trovare un Ptr all'interfaccia verso l'API della sottoclasse?"
La risposta è che in molte situazioni entrambe le tecniche funzionano. NS-3 fornisce un
funzione modello per rendere la sintassi del casting dinamico di oggetti molto più intuitiva
amichevole:
modello
Ptr
DynamicCast (Ptr costante&p)
{
ritorno Ptr (cast dinamico (PeekPointer (p)));
}
DynamicCast funziona quando il programmatore ha un puntatore di tipo base e sta testando contro un
puntatore di sottoclasse. GetObject funziona quando si cercano diversi oggetti aggregati, ma anche
funziona con le sottoclassi, allo stesso modo di DynamicCast. In caso di dubbi, il programmatore dovrebbe
utilizzare GetObject, poiché funziona in tutti i casi. Se il programmatore conosce la gerarchia delle classi di
l'oggetto in esame, è più diretto utilizzare semplicemente DynamicCast.
Configurazione e Attributi
In NS-3 simulazioni, ci sono due aspetti principali nella configurazione:
· La topologia della simulazione e il modo in cui gli oggetti sono collegati.
· I valori utilizzati dai modelli istanziati nella topologia.
Questo capitolo si concentra sul secondo punto sopra: come i molti valori in uso in NS-3 sono
organizzato, documentato e modificabile da NS-3 utenti. Il NS-3 il sistema di attributi è anche il
fondamento del modo in cui tracce e statistiche vengono raccolte nel simulatore.
Nel corso di questo capitolo discuteremo i vari modi per impostare o modificare i valori
utilizzato da NS-3 oggetti modello. In ordine crescente di specificità, questi sono:
? ?
│Metodo │ Ambito │
? ?
│Impostazione dei valori degli attributi predefiniti │ Influenza tutte le istanze di │
│quando gli attributi sono definiti nella classe │. │
│Ottieni IDTipo (). │ │
? ?
│Riga di comando │ Influisce su tutte le istanze future. │
│Config::SetDefault() │ │
│ConfigStore │ │
? ?
│Fabbrica di oggetti │ Influisce su tutte le istanze create │
│ │ con la fabbrica. │
? ?
│XHelperSetAttribute () │ Riguarda tutte le istanze create da │
│ │ l'aiutante. │
? ?
│MyClass::SetX () │ Modifica questa particolare istanza. │
│Oggetto::ImpostaAttributo () │ Generalmente questa è l'unica forma │
│Config::Set() │ che può essere programmato per modificare │
│ │ un'istanza una volta che la simulazione │
│ │ sta correndo. │
? ?
Per "specificità" intendiamo che i metodi nelle righe successive della tabella sovrascrivono i valori impostati
e in genere interessano meno istanze rispetto ai metodi precedenti.
Prima di addentrarci nei dettagli del sistema di valori degli attributi, sarà utile rivedere alcuni
proprietà di base della classe Oggetto.
Oggetto Panoramica
NS-3 è fondamentalmente un sistema basato su oggetti C++. Con questo intendiamo che le nuove classi C++
(i tipi) possono essere dichiarati, definiti e sottoclassati come di consueto.
Molti NS-3 gli oggetti ereditano da Oggetto classe base. Questi oggetti hanno alcune caratteristiche aggiuntive
proprietà che sfruttiamo per organizzare il sistema e migliorare la gestione della memoria
dei nostri oggetti:
· Sistema di "metadati" che collega il nome della classe a molte meta-informazioni sulla
oggetto, tra cui:
· La classe base della sottoclasse,
· L'insieme dei costruttori accessibili nella sottoclasse,
· L'insieme degli "attributi" della sottoclasse,
· Se ogni attributo può essere impostato o è di sola lettura,
· L'intervallo di valori consentito per ciascun attributo.
· Implementazione di puntatori intelligenti per il conteggio dei riferimenti, per la gestione della memoria.
NS-3 gli oggetti che utilizzano il sistema di attributi derivano da entrambi Oggetto or BaseOggetto. Più
NS-3 gli oggetti di cui parleremo derivano da Oggetto, ma alcuni che sono fuori dalla smart
framework di gestione della memoria dei puntatori deriva da BaseOggetto.
Esaminiamo un paio di proprietà di questi oggetti.
Smart Puntatori
Come introdotto nel NS-3 tutorial, NS-3 gli oggetti sono gestiti dalla memoria di un riferimento
conteggio smart pointer implementazione, classe Ptr.
I puntatori intelligenti sono ampiamente utilizzati nel NS-3 API, per evitare di passare riferimenti a
oggetti allocati nell'heap che possono causare perdite di memoria. Per l'utilizzo più elementare (sintassi), trattare
un puntatore intelligente come un puntatore normale:
Ptr nd = ...;
nd->ChiamaQualcheFunzione ();
// ecc.
Come si ottiene quindi un puntatore intelligente a un oggetto, come nella prima riga di questo esempio?
CreateObject
Come abbiamo discusso sopra in Memory-management-and-class-Ptr, all'API di livello più basso, gli oggetti
di tipo Oggetto non sono istanziati utilizzando operatore nuovi come al solito ma invece da un modello
funzione chiamata CreateObject ().
Un modo tipico per creare un oggetto di questo tipo è il seguente:
Ptr nd = CreaOggetto ();
Si può pensare che questo sia funzionalmente equivalente a:
Dispositivo di rete Wifi* nd = nuovo dispositivo di rete Wifi ();
Oggetti che derivano da Oggetto deve essere allocato sull'heap utilizzando CreateObject (). quelli
derivante da BaseOggetto, come NS-3 funzioni di supporto e intestazioni e trailer dei pacchetti,
può essere allocato sullo stack.
In alcuni script, potresti non vedere molto CreateObject () chiamate nel codice; questo è
perché ci sono alcuni oggetti helper in effetti che stanno facendo il CreateObject () chiamate
per voi.
ID tipo
NS-3 classi che derivano dalla classe Oggetto può includere una classe di metadati chiamata ID tipo che
registra meta-informazioni sulla classe, da utilizzare nell'aggregazione degli oggetti e nei componenti
sistemi di gestione:
· Una stringa univoca che identifica la classe.
· La classe base della sottoclasse, all'interno del sistema di metadati.
· L'insieme dei costruttori accessibili nella sottoclasse.
· Un elenco delle proprietà accessibili al pubblico ("attributi") della classe.
Oggetto Sintesi
Mettendo insieme tutti questi concetti, diamo un'occhiata a un esempio specifico: classe Nodo.
Il file di intestazione pubblico nodo.h ha una dichiarazione che include uno statico Ottieni IDTipo ()
chiamata di funzione:
classe Nodo: Oggetto pubblico
{
pubblico:
TypeId statico GetTypeId (vuoto);
...
Ciò è definito nel node.cc file come segue:
ID tipo
Node::GetTypeId (vuoto)
{
TypeId statico tid = TypeId ("ns3::Node")
.ImpostaParente ()
.AddConstructor ()
.AddAttribute ("Elenco dispositivi",
"L'elenco dei dispositivi associati a questo nodo.",
ObjectVectorValue (),
MakeObjectVectorAccessor (&Node::m_devices),
MakeObjectVectorChecker ())
.AddAttribute ("ElencoApplicazioni",
"L'elenco delle applicazioni associate a questo nodo.",
ObjectVectorValue (),
MakeObjectVectorAccessor (&Node::m_applications),
MakeObjectVectorChecker ())
.AddAttribute ("Id",
"L'ID (numero intero univoco) di questo nodo.",
TypeId::ATTR_GET, // consente solo di ottenerlo.
Valore intero (0),
MakeUintegerAccessor (&Nodo::m_id),
MakeUintegerChecker ())
;
ritorno a mare;
}
Tieni a mente l' ID tipo di NS-3 Oggetto classe come forma estesa del tipo runtime
informazioni (RTTI). Il linguaggio C++ include un semplice tipo di RTTI per supportare
dynamic_cast e tipoid gli operatori.
. Imposta genitore () chiamata nella definizione di cui sopra è usata in congiunzione con la nostra
meccanismi di aggregazione di oggetti per consentire un upcasting e un downcasting sicuri negli alberi di ereditarietà
durante OttieniOggetto (). Consente inoltre alle sottoclassi di ereditare gli attributi del loro genitore
classe.
. Aggiungi Costruttore () la chiamata viene utilizzata insieme alla nostra fabbrica di oggetti astratti
meccanismi che ci consentono di costruire oggetti C++ senza costringere un utente a conoscere il
classe concreta dell'oggetto che sta costruendo.
Le tre chiamate a Aggiungi attributo () associare una stringa data con un valore fortemente tipizzato in
la classe. Nota che devi fornire una stringa di aiuto che può essere visualizzata, ad esempio,
via processori della riga di comando. Ogni Attributo è associato a meccanismi di accesso
la variabile membro sottostante nell'oggetto (ad esempio, MakeUintegerAccessor () dice
il generico Attributo codice su come arrivare all'ID del nodo sopra). Ci sono anche "Checker"
metodi che vengono utilizzati per convalidare i valori rispetto alle limitazioni di intervallo, come massimo e
valori minimi consentiti.
Quando gli utenti vogliono creare nodi, solitamente chiamano una qualche forma di CreateObject (),:
Ptr n = CreaOggetto ();
o più astrattamente, utilizzando una fabbrica di oggetti, è possibile creare un Nodo oggetto senza nemmeno
conoscendo il tipo C++ concreto:
Fabbrica ObjectFactory;
const std::string typeId = "ns3::Node'';
factory.SetTypeId (typeId);
Ptr nodo = fabbrica.Crea ();
Entrambi questi metodi determinano che gli attributi completamente inizializzati siano disponibili nel
risultante Oggetto le istanze.
Successivamente discutiamo come gli attributi (valori associati alle variabili membro o alle funzioni di
la classe) sono inseriti in quanto sopra ID tipo.
Attributi
L'obiettivo del sistema di attributi è organizzare l'accesso degli oggetti membri interni di un
simulazione. Questo obiettivo nasce perché, in genere nella simulazione, gli utenti taglieranno e
incollare/modificare gli script di simulazione esistenti o utilizzare costrutti di simulazione di livello superiore,
ma spesso saranno interessati a studiare o tracciare particolari variabili interne. Per
ad esempio, casi d'uso come:
· "I volere a tracciare , il pacchetti on , il senza fili interfaccia esclusivamente on , il prima di tutto accesso punto."
· "I volere a tracciare , il APPREZZIAMO of , il TCP congestione finestra (ogni tempo it i cambiamenti) on a
particolare TCP PRESA."
· "I volere a cumulo di rifiuti of contro tutti i valori che sono stati utilizzato in my simulazione."
Allo stesso modo, gli utenti potrebbero desiderare un accesso dettagliato alle variabili interne nella simulazione, oppure
potrebbe voler modificare ampiamente il valore iniziale utilizzato per un parametro particolare in tutti
oggetti creati successivamente. Infine, gli utenti potrebbero voler sapere quali variabili sono impostabili
e recuperabile in una configurazione di simulazione. Questo non vale solo per la simulazione diretta
interazione sulla riga di comando; considerare anche un'interfaccia utente grafica (futura) che
vorrei essere in grado di fornire una funzionalità tramite la quale un utente potrebbe fare clic con il pulsante destro del mouse su un nodo su
la tela e vedere un elenco gerarchico e organizzato di parametri che possono essere impostati su
nodo e i suoi oggetti membri costituenti, nonché testo di aiuto e valori predefiniti per ciascuno
parametro.
Definizione Attributi
Forniamo agli utenti un modo per accedere ai valori in profondità nel sistema, senza dover effettuare scandagli
accessor (puntatori) attraverso il sistema e percorrere le catene di puntatori per raggiungerli. Si consideri un
classe DropTailQueue che ha una variabile membro che è un intero senza segno m_maxPacchetti;
questa variabile membro controlla la profondità della coda.
Se guardiamo alla dichiarazione di DropTailQueue, vediamo quanto segue:
classe DropTailQueue : coda pubblica {
pubblico:
TypeId statico GetTypeId (vuoto);
...
privato:
std::coda > m_pacchetti;
uint32_t m_maxPacchetti;
};
Consideriamo le cose che un utente potrebbe voler fare con il valore di m_maxPacchetti:
· Impostare un valore predefinito per il sistema, in modo che ogni volta che viene visualizzato un nuovo DropTailQueue è creato,
questo membro è inizializzato a quel valore predefinito.
· Imposta o ottieni il valore su una coda già istanziata.
Le cose di cui sopra in genere richiedono di fornire Impostato () e Ottieni () funzioni e qualche tipo di
valore predefinito globale.
Nel NS-3 sistema di attributi, queste definizioni di valore e registrazioni di funzioni di accesso
vengono spostati nel ID tipo classe; per esempio.:
NS_OBJECT_ENSURE_REGISTERED (DropTailQueue);
ID tipo
DropTailQueue::GetTypeId (vuoto)
{
TypeId statico tid = TypeId ("ns3::DropTailQueue")
.ImpostaParente ()
.AddConstructor ()
.AddAttribute ("MaxPackets",
"Il numero massimo di pacchetti accettati da questa DropTailQueue.",
Valore intero (100),
MakeUintegerAccessor (&DropTailQueue::m_maxPackets),
MakeUintegerChecker ())
;
ritorno a mare;
}
. Aggiungi attributo () il metodo esegue una serie di cose per il m_maxPacchetti valore:
· Associazione della variabile membro (solitamente privata) m_maxPacchetti a una stringa pubblica
"MaxPackets".
· Fornire un valore predefinito (100 pacchetti).
· Fornire un testo di aiuto che definisca il significato del valore.
· Fornire un "Checker" (non utilizzato in questo esempio) che può essere utilizzato per impostare i limiti sul
intervallo di valori consentito.
Il punto chiave è che ora il valore di questa variabile e il suo valore predefinito sono accessibili
nello spazio dei nomi degli attributi, che si basa su stringhe come "MaxPackets" e ID tipo Nome
stringhe. Nella prossima sezione, forniremo uno script di esempio che mostra come gli utenti possono
manipolare questi valori.
Si noti che l'inizializzazione dell'attributo si basa sulla macro NS_OBJECT_ENSURE_REGISTERED
(DropTailQueue) essere chiamato; se lo si lascia fuori dalla nuova implementazione della classe, il tuo
gli attributi non verranno inizializzati correttamente.
Sebbene abbiamo descritto come creare attributi, non abbiamo ancora descritto come accedervi
e gestire questi valori. Ad esempio, non c'è globali.h file di intestazione in cui si trovano questi
memorizzati; gli attributi sono memorizzati con le loro classi. Le domande che sorgono naturalmente sono come
gli utenti apprendono facilmente tutti gli attributi dei loro modelli e come fa un utente
accedere a questi attributi o documentare i loro valori come parte del record dei loro
simulazione?
Documentazione dettagliata degli attributi effettivi definiti per un tipo e un elenco globale di
tutti gli attributi definiti sono disponibili nella documentazione API. Per il resto di questo
documento mostreremo i vari modi per ottenere e impostare gli attributi
valori.
Configurazione Predefinito Valori
Config::SetDefault e Riga di comando
Diamo un'occhiata a come uno script utente potrebbe accedere a un valore di attributo specifico.
Usa il src/punto-a-punto/esempi/valore-attributo-principale.cc sceneggiatura per illustrazione, con
alcuni dettagli sono stati eliminati. Il principale la funzione inizia:
// Questo è un esempio di base di come utilizzare il sistema di attributi per
// imposta e ottieni un valore nel sistema sottostante; vale a dire, un valore non firmato
// intero del numero massimo di pacchetti in una coda
//
int
principale (int argc, char *argv[])
{
// Per impostazione predefinita, l'attributo MaxPackets ha un valore di 100 pacchetti
// (questo valore predefinito può essere osservato nella funzione DropTailQueue::GetTypeId)
//
// Qui, lo impostiamo a 80 pacchetti. Potremmo usare uno dei due tipi di valore:
// un valore basato su stringa o un valore Uinteger
Config::SetDefault ("ns3::DropTailQueue::MaxPackets", StringValue ("80"));
// La chiamata alla funzione sottostante è ridondante
Config::SetDefault ("ns3::DropTailQueue::MaxPackets", UintegerValue (80));
// Consenti all'utente di sovrascrivere qualsiasi impostazione predefinita e quanto sopra
// SetDefaults() in fase di esecuzione, tramite argomenti della riga di comando
// Ad esempio, tramite "--ns3::DropTailQueue::MaxPackets=80"
Riga di comando cmd;
// Questo fornisce un altro modo per impostare il valore dalla riga di comando:
cmd.AddValue ("maxPackets", "ns3::DropTailQueue::MaxPackets");
cmd.Parse (argc, argv);
La cosa principale da notare in quanto sopra sono le due chiamate equivalenti a Config::SetDefault
()Ecco come impostiamo il valore predefinito per tutti i successivi istanziati
DropTailQueues. Illustriamo che due tipi di Valore classi, una Valore stringa e
Valore intero classe, può essere utilizzata per assegnare il valore all'attributo denominato da
"ns3::DropTailQueue::MaxPackets".
È anche possibile manipolare gli attributi utilizzando Riga di comando; abbiamo visto alcuni esempi
all'inizio del Tutorial. In particolare, è semplice aggiungere un argomento abbreviato
nome, come --maxPacchetti, per un attributo particolarmente rilevante per il tuo modello,
in questo caso "ns3::DropTailQueue::MaxPackets". Questo ha la caratteristica aggiuntiva che il
la stringa di aiuto per l'attributo verrà stampata come parte del messaggio di utilizzo per lo script.
Per ulteriori informazioni, vedere la Riga di comando Documentazione API.
Ora creeremo alcuni oggetti utilizzando l'API di basso livello. Le nostre code appena create saranno
non hanno m_maxPacchetti inizializzato a 100 pacchetti, come definito nel
DropTailQueue::GetTypeId () funzione, ma a 80 pacchetti, a causa di ciò che abbiamo fatto sopra con
valori predefiniti.:
Ptr n0 = CreaOggetto ();
Ptr net0 = CreaOggetto ();
n0->Aggiungi dispositivo (net0);
Ptr q = CreaOggetto ();
net0->AggiungiCoda(q);
A questo punto abbiamo creato un singolo Nodo (n0) e un singolo Dispositivo PointToPointNet
(net0), e ha aggiunto un DropTailQueue (q) A net0.
Costruttori, Helpers e Fabbrica di oggetti
È possibile impostare e recuperare combinazioni arbitrarie di attributi dall'helper e dal basso livello
API; sia dai costruttori stessi:
Ptr p =
CreaOggettoConAttributi
("MinX", DoubleValue (-100.0),
"MinY", DoubleValue (-100.0),
"DeltaX", DoubleValue (5.0),
"DeltaY", DoubleValue (20.0),
"GridWidth", UintegerValue (20),
"LayoutType", StringValue ("RowFirst"));
o dalle API helper di livello superiore, come:
mobilità.SetPositionAllocator
("ns3::GridPositionAllocator",
"MinX", DoubleValue (-100.0),
"MinY", DoubleValue (-100.0),
"DeltaX", DoubleValue (5.0),
"DeltaY", DoubleValue (20.0),
"GridWidth", UintegerValue (20),
"LayoutType", StringValue ("RowFirst"));
Non lo illustriamo qui, ma puoi anche configurare un Fabbrica di oggetti con nuovi valori
per attributi specifici. Istanze create da Fabbrica di oggetti avrà quelli
attributi impostati durante la costruzione. Questo è molto simile all'utilizzo di una delle API di supporto
per la classe.
Per rivedere, ci sono diversi modi per impostare i valori per gli attributi per le istanze di classe a be
creato in , il futuro:
· Config::SetDefault ()
· CommandLine::AddValue ()
· CreaOggettoConAttributi<> ()
· Varie API di supporto
Ma cosa succede se hai già creato un'istanza e vuoi modificare il valore di
attributo? In questo esempio, come possiamo manipolare l' m_maxPacchetti valore del già
istanziato DropTailQueueEcco vari modi per farlo.
Cambiare Valori
Puntatore intelligente
Supponiamo che un puntatore intelligente (Ptr) a un dispositivo di rete rilevante è in mano; nella corrente
esempio, è il net0 puntatore.
Un modo per modificare il valore è accedere a un puntatore alla coda sottostante e modificarne il valore.
attributo.
Innanzitutto, osserviamo che possiamo ottenere un puntatore alla (classe base) Fare la coda via , il
Dispositivo PointToPointNet attributi, dove viene chiamato "Coda di trasmissione":
PointerValue tmp;
net0->GetAttribute ("TxQueue", tmp);
Ptr txQueue = tmp.GetObject ();
Usando il OttieniOggetto () funzione, possiamo eseguire un downcast sicuro a un DropTailQueueDurante la serata,
"MaxPackets" è un attributo:
Ptr dtq = txQueue->GetObject ();
NS_ASSERT (dtq != 0);
Successivamente, possiamo ottenere il valore di un attributo su questa coda. Abbiamo introdotto il wrapper
Valore classi per i tipi di dati sottostanti, simili ai wrapper Java attorno a questi tipi,
poiché il sistema di attributi memorizza i valori serializzati in stringhe e non in tipi diversi.
Qui, il valore dell'attributo viene assegnato a un Valore intero Ottieni () metodo su questo
il valore produce il (non confezionato) uint32_t.:
UintegerValue limite;
dtq->GetAttribute ("MaxPackets", limite);
NS_LOG_INFO ("1. dtq limit: " << limit.Get () << " pacchetti");
Si noti che il downcast di cui sopra non è realmente necessario; avremmo potuto ottenere l'attributo
valore direttamente da coda di trasmissione, che è un Oggetto:
txQueue->GetAttribute ("MaxPackets", limite);
NS_LOG_INFO ("2. Limite txQueue: " << limit.Get () << " pacchetti");
Ora impostiamolo su un altro valore (60 pacchetti):
txQueue->SetAttribute("MaxPackets", UintegerValue (60));
txQueue->GetAttribute ("MaxPackets", limite);
NS_LOG_INFO ("3. Limite txQueue modificato: " << limit.Get () << " pacchetti");
Config Spazio dei nomi sentiero
Un modo alternativo per ottenere l'attributo è utilizzare lo spazio dei nomi di configurazione. Qui,
questo attributo risiede su un percorso noto in questo spazio dei nomi; questo approccio è utile se uno
non ha accesso ai puntatori sottostanti e vorrebbe configurare uno specifico
attributo con una singola istruzione.:
Config::Set ("/NodeList/0/DeviceList/0/TxQueue/MaxPackets",
Valore intero (25));
txQueue->GetAttribute ("MaxPackets", limite);
NS_LOG_INFO ("4. Limite txQueue modificato tramite namespace: "
<< limit.Get () << " pacchetti");
Il percorso di configurazione ha spesso la forma di ".../
nome>/ /.../ / " per fare riferimento a un'istanza specifica tramite l'indice di un
oggetto nel contenitore. In questo caso il primo contenitore è l'elenco di tutti NodoS; il
il secondo contenitore è l'elenco di tutti NetDevices sul prescelto Nodo. Infine, il
il percorso di configurazione di solito termina con una successione di attributi membro, in questo caso il
"MaxPackets" attributo di "Coda di trasmissione" degli eletti NetDevice.
Avremmo potuto anche usare i caratteri jolly per impostare questo valore per tutti i nodi e tutti i dispositivi di rete
(che in questo semplice esempio ha lo stesso effetto del precedente Config::Imposta ()):
Config::Set ("/NodeList/*/DeviceList/*/TxQueue/MaxPackets",
Valore intero (15));
txQueue->GetAttribute ("MaxPackets", limite);
NS_LOG_INFO ("5. Limite txQueue modificato tramite namespace con caratteri jolly: "
<< limit.Get () << " pacchetti");
Oggetto Nome Servizi
Un altro modo per ottenere l'attributo è quello di utilizzare la funzione di servizio del nome dell'oggetto.
il servizio di denominazione degli oggetti ci consente di aggiungere elementi allo spazio dei nomi di configurazione sotto
"/Nomi/" percorso con una stringa di nome definita dall'utente. Questo approccio è utile se non si
hanno accesso ai puntatori sottostanti ed è difficile determinare il necessario
percorso dello spazio dei nomi di configurazione concreto.
Nomi::Aggiungi ("server", n0);
Nomi::Aggiungi ("server/eth0", net0);
...
Config::Set ("/Names/server/eth0/TxQueue/MaxPackets", UintegerValue (25));
Qui abbiamo aggiunto gli elementi del percorso "server" e "eth0" sotto il "/Nomi/" spazio dei nomi, quindi
ha utilizzato il percorso di configurazione risultante per impostare l'attributo.
Vedere Object-names per un trattamento più completo dell' NS-3 spazio dei nomi di configurazione.
Implementazione/Attuazione Dettagli
Valore Classi
I lettori noteranno che TipoValore classi che sono sottoclassi del ValoreAttributo base
classe. Queste possono essere pensate come classi intermedie che vengono utilizzate per convertire da raw
tipi al ValoreAttributoche vengono utilizzati dal sistema di attributi. Ricordiamo che questo
Il database contiene oggetti di molti tipi serializzati in stringhe. Le conversioni a questo tipo
può essere fatto utilizzando una classe intermedia (come Valore intero, o Doppio valore da
numeri in virgola mobile) o via stringhe. Conversione implicita diretta dei tipi in
ValoreAttributo non è molto pratico. Quindi, come sopra, gli utenti hanno la possibilità di utilizzare
stringhe o valori:
p->Set ("cwnd", StringValue ("100")); // setter basato su stringhe
p->Set ("cwnd", IntegerValue (100)); // setter basato su numeri interi
Il sistema fornisce alcune macro che aiutano gli utenti a dichiarare e definire nuovi AttributeValue
sottoclassi per i nuovi tipi che vogliono introdurre nel sistema degli attributi:
· ATTRIBUTE_HELPER_HEADER
· ATTRIBUTE_HELPER_CPP
Per ulteriori informazioni, consultare la documentazione API per queste strutture.
Inizializzazione Ordine
Gli attributi nel sistema non devono dipendere dallo stato di nessun altro attributo in questo
sistema. Questo perché non è specificato un ordinamento dell'inizializzazione degli attributi, né
imposto dal sistema. Un esempio specifico di ciò può essere visto nella configurazione automatizzata
programmi come ConfigStoreSebbene un dato modello possa organizzarlo in modo che gli attributi
vengono inizializzati in un ordine particolare, un altro configuratore automatico può decidere
in modo indipendente per modificare gli attributi, ad esempio in ordine alfabetico.
A causa di questo ordinamento non specifico, nessun attributo nel sistema può avere alcuna dipendenza
su qualsiasi altro attributo. Come corollario, i setter di attributi non devono mai fallire a causa dello stato
di un altro attributo. Nessun setter di attributi può modificare (impostare) alcun altro valore di attributo come
risultato della modifica del suo valore.
Questa è una restrizione molto forte e ci sono casi in cui gli attributi devono essere impostati
in modo coerente per consentire il corretto funzionamento. A tal fine consentiamo il controllo della coerenza
quando , il attributo is utilizzato (cf. NS_ASSERT_MSG or NS_ABORT_MSG).
In generale, il codice dell'attributo per assegnare valori alle variabili membro della classe sottostante
viene eseguito dopo che un oggetto è stato costruito. Ma cosa succede se hai bisogno dei valori assegnati
prima che il corpo del costruttore venga eseguito, perché ne hai bisogno nella logica del
costruttore? Esiste un modo per farlo, utilizzato ad esempio nella classe ConfigStore: chiamata
ObjectBase::ConstructSelf () come segue:
ConfigStore::ConfigStore ()
{
ObjectBase::ConstructSelf (AttributeConstructionList ());
// continua con il costruttore.
}
Attenzione che l'oggetto e tutte le sue classi derivate devono anche implementare un OttieniInstanceTypeId
() metodo. Altrimenti il ObjectBase::ConstructSelf () non sarà in grado di leggere il
attributi.
Aggiunta Attributi
. NS-3 il sistema inserirà un certo numero di valori interni sotto il sistema di attributi, ma
senza dubbio gli utenti vorranno estendere questo per raccogliere quelli che ci sono sfuggiti, o per aggiungere i loro
proprie classi al sistema.
Esistono tre casi d'uso tipici:
· Rendere accessibile un membro dati di una classe esistente come attributo, quando non lo è già.
· Creare una nuova classe in grado di esporre alcuni membri dati come attributi assegnandole un TypeId.
· Creazione di un ValoreAttributo sottoclasse per una nuova classe in modo che sia possibile accedervi come
Attributo.
Esistente Member Variabile
Considera questa variabile in TCPSocket:
uint32_t m_cWnd; // Finestra di congestione
Supponiamo che qualcuno che lavora con TCP voglia ottenere o impostare il valore di quella variabile
utilizzando il sistema di metadati. Se non fosse già fornito da NS-3, l'utente potrebbe dichiarare
la seguente aggiunta nel sistema di metadati di runtime (al OttieniIDTipo() definizione per
TCPSocket):
.AddAttribute ("Finestra di congestione",
"Finestra di congestione TCP (byte)",
Valore intero (1),
MakeUintegerAccessor (&TcpSocket::m_cWnd),
MakeUintegerChecker ())
Ora, l'utente con un puntatore a un TCPSocket l'istanza può eseguire operazioni come
impostando e ottenendo il valore, senza dover aggiungere queste funzioni in modo esplicito.
Inoltre, è possibile applicare controlli di accesso, ad esempio consentendo la lettura del parametro e
non scritto, oppure è possibile applicare il controllo dei limiti sui valori ammissibili.
New Classe ID tipo
Qui, discutiamo l'impatto su un utente che desidera aggiungere una nuova classe a NS-3. Che cosa
bisogna fare altre cose per consentirgli di contenere attributi?
Supponiamo che la nostra nuova classe, chiamata ns3::MyMobility, è un tipo di modello di mobilità. In primo luogo,
la classe dovrebbe ereditare dalla sua classe genitore, ns3::Modello di mobilità. Nel la mia mobilità.h
file di intestazione:
spazio dei nomi ns3 {
classe MyClass : pubblico MobilityModel
{
Ciò richiede che dichiariamo il Ottieni IDTipo () funzione. Questa è una funzione pubblica di una riga
dichiarazione:
pubblico:
/ **
* Registra questo tipo.
* \return L'oggetto TypeId.
*/
TypeId statico GetTypeId (vuoto);
Abbiamo già introdotto cosa è un ID tipo la definizione apparirà come nel my-mobility.cc
file di implementazione:
NS_OBJECT_ENSURE_REGISTERED (MyMobility);
ID tipo
MyMobility::GetTypeId (vuoto)
{
TypeId statico tid = TypeId ("ns3::MyMobility")
.ImpostaParente ()
.SetGroupName ("Mobilità")
.AddConstructor ()
.AddAttribute ("Limiti",
"Confini dell'area di navigazione."
RectangleValue (Rettangolo (0.0, 0.0, 100.0, 100.0)),
MakeRectangleAccessor (&MyMobility::m_bounds),
CreaControlloRettangolo ())
.AddAttribute ("Tempo",
"Modifica la direzione e la velocità attuali dopo lo spostamento per questo ritardo."
Valore temporale (secondi (1.0)),
MakeTimeAccessor (&MyMobility::m_modeTime),
MakeTimeChecker ())
// ecc. (altri parametri).
;
ritorno a mare;
}
Se non vogliamo creare una sottoclasse da una classe esistente, nel file di intestazione ereditiamo semplicemente
da ns3::Oggettoe nel file oggetto impostiamo la classe padre su ns3::Oggetto con
.ImpostaParente ().
Gli errori tipici in questo caso sono:
· Non chiamare NS_OBJECT_ENSURE_REGISTERED ()
· Non chiamare il Imposta genitore () metodo o chiamandolo con il tipo sbagliato.
· Non chiamare il Aggiungi Costruttore () metodo o chiamandolo con il tipo sbagliato.
· Introduzione di un errore tipografico nel nome del ID tipo nel suo costruttore.
· Non utilizzare il nome del tipo C++ completamente qualificato della classe C++ racchiusa come nome del
ID tipo. Nota che "ns3::" è richiesto.
Nessuno di questi errori può essere rilevato dal NS-3 codebase, quindi si consiglia agli utenti di controllare
attentamente più volte per essere sicuri di aver indovinato.
New ValoreAttributo Tipo
Dal punto di vista dell'utente che scrive una nuova classe nel sistema e desidera che sia
accessibile come attributo, c'è principalmente la questione di scrivere le conversioni da/a
stringhe e valori di attributo. La maggior parte di questi può essere copiata/incollata con codice macro. Per
ad esempio, si consideri una dichiarazione di classe per Rettangolo nella src/mobilità/modello directory:
testata Compila il
/ **
* \brief un rettangolo 2d
*/
classe Rettangolo
{
...
doppio xMin;
doppio xMax;
doppio yMin;
doppio yMax;
};
Una chiamata macro e due operatori devono essere aggiunti sotto la dichiarazione della classe per
trasformare un rettangolo in un valore utilizzabile da Attributo sistema:
std::ostream &operator << (std::ostream &os, const Rettangolo &rettangolo);
std::istream &operatore >> (std::istream &is, Rettangolo &rettangolo);
ATTRIBUTE_HELPER_HEADER (Rettangolo);
Implementazione/Attuazione Compila il
Nella definizione della classe (. Cc file), il codice appare così:
ATTRIBUTE_HELPER_CPP (Rettangolo);
std::ostream &
operatore << (std::ostream &os, const Rettangolo &rettangolo)
{
os << rettangolo.xMin << "|" << rettangolo.xMax << "|" << rettangolo.yMin << "|"
<< rettangolo.yMax;
restituisci os;
}
std::istream &
operatore >> (std::istream &is, Rettangolo &rettangolo)
{
carattere c1, c2, c3;
è >> rettangolo.xMin >> c1 >> rettangolo.xMax >> c2 >> rettangolo.yMin >> c3
>> rettangolo.yMax;
se (c1 != '|' ||
c2 != '|' ||
c3 != '|')
{
is.setstate (std::ios_base::failbit);
}
il ritorno è;
}
Questi operatori di flusso convertono semplicemente da una rappresentazione di stringa del rettangolo
("xMin|xMax|yMin|yMax") al rettangolo sottostante. Il modellatore deve specificare questi
operatori e la rappresentazione sintattica della stringa di un'istanza della nuova classe.
ConfigStore
Valori per NS-3 gli attributi possono essere memorizzati in un file di testo ASCII o XML e caricati in un
simulazione futura. Questa funzione è nota come NS-3 ConfigStore. Il ConfigStore is
un database specializzato per valori di attributi e valori predefiniti.
Sebbene sia un modulo mantenuto separatamente nel src/config-store/ directory, noi
documentalo qui perché dipende esclusivamente da NS-3 modulo principale e attributi.
Possiamo esplorare questo sistema utilizzando un esempio da
src/config-store/examples/config-store-save.cc.
In primo luogo, tutti gli utenti del ConfigStore deve includere la seguente dichiarazione:
#include "ns3/config-store-module.h"
Successivamente, questo programma aggiunge un oggetto campione Esempio di configurazione per mostrare come il sistema è esteso:
classe ConfigExample : oggetto pubblico
{
pubblico:
static TypeId GetTypeId (void) {
TypeId statico tid = TypeId ("ns3::A")
.ImpostaParente ()
.AddAttribute ("TestInt16", "testo di aiuto",
Valore intero (-2),
MakeIntegerAccessor (&A::m_int16),
MakeIntegerChecker ())
;
ritorno a mare;
}
int16_t m_int16;
};
NS_OBJECT_ENSURE_REGISTERED (ConfigExample);
Successivamente, utilizziamo il sottosistema Config per sovrascrivere le impostazioni predefinite in un paio di modi:
Config::SetDefault ("ns3::ConfigExample::TestInt16", IntegerValue (-5));
Ptr a_obj = CreaOggetto ();
NS_ABORT_MSG_UNLESS (a_obj->m_int16 == -5,
"Impossibile impostare l'attributo intero di ConfigExample tramite Config::SetDefault");
Ptr a2_obj = CreaOggetto ();
a2_obj->SetAttribute ("TestInt16", IntegerValue (-3));
ValoreIntero iv;
a2_obj->GetAttribute ("TestInt16", iv);
NS_ABORT_MSG_UNLESS (iv.Get() == -3,
"Impossibile impostare l'attributo intero di ConfigExample tramite SetAttribute");
La prossima istruzione è necessaria per assicurarsi che (uno degli) oggetti creati sia rootato
nello spazio dei nomi di configurazione come istanza di oggetto. Questo accade normalmente quando si
aggregare oggetti in un ns3::Nodo or ns3::Canale esempio, ma qui, poiché stiamo lavorando
a livello di base, dobbiamo creare un nuovo oggetto namespace radice:
Config::RegisterRootNamespaceObject (a2_obj);
scrittura
Successivamente, vogliamo generare l'archivio di configurazione. Gli esempi mostrano come farlo in due
formati, XML e testo grezzo. In pratica, si dovrebbe eseguire questo passaggio appena prima di chiamare
Simulatore::Esegui () per salvare la configurazione finale appena prima di eseguire la simulazione.
Ci sono tre attributi che regolano il comportamento del ConfigStore: "Modalità",
"Nome del file"e "Formato file". La modalità (predefinita "Nessuno") configura se NS-3 dovrebbero
caricare la configurazione da un file salvato in precedenza (specificare "Modalità=Carica") o salvarlo in un file
(specificare "Modalità=Salva"). Il nome del file (predefinito "") è dove ConfigStore dovrebbe leggere o
scrive i suoi dati. Il FileFormat (predefinito "Testo grezzo") determina se il formato ConfigStore
è testo normale o Xml ("FileFormat=Xml")
L'esempio mostra:
Config::SetDefault ("ns3::ConfigStore::Filename", StringValue ("output-attributes.xml"));
Config::SetDefault ("ns3::ConfigStore::FileFormat", StringValue ("Xml"));
Config::SetDefault ("ns3::ConfigStore::Mode", StringValue ("Salva"));
ConfigStore outputConfig;
outputConfig.ConfigureDefaults ();
outputConfig.ConfigureAttributes ();
// Emette l'archivio di configurazione in formato txt
Config::SetDefault ("ns3::ConfigStore::Filename", StringValue ("output-attributes.txt"));
Config::SetDefault ("ns3::ConfigStore::FileFormat", StringValue ("RawText"));
Config::SetDefault ("ns3::ConfigStore::Mode", StringValue ("Salva"));
OutputConfigStoreConfig2;
outputConfig2.ConfigureDefaults ();
outputConfig2.ConfigureAttributes ();
Simulatore::Esegui ();
Simulatore::Distruggi ();
Notare la collocazione di queste affermazioni appena prima del Simulatore::Esegui () .
Questo output registra tutti i valori presenti appena prima dell'avvio della simulazione (ie.
dopo che tutta la configurazione è stata eseguita).
Dopo l'esecuzione, è possibile aprire il attributi di output.txt archivia e vedi:
predefinito ns3::RealtimeSimulatorImpl::SynchronizationMode "BestEffort"
predefinito ns3::RealtimeSimulatorImpl::HardLimit "+100000000.0ns"
predefinito ns3::PcapFileWrapper::CaptureSize "65535"
predefinito ns3::PacketSocket::RcvBufSize "131072"
predefinito ns3::ErrorModel::IsEnabled "true"
predefinito ns3::RateErrorModel::ErrorUnit "EU_BYTE"
predefinito ns3::RateErrorModel::ErrorRate "0"
predefinito ns3::RateErrorModel::RanVar "Uniform:0:1"
default ns3::DropTailQueue::Mode "Pacchetti"
predefinito ns3::DropTailQueue::MaxPackets "100"
predefinito ns3::DropTailQueue::MaxBytes "6553500"
predefinito ns3::Application::StartTime "+0.0ns"
predefinito ns3::Application::StopTime "+0.0ns"
default ns3::ConfigStore::Mode "Salva"
predefinito ns3::ConfigStore::Filename "output-attributes.txt"
predefinito ns3::ConfigStore::FileFormat "RawText"
predefinito ns3::ConfigExample::TestInt16 "-5"
RngSeed globale "1"
RngRun globale "1"
SimulatorImplementationType globale "ns3::DefaultSimulatorImpl"
SchedulerType globale "ns3::MapScheduler"
ChecksumEnabled globale "falso"
valore /$ns3::ConfigExample/TestInt16 "-3"
In alto sono mostrati tutti i valori predefiniti per gli attributi del modulo principale.
Quindi, tutti i valori per il NS-3 vengono registrati i valori globali. Infine, il valore del
istanza di Esempio di configurazione che era radicato nello spazio dei nomi di configurazione viene mostrato. In un
di rose NS-3 programma, verrebbero mostrati molti più modelli, attributi e valori predefiniti.
Esiste anche una versione XML in attributi di output.xml:
Questo file può essere archiviato con lo script di simulazione e i dati di output.
Lettura
Successivamente, discutiamo la configurazione delle simulazioni via un file di configurazione di input memorizzato. Ci sono
un paio di differenze chiave rispetto alla scrittura della configurazione finale della simulazione.
Per prima cosa, dobbiamo inserire istruzioni come queste all'inizio del programma, prima
vengono scritte le istruzioni di configurazione della simulazione (quindi i valori vengono registrati prima di essere
utilizzato nella costruzione di oggetti).
Config::SetDefault ("ns3::ConfigStore::Filename", StringValue ("input-defaults.xml"));
Config::SetDefault ("ns3::ConfigStore::Mode", StringValue ("Carica"));
Config::SetDefault ("ns3::ConfigStore::FileFormat", StringValue ("Xml"));
inputConfigStoreConfig;
inputConfig.ConfigureDefaults ();
Successivamente, si noti che il caricamento dei dati di configurazione di input è limitato all'attributo predefinito (ie.
valori (non di istanza) e valori globali. I valori di istanza degli attributi non sono supportati
perché in questa fase della simulazione, prima che vengano costruiti oggetti, non ci sono
tali istanze di oggetti in giro. (Nota, i futuri miglioramenti all'archivio di configurazione potrebbero cambiare
questo comportamento).
In secondo luogo, mentre l'output di ConfigStore lo stato elencherà tutto nel database, il
il file di input deve contenere solo i valori specifici da sovrascrivere. Quindi, un modo per utilizzare
questa classe per la configurazione del file di input serve a generare una configurazione iniziale utilizzando
produzione ("Salvare") "Modalità" descritto sopra, estrarre da quel file di configurazione solo il
elementi che si desidera modificare e spostare questi elementi minimi in un nuovo file di configurazione
che può quindi essere modificato e caricato in modo sicuro in una successiva simulazione.
Quando il ConfigStore l'oggetto è istanziato, i suoi attributi "Nome del file", "Modalità"e
"Formato file" deve essere impostato, o via riga di comando o via dichiarazioni del programma.
Lettura / scrittura Esempio
Come esempio più complicato, supponiamo di voler leggere in una configurazione di
predefiniti da un file di input denominato input-defaults.xmle scrivere il risultato
attributi in un file separato chiamato attributi di output.xml.:
#include "ns3/config-store-module.h"
...
int principale (...)
{
Config::SetDefault ("ns3::ConfigStore::Filename", StringValue ("input-defaults.xml"));
Config::SetDefault ("ns3::ConfigStore::Mode", StringValue ("Carica"));
Config::SetDefault ("ns3::ConfigStore::FileFormat", StringValue ("Xml"));
inputConfigStoreConfig;
inputConfig.ConfigureDefaults ();
//
// Consenti all'utente di sovrascrivere qualsiasi impostazione predefinita e il Bind() sopra riportato
// runtime, tramite argomenti della riga di comando
//
Riga di comando cmd;
cmd.Parse (argc, argv);
// topologia di configurazione
...
// Invoca subito prima di entrare in Simulator::Run()
Config::SetDefault ("ns3::ConfigStore::Filename", StringValue ("output-attributes.xml"));
Config::SetDefault ("ns3::ConfigStore::Mode", StringValue ("Salva"));
ConfigStore outputConfig;
outputConfig.ConfigureAttributes ();
Simulatore::Esegui ();
}
ConfigStore GUI
Esiste un front-end basato su GTK per ConfigStore. Questo consente agli utenti di utilizzare una GUI per
accedere e modificare le variabili. Gli screenshot di questa funzionalità sono disponibili in |ns3|
Panoramica presentazione.
Per utilizzare questa funzionalità, è necessario installare libgtk e libgtk-dev; un esempio di Ubuntu
il comando di installazione è:
$ sudo apt-get install libgtk2.0-0 libgtk2.0-dev
Per verificare se è configurato o meno, controllare l'output del passaggio:
$ ./waf configure --enable-examples --enable-tests
---- Riepilogo delle funzioni opzionali di NS-3:
Associazioni Python: abilitate
Supporto per la scansione dell'API Python: abilitato
Integrazione clic NS-3: abilitata
GtkConfigStore: non abilitato (libreria 'gtk+-2.0 >= 2.12' non trovata)
Nell'esempio sopra, non è stato abilitato, quindi non può essere utilizzato finché non viene rilasciata una versione adatta
installato e:
$ ./waf configure --enable-examples --enable-tests
$ ./waf
viene ripetuto.
L'utilizzo è quasi lo stesso della versione non basata su GTK, ma non ci sono ConfigStore
attributi coinvolti:
// Invoca subito prima di entrare in Simulator::Run()
Configurazione GtkConfigStore;
config.ConfigureDefaults ();
config.ConfigureAttributes ();
Ora, quando esegui lo script, dovrebbe apparire una GUI, che ti consente di aprire i menu di
attributi su diversi nodi/oggetti, quindi avviare l'esecuzione della simulazione quando si
sono fatti.
Futuro lavoro
Ci sono un paio di possibili miglioramenti:
· Salvare un numero di versione univoco con data e ora all'inizio del file.
· Salva il seed iniziale rng da qualche parte.
· Fare in modo che ogni RandomVariable serializzi il proprio seme iniziale e lo rilegga in seguito.
Oggetto nomi
segnaposto capitolo
Registrazione
. NS-3 la funzione di registrazione può essere utilizzata per monitorare o eseguire il debug dell'avanzamento della simulazione
programmi. L'output di registrazione può essere abilitato tramite istruzioni di programma nel tuo principale() programma o
mediante l'uso di NS_LOG variabile d'ambiente.
Le istruzioni di registrazione non vengono compilate in build ottimizzate di NS-3Per utilizzare la registrazione, uno
deve compilare la build di debug (predefinita) di NS-3.
Il progetto non garantisce che l'output di registrazione rimarrà lo stesso nel tempo
tempo. Gli utenti sono avvertiti di non creare framework di output di simulazione in aggiunta alla registrazione
codice, poiché l'output e il modo in cui l'output viene abilitato potrebbero cambiare nel tempo.
Panoramica
NS-3 Le istruzioni di registrazione vengono in genere utilizzate per registrare vari eventi di esecuzione del programma, come
come il verificarsi di eventi di simulazione o l'uso di una particolare funzione.
Ad esempio, questo frammento di codice è tratto da Ipv4L3Protocol::IsDestinationAddress():
se (indirizzo == iaddr.GetBroadcast ())
{
NS_LOG_LOGIC ("Per me (indirizzo di broadcast dell'interfaccia)");
return true;
}
Se la registrazione è stata abilitata per Protocollo IPv4L3 componente a una gravità di LOGICA or
sopra (vedere di seguito sulla gravità del registro), la dichiarazione verrà stampata; in caso contrario,
verrà soppresso.
Abilitare Uscita
Ci sono due modi in cui gli utenti in genere controllano l'output del registro. Il primo è impostando
NS_LOG variabile d'ambiente; ad esempio:
$ NS_LOG="*" ./waf --esegui prima
eseguirà il prima di tutto programma tutorial con tutti gli output di registrazione. (Le specifiche del NS_LOG
(Il formato verrà discusso di seguito.)
È possibile rendere questa operazione più dettagliata selezionando i singoli componenti:
$ NS_LOG="Ipv4L3Protocol" ./waf --esegui prima
L'output può essere ulteriormente personalizzato con le opzioni di prefisso.
Il secondo modo per abilitare la registrazione è utilizzare istruzioni esplicite nel programma, come in
, il prima di tutto programma tutorial:
int
principale (int argc, char *argv[])
{
LogComponentEnable ("UdpEchoClientApplication", LOG_LEVEL_INFO);
LogComponentEnable ("UdpEchoServerApplication", LOG_LEVEL_INFO);
...
(Il significato di LOG_LEVEL_INFOe altri possibili valori saranno discussi di seguito.)
NS_LOG Sintassi
. NS_LOG variabile di ambiente contiene un elenco di componenti e opzioni di registro. Registro
i componenti sono separati dai caratteri `:':
$ NS_LOG=" : ..."
Le opzioni per ciascun componente del registro sono fornite come flag dopo ogni componente del registro:
$ NS_LOG=" = | ...: ..."
Le opzioni controllano la gravità e il livello di quel componente e se è facoltativo
dovrebbero essere incluse informazioni come il tempo di simulazione, il nodo di simulazione, la funzione
nome e la severità simbolica.
Log Componenti
Generalmente un componente di registro si riferisce a un singolo codice sorgente . Cc file e comprende il
intero file.
Alcuni helper hanno metodi speciali per abilitare la registrazione di tutti i componenti in un modulo,
che abbracciano diverse unità di compilazione, ma logicamente raggruppate insieme, come ad esempio NS-3
codice wifi:
WifiHelper wifiHelper;
wifiHelper.EnableLogComponents ();
. NS_LOG il carattere jolly `*' del componente di registro abiliterà tutti i componenti.
Per vedere quali componenti del registro sono definiti, uno qualsiasi di questi funzionerà:
$ NS_LOG="elenco-stampa" ./waf --run ...
$ NS_LOG="foo" # un token che non corrisponde ad alcun componente del registro
Il primo modulo stamperà il nome e i flag abilitati per tutti i componenti del registro che sono
collegato; provalo con simulatore di scratchIl secondo modulo stampa tutti i log registrati
componenti, quindi esce con un errore.
Gravità e Livello Opzioni
I singoli messaggi appartengono a una singola "classe di gravità", impostata dalla macro che crea il
messaggio. Nell'esempio sopra, NS_LOG_LOGIC(..) crea il messaggio nel LOG_LOGIC
classe di gravità.
Le seguenti classi di gravità sono definite come enum costanti:
┌─────────────────┬───────────────────────────────────┐
│Classe di gravità │ Significato │
├────────────────┼──────────────────────────────────┤
│LOG_NONE │ L'impostazione predefinita, nessuna registrazione │
├────────────────┼──────────────────────────────────┤
│LOG_ERROR │ Solo messaggi di errore gravi │
├────────────────┼──────────────────────────────────┤
│LOG_WARN │ Messaggi di avviso │
├────────────────┼──────────────────────────────────┤
│LOG_DEBUG │ Per l'uso nel debug │
├────────────────┼──────────────────────────────────┤
│LOG_INFO │ Informativo │
├────────────────┼──────────────────────────────────┤
│LOG_FUNCTION │ Tracciamento delle funzioni │
├────────────────┼──────────────────────────────────┤
│LOG_LOGIC │ Tracciamento del flusso di controllo all'interno di │
│ │ funzioni │
└──────────────────┴───────────────────────────────┘
In genere si desidera visualizzare i messaggi con una determinata classe di gravità e superiore. Questo viene fatto da
definizione di "livelli" di registrazione inclusivi:
? ?
│Livello │ Significato │
? ?
│LOG_LEVEL_ERROR │ Solo LOG_ERROR classe di gravità │
│ │ messaggi. │
? ?
│LOG_LEVEL_WARN │ LOG_WARN e sopra. │
? ?
│LOG_LEVEL_DEBUG │ LOG_DEBUG e sopra. │
? ?
│LOG_LEVEL_INFO │ LOG_INFO e sopra. │
? ?
│LOG_LEVEL_FUNCTION │ LOG_FUNCTION e sopra. │
? ?
│LOG_LEVEL_LOGIC │ LOG_LOGIC e sopra. │
? ?
│LOG_LEVEL_ALL │ Tutte le classi di gravità. │
? ?
│LOG_ALL │ Sinonimo di LOG_LEVEL_ALL │
? ?
Le opzioni di classe e livello di gravità possono essere fornite nel NS_LOG variabile d'ambiente di
questi token:
┌──────────┬─────────────────┐
│Classe │Livello │
├─────────┼────────────────┤
│errore │ livello_errore │
├─────────┼────────────────┤
│avvertire │ livello_avviso │
├─────────┼────────────────┤
│mettere a punto │ livello_debug │
├─────────┼────────────────┤
│Maggiori informazioni. │ informazioni_livello │
├─────────┼────────────────┤
│funzione │ funzione_livello │
├─────────┼────────────────┤
│logica │ livello_logico │
├─────────┼────────────────┤
│ │ livello_tutto │
│ │ contro tutti i │
│ │ * │
└──────────┴───────────────┘
L'utilizzo di un token di classe di gravità abilita i messaggi di log solo a quella gravità. Ad esempio,
NS_LOG="*=avvertimento" non emetterà messaggi con gravità errore. NS_LOG="*=livello_debug" andrete a
messaggi di output a livelli di gravità mettere a punto e al di sopra.
Le classi e i livelli di gravità possono essere combinati con l'operatore `|':
NS_LOG="*=livello_avviso|logica" emetterà messaggi a livelli di gravità errore, avvertire e logica.
. NS_LOG carattere jolly del livello di gravità `*' e contro tutti i sono sinonimi di livello_tutto.
Per i componenti del registro semplicemente menzionati in NS_LOG
$ NS_LOG=" :..."
la gravità predefinita è LOG_LEVEL_ALL.
Prefisso Opzioni
Diversi prefissi possono aiutare a identificare dove e quando un messaggio ha avuto origine e a cosa
severità.
Le opzioni di prefisso disponibili (come enum costanti) sono
? ?
│Simbolo prefisso │ Significato │
? ?
│LOG_PREFIX_FUNC │ Aggiungere il prefisso al nome del chiamante │
│ │ funzione. │
? ?
│LOG_PREFIX_TIME │ Aggiungere il prefisso all'ora della simulazione. │
? ?
│LOG_PREFIX_NODE │ Aggiungi il prefisso all'ID del nodo. │
? ?
│LOG_PREFIX_LEVEL │ Aggiungere il prefisso al livello di gravità. │
? ?
│LOG_PREFIX_ALL │ Abilita tutti i prefissi. │
? ?
Di seguito vengono descritte brevemente le opzioni del prefisso.
Le opzioni possono essere fornite nel NS_LOG variabile d'ambiente tramite questi token:
┌──────────────┬────────────┐
│Token │ Alternativo │
├─────────────┼───────────┤
│prefisso_funzione │ func │
├─────────────┼───────────┤
│prefisso_ora │ tempo │
└──────────────┴───────────┘
│prefisso_nodo │ nodo │
├─────────────┼───────────┤
│prefisso_livello │ livello │
├─────────────┼───────────┤
│prefisso_tutto │ contro tutti i │
│ │ * │
└──────────────┴───────────┘
Per i componenti del registro semplicemente menzionati in NS_LOG
$ NS_LOG=" :..."
le opzioni di prefisso predefinite sono LOG_PREFIX_ALL.
Gravità Prefisso
La classe di gravità di un messaggio può essere inclusa con le opzioni prefisso_livello or livello.
Ad esempio, questo valore di NS_LOG abilita la registrazione per tutti i componenti del registro (`*') e tutti
classi di gravità (=tutti), e antepone al messaggio la classe di gravità (|prefisso_livello).
$ NS_LOG="*=all|prefix_level" ./waf --run scratch-simulator
Simulatore di Scratch
messaggio di errore [ERRORE]
[WARN] messaggio di avviso
[DEBUG] messaggio di debug
[INFO] messaggio informativo
messaggio di funzione [FUNCT]
[LOGICA] messaggio logico
Ora Prefisso
Il tempo di simulazione può essere incluso con le opzioni prefisso_ora or tempo. Questo stampa il
tempo di simulazione in secondi.
Nodo Prefisso
L'ID del nodo di simulazione può essere incluso con le opzioni prefisso_nodo or nodo.
Funzione Prefisso
Il nome della funzione chiamante può essere incluso con le opzioni prefisso_funzione or func.
NS_LOG I caratteri jolly
Il carattere jolly `*' del componente di registro abiliterà tutti i componenti. Per abilitare tutti i componenti contemporaneamente
livello di gravità specifico da utilizzare *=.
Il carattere jolly `*' dell'opzione del livello di gravità è sinonimo di contro tutti iCiò deve avvenire prima di qualsiasi
Caratteri `|' che separano le opzioni. Per abilitare tutte le classi di gravità, utilizzare =*,
or =*|.
Il carattere jolly `*' o il token dell'opzione contro tutti i abilita tutte le opzioni del prefisso, ma deve verificarsi dopo a
Carattere `|'. Per abilitare una classe o un livello di gravità specifico e tutti i prefissi, utilizzare
= |*.
Il jolly dell'opzione combinata ** abilita tutte le gravità e tutti i prefissi; ad esempio,
=**.
Il jolly assoluto *** abilita tutti i livelli di gravità e tutti i prefissi per tutti i componenti del registro.
Sono tutti equivalenti:
$ NS_LOG="***" ... $ NS_LOG="*=tutti|*" ... $ NS_LOG="*=*|tutti" ...
$ NS_LOG="*=**" ... $ NS_LOG="*=livello_tutti|*" ... $ NS_LOG="*=*|prefisso_tutti" ...
$ NS_LOG="*=*|*" ...
Siate avvisati: anche le cose banali simulatore di scratch produce oltre 46K linee di output con
NS_LOG="***"!
Come a aggiungere registrazione a il tuo codice
Aggiungere la registrazione al codice è molto semplice:
1. Invocare il NS_LOG_COMPONENT_DEFINE (...); macro all'interno di namespace ns3.
Crea un identificatore di stringa univoco (solitamente basato sul nome del file e/o della classe
definito all'interno del file) e registrarlo con una chiamata macro come la seguente:
spazio dei nomi ns3 {
NS_LOG_COMPONENT_DEFINE ("Protocollo Ipv4L3");
...
Questo registra Protocollo IPv4L3 come componente di registro.
(La macro è stata scritta con cura per consentire l'inclusione sia all'interno che all'esterno di
namespace ns3e l'utilizzo varierà a seconda della base di codice, ma l'intento originale era quello di
registra questo al di fuori dello spazio dei nomi ns3 nell'ambito globale del file.)
2. Aggiungere istruzioni di registrazione (chiamate macro) alle funzioni e ai corpi delle funzioni.
Registrazione Macro
Le macro di registrazione e i livelli di gravità associati sono
┌─────────────────┬────────────────────────┐
│Classe di gravità │ Macro │
├───────────────┼───────────────────────┤
│LOG_NONE │ (non necessario) │
├───────────────┼───────────────────────┤
│LOG_ERROR │ NS_LOG_ERRORE (...); │
├───────────────┼───────────────────────┤
│LOG_WARN │ NS_LOG_WARN (...); │
├───────────────┼───────────────────────┤
│LOG_DEBUG │ NS_LOG_DEBUG (...); │
├───────────────┼───────────────────────┤
│LOG_INFO │ NS_LOG_INFO (...); │
├───────────────┼───────────────────────┤
│LOG_FUNCTION │ NS_LOG_FUNCTION (...); │
├───────────────┼───────────────────────┤
│LOG_LOGIC │ NS_LOG_LOGICA (...); │
└─────────────────┴──────────────────────┘
Le macro funzionano come streamer di output, quindi tutto ciò che puoi inviare a std::out, Iscritto
by << operatori, è consentito:
void MyClass::Check (int valore, char * elemento)
{
NS_LOG_FUNCTION (questo << arg << elemento);
se (argomento > 10)
{
NS_LOG_ERROR ("rilevato valore errato" << valore <
" durante il controllo " << nome << "!");
}
...
}
Si noti che NS_LOG_FUNCTION inserisce automaticamente un `,' (virgola-spazio) separatore tra
ciascuno dei suoi argomenti. Questo semplifica la registrazione degli argomenti della funzione; basta concatenare
loro << come nell'esempio sopra.
Incondizionato Registrazione
Per comodità, il NS_LOG_UNCOND (...); la macro registrerà sempre i suoi argomenti, anche se
il componente di registro associato non è abilitato a nessuna gravità. Questa macro non utilizza alcun
delle opzioni del prefisso. Nota che la registrazione è abilitata solo nelle build di debug; questa macro
non produrrà output nelle build ottimizzate.
Linee Guida
· Inizia ogni metodo di classe con NS_LOG_FUNCTION (Questo << argomenti...); Ciò consente una facile
tracciamento delle chiamate di funzione.
· Eccetto: non registrare operatori o costruttori di copia espliciti, poiché questi causeranno
ricorsione infinita e overflow dello stack.
· Per i metodi senza argomenti utilizzare la stessa forma: NS_LOG_FUNCTION (Questo);
· Per funzioni statiche:
· Con argomenti utilizzare NS_LOG_FUNCTION (...); come normale.
· Senza argomenti utilizzare NS_LOG_FUNCTION_NOARGS ();
· Utilizzo NS_LOG_ERRORE per gravi condizioni di errore che probabilmente invalidano la simulazione
esecuzione.
· Utilizzo NS_LOG_WARN per condizioni insolite che potrebbero essere correggibili. Si prega di fornire alcuni suggerimenti
sulla natura del problema e su come potrebbe essere corretto.
· NS_LOG_DEBUG è solitamente utilizzato in un ad hoc modo per comprendere l'esecuzione di un modello.
· Utilizzo NS_LOG_INFO per ulteriori informazioni sull'esecuzione, come la dimensione di un
struttura dei dati quando si aggiungono/rimuovono dati da essa.
· Utilizzo NS_LOG_LOGICA per tracciare importanti rami logici all'interno di una funzione.
· Verifica che le modifiche al registro non interrompano il codice. Esegui alcuni programmi di esempio con
tutti i componenti del registro attivati (ad esempio NS_LOG="***").
Tracciato
Il sottosistema di tracciamento è uno dei meccanismi più importanti da comprendere in NS-3. in
nella maggior parte dei casi, NS-3 gli utenti avranno un'idea brillante per una rete nuova e migliorata
caratteristica. Per verificare che questa idea funzioni, il ricercatore apporterà modifiche a un
sistema esistente e quindi eseguire esperimenti per vedere come si comporta la nuova funzionalità raccogliendo
statistiche che catturano il comportamento della funzionalità.
In altre parole, lo scopo di eseguire una simulazione è generare output per ulteriori
studio. In NS-3, il sottosistema che consente al ricercatore di fare ciò è il tracciamento
sottosistema.
Tracciato Motivazione
Ci sono molti modi per ottenere informazioni da un programma. Il modo più semplice è
per stampare direttamente le informazioni sull'output standard, come in,
#includere
...
intero principale ()
{
...
std::cout << "Il valore di x è " << x << std::endl;
...
}
Questo è fattibile in piccoli ambienti, ma man mano che le tue simulazioni diventano sempre più
complicato, ti ritrovi con sempre più stampe e il compito di analizzare ed eseguire
i calcoli sull'output cominciano a diventare sempre più difficili.
Un'altra cosa da considerare è che ogni volta che è necessario un nuovo bocconcino, il core del software
deve essere modificato e deve essere introdotta un'altra stampa. Non esiste un modo standardizzato per controllare tutto
di questa produzione, quindi la quantità di produzione tende a crescere senza limiti. Alla fine, il
la larghezza di banda richiesta per la semplice trasmissione di queste informazioni inizia a limitare il tempo di esecuzione
della simulazione. I file di output raggiungono dimensioni enormi e analizzarli diventa un
problema.
NS-3 fornisce un semplice meccanismo per la registrazione e per fornire un certo controllo sull'output tramite
Log Componenti, ma il livello di controllo non è affatto molto fine. La registrazione
il modulo è uno strumento relativamente ottuso.
È auspicabile avere una struttura che consenta di raggiungere il sistema centrale e solo
ottenere le informazioni richieste senza dover modificare e ricompilare il sistema principale. Anche
sarebbe meglio un sistema che avvisasse l'utente quando un elemento di interesse cambia o un
è accaduto un evento interessante.
. NS-3 il sistema di tracciamento è progettato per funzionare lungo queste linee ed è ben integrato con
i sottosistemi Attributo e Configurazione consentono scenari di utilizzo relativamente semplici.
Panoramica
Il sottosistema di tracciamento si basa fortemente su NS-3 Meccanismi di callback e attributi. Tu
dovrebbe leggere e comprendere le sezioni corrispondenti del manuale prima di tentare di
comprendere il sistema di tracciamento.
. NS-3 sistema di tracciamento è costruito sui concetti di fonti di tracciamento indipendenti e
tracciamento dei sink; insieme a un meccanismo uniforme per collegare le sorgenti ai sink.
Le sorgenti di traccia sono entità che possono segnalare eventi che accadono in una simulazione e fornire
accesso a dati sottostanti interessanti. Ad esempio, un'origine di traccia potrebbe indicare quando a
il pacchetto viene ricevuto da un dispositivo di rete e fornisce l'accesso al contenuto del pacchetto per
la traccia interessata sprofonda. Una fonte di traccia potrebbe anche indicare quando uno stato interessante
il cambiamento avviene in un modello. Ad esempio, la finestra di congestione di un modello TCP è un numero primo
candidato per una fonte di traccia.
Le fonti di traccia non sono utili da sole; devono essere collegati ad altri pezzi di codice
che effettivamente fanno qualcosa di utile con le informazioni fornite dalla fonte. Il
le entità che consumano informazioni di traccia sono chiamate sink di traccia. Le fonti di traccia lo sono
i generatori di eventi e i sink di traccia sono consumatori.
Questa divisione esplicita consente di distribuire un gran numero di fonti di traccia
il sistema in luoghi che gli autori del modello ritengono possano essere utili. A meno che un utente non colleghi un
traccia il sink verso una di queste sorgenti, non viene emesso nulla. Questa disposizione consente relativamente
utenti inesperti di collegare nuovi tipi di sink alle fonti di tracciamento esistenti, senza
che richiede la modifica e la ricompilazione del nucleo o dei modelli del simulatore.
Possono esserci zero o più consumatori di eventi di traccia generati da un'origine di traccia. Uno può
si pensi a una sorgente di traccia come a una sorta di collegamento informativo punto-multipunto.
Il "protocollo di trasporto" per questo collegamento concettuale punto-multipunto è un NS-3 Richiamata.
Ricorda dalla sezione Callback che la funzione di callback è un modo per consentire a due moduli di
il sistema per comunicare tramite chiamate di funzione disaccoppiando allo stesso tempo la chiamata
funzione dalla classe chiamata completamente. Questo è lo stesso requisito descritto sopra
per il sistema di tracciamento.
Fondamentalmente, una fonte di traccia is una callback a cui possono essere registrate più funzioni.
Quando un sink di traccia esprime interesse nel ricevere eventi di traccia, aggiunge un callback a un
elenco di callback mantenuti dalla sorgente di traccia. Quando si verifica un evento interessante, la traccia
la fonte invoca il suo operatore() fornendo zero o più parametri. Questo indica alla sorgente di
scorrere l'elenco delle callback, invocandole una alla volta. In questo modo, il/i parametro/i
vengono comunicati ai sink di traccia, che sono semplicemente funzioni.
. Più semplice Esempio
Sarà utile fare un rapido esempio, giusto per rafforzare quanto detto:
#include "ns3/object.h"
#include "ns3/uinteger.h"
#include "ns3/traced-value.h""
#include "ns3/trace-source-accessor.h"
#includere
usando lo spazio dei nomi ns3;
La prima cosa da fare è includere i file richiesti. Come accennato in precedenza, il sistema di tracciamento
fa ampio uso dei sistemi di oggetti e attributi. I primi due includono l'inserimento di
dichiarazioni per quei sistemi. Il file, valore-tracciato.h porta il necessario
dichiarazioni per tracciare i dati che rispettano la semantica del valore.
In generale, la semantica del valore significa semplicemente che è possibile passare l'oggetto in giro, non un
indirizzo. Per poter utilizzare la semantica del valore è necessario disporre di un oggetto con un
costruttore di copia associato e operatore di assegnazione disponibili. Estendiamo i requisiti
per parlare dell'insieme di operatori predefiniti per i tipi POD (plain-old-data).
Operatore=, operatore++, operatore--, operatore+, operatore==, ecc.
Tutto ciò significa che sarai in grado di tracciare le modifiche apportate a un oggetto utilizzando
quegli operatori.:
classe MyObject : oggetto pubblico
{
pubblico:
statico TypeId GetTypeId (vuoto)
{
statico TypeId tid = TypeId ("MyObject")
.SetParent (Oggetto::GetTypeId ())
.Aggiungi costruttore ()
.AddTraceSource ("MyInteger",
"Un valore intero da tracciare.",
MakeTraceSourceAccessor (&MyObject::m_myInt))
;
ritorno a mare;
}
MioOggetto () {}
Valore Tracciato m_myInt;
};
Poiché il sistema di tracciamento è integrato con gli attributi e gli attributi funzionano con gli oggetti,
ci deve essere un NS-3 Oggetto per la fonte di traccia in cui vivere. Le due linee importanti di
il codice è il .AddTraceSource e la Valore tracciato dichiarazione.
. .AddTraceSource fornisce i "ganci" utilizzati per connettere la sorgente di traccia al file
mondo esterno. Il Valore tracciato la dichiarazione fornisce l'infrastruttura che sovraccarica il
operatori sopra menzionati e guida il processo di callback.:
nulla
IntTrace (Int oldValue, Int newValue)
{
std::cout << "Tracciato" << oldValue << " to " << newValue << std::endl;
}
Questa è la definizione del trace sink. Corrisponde direttamente a una funzione di callback.
Questa funzione verrà chiamata ogni volta che uno degli operatori del Valore tracciato is
eseguito.:
int
principale (int argc, char *argv[])
{
pt mioOggetto = CreaOggetto ();
myObject->TraceConnectWithoutContext ("MyInteger", MakeCallback(&IntTrace));
mioOggetto->m_mioInt = 1234;
}
In questo frammento, la prima cosa che deve essere fatta è creare l'oggetto in cui
la fonte della traccia è viva.
Il prossimo passo, il TraceConnectWithoutContext, costituisce il collegamento tra la traccia
sorgente e la traccia sink. Notare il Effettua una richiamata funzione modello. Richiama dal
Sezione di callback che crea il funtore specializzato responsabile della fornitura del
stracarico operatore() utilizzato per "attivare" la callback. Gli operatori sovraccaricati (++, --, ecc.)
userà questo operatore() per invocare effettivamente il callback. Il TraceConnectWithoutContext,
accetta un parametro stringa che fornisce il nome dell'attributo assegnato alla traccia
fonte. Per ora ignoriamo la parte sul contesto, perché non è ancora importante.
Infine, la riga:
mioOggetto->m_mioInt = 1234;
deve essere interpretato come un'invocazione di operatore= sulla variabile membro m_mioInt con
l'intero 1234 passato come parametro. Si scopre che questo operatore è definito (da
Valore tracciato) per eseguire una callback che restituisce void e accetta due valori interi come
parametri: un valore vecchio e un nuovo valore per l'intero in questione. Questo è esattamente
la firma della funzione per la funzione di callback che abbiamo fornito -- IntTrace.
Per riassumere, un'origine di traccia è, in sostanza, una variabile che contiene un elenco di callback. UN
trace sink è una funzione utilizzata come destinazione di un callback. L'attributo e il tipo di oggetto
I sistemi informativi vengono utilizzati per fornire un modo per collegare le fonti di traccia ai pozzi di traccia.
l'atto di "colpire" una sorgente di traccia è l'esecuzione di un operatore sulla sorgente di traccia che si attiva
callback. Ciò si traduce nella registrazione dell'interesse nella sorgente da parte dei callback del sink di traccia
essere chiamato con i parametri forniti dalla sorgente.
utilizzando , il Config Sottosistema a Connettere a Traccia fonti
. TraceConnectWithoutContext la chiamata mostrata sopra nel semplice esempio è in realtà molto
usato raramente nel sistema. Più tipicamente, il Config il sottosistema viene utilizzato per consentire la selezione
una fonte di traccia nel sistema utilizzando quello che viene chiamato un config sentiero.
Ad esempio, si potrebbe trovare qualcosa che assomiglia a quanto segue nel sistema (preso
da esempi/tcp-large-transfer.cc):
void CwndTracer (uint32_t vecchio valore, uint32_t nuovo valore) {}
...
Config::ConnectWithoutContext (
"/NodeList/0/$ns3::TcpL4Protocol/SocketList/0/CongestionWindow",
CreaCallback (&CwndTracer));
Questo dovrebbe sembrarti molto familiare. È la stessa cosa dell'esempio precedente, tranne che
una funzione membro statica della classe Config viene chiamato invece di un metodo su Oggetto;
e invece di un Attributo nome, viene fornito un percorso.
La prima cosa da fare è leggere il percorso a ritroso. L'ultimo segmento del percorso deve essere
an Attributo di Oggetto. Infatti, se avessi un puntatore al Oggetto quello ha il
"Finestra di congestione" Attributo pratico (chiamalo l'Oggetto), potresti scriverlo proprio come
esempio precedente:
void CwndTracer (uint32_t vecchio valore, uint32_t nuovo valore) {}
...
theObject->TraceConnectWithoutContext ("CongestionWindow", MakeCallback (&CwndTracer));
Si scopre che il codice per Configurazione::ConnectWithoutContext fa esattamente questo. Questo
la funzione prende un percorso che rappresenta una catena di Oggetto puntatori e li segue finché non
arriva alla fine del percorso e interpreta l'ultimo segmento come un Attributo sull'ultimo
oggetto. Vediamo cosa succede.
Il carattere "/" iniziale nel percorso si riferisce a un cosiddetto spazio dei nomi. Uno di
lo spazio dei nomi predefinito nel sistema di configurazione è "NodeList" che è un elenco di tutti i
nodi nella simulazione. Gli elementi nell'elenco sono indicati da indici nell'elenco, quindi
"/NodeList/0" si riferisce al nodo zero nell'elenco dei nodi creati dalla simulazione.
Questo nodo è in realtà un Ptr e così è una sottoclasse di un ns3::Oggetto.
Come descritto nella sezione Modello oggetto, NS-3 supporta un modello di aggregazione di oggetti. Il
il segmento del percorso successivo inizia con il carattere "$" che indica un OttieniOggetto la chiamata dovrebbe essere
effettuato cercando il tipo che segue. Quando un nodo viene inizializzato da un
Internet StackHelper un certo numero di interfacce sono aggregate al nodo. Una di queste è la
Protocollo TCP di livello quattro. Il tipo di runtime di questo oggetto protocollo è ns3::TcpL4Protocol''.
Quando , il ``OttieniOggetto viene eseguito, restituisce un puntatore all'oggetto di questo tipo.
. Protocollo TcpL4 la classe definisce un attributo chiamato "SocketList" che è un elenco di
prese. Ogni presa è in realtà una ns3::Oggetto con il proprio AttributiGli elementi in
l'elenco dei socket è indicato dall'indice proprio come in NodeList, quindi "SocketList/0"
si riferisce al socket zero nell'elenco dei socket sul nodo zero in NodeList --
il primo nodo costruito nella simulazione.
Questa presa, il cui tipo risulta essere un ns3::TcpSocketImpl definisce un attributo
chiamato "CongestionWindow" che è un Valore Tracciato.
Configurazione::ConnectWithoutContext ora fa un,:
oggetto->TraceConnectWithoutContext ("CongestionWindow", MakeCallback (&CwndTracer));
utilizzando il puntatore all'oggetto da "SocketList/0" che crea la connessione tra la traccia
sorgente definita nel socket per il callback -- CwndTracer.
Ora, ogni volta che viene apportata una modifica al Valore Tracciato che rappresenta la congestione
finestra nel socket TCP, verrà eseguita la callback registrata e la funzione
CwndTracer verrà chiamato stampando i vecchi e nuovi valori della congestione TCP
finestra.
utilizzando , il Tracciato API
Esistono tre livelli di interazione con il sistema di tracciamento:
· L'utente principiante può facilmente controllare quali oggetti partecipano al tracciamento;
· Gli utenti intermedi possono estendere il sistema di tracciamento per modificare il formato di output generato
o utilizzare le fonti di traccia esistenti in modi diversi, senza modificare il nucleo del
simulatore;
· Gli utenti avanzati possono modificare il core del simulatore per aggiungere nuove fonti di tracciamento e sink.
utilizzando Traccia Helpers
. NS-3 gli helper di traccia forniscono un ambiente ricco per la configurazione e la selezione di diversi
tracciare gli eventi e scriverli nei file. Nelle sezioni precedenti, principalmente "Creazione
Topologie", abbiamo visto diverse varietà di metodi di tracciamento progettati per l'uso
all'interno di altri helper (del dispositivo).
Forse ricorderete di aver visto alcune di queste variazioni:
pointToPoint.EnablePcapAll ("secondo");
pointToPoint.EnablePcap ("secondo", p2pNodes.Get (0)->GetId (), 0);
csma.EnablePcap ("terzo", csmaDevices.Get (0), true);
pointToPoint.EnableAsciiAll (ascii.CreateFileStream ("myfirst.tr"));
Ciò che potrebbe non essere ovvio, tuttavia, è che esiste un modello coerente per tutti
metodi relativi alla traccia trovati nel sistema. Ora ci prendiamo un po' di tempo e diamo un'occhiata
al "quadro generale".
Attualmente ci sono due casi d'uso principali degli helper di tracciamento NS-3: Aiutanti del dispositivo
e helper di protocollo. Gli helper dei dispositivi affrontano il problema di specificare quali tracce dovrebbero
essere abilitato tramite un nodo, una coppia di dispositivi. Ad esempio, potresti voler specificare che pcap
il tracciamento dovrebbe essere abilitato su un dispositivo specifico su un nodo specifico. Ciò deriva da
NS-3 modello concettuale del dispositivo, e anche i modelli concettuali dei vari dispositivi
aiutanti. Seguendo naturalmente da questo, i file creati seguono un
- - convenzione di denominazione.
Gli helper del protocollo esaminano il problema di specificare quali tracce devono essere abilitate
una coppia di protocollo e interfaccia. Ciò deriva dal NS-3 modello concettuale dello stack di protocolli,
e anche i modelli concettuali degli helper dello stack Internet. Naturalmente, i file di traccia
dovrebbe seguire un - - convenzione di denominazione.
Gli helper di traccia rientrano quindi naturalmente in una tassonomia bidimensionale. Ci sono
sottigliezze che impediscono a tutte e quattro le classi di comportarsi in modo identico, ma ci sforziamo di farlo
farli funzionare tutti nel modo più simile possibile; e quando possibile ci sono analoghi per
tutti i metodi in tutte le classi.
┌───────────────┬──────┬───────┐
│ │ pcap │ ascii │
├───────────────┼──────┼───────┤
│Assistente dispositivo │ │ │
├───────────────┼──────┼───────┤
│Protocollo Helper │ │ │
└────────────────┴──────┴───────┘
Usiamo un approccio chiamato a mixin per aggiungere funzionalità di tracciamento alle nostre classi di supporto. UN
mixin è una classe che fornisce funzionalità ereditate da una sottoclasse.
Ereditare da un mixin non è considerato una forma di specializzazione ma è davvero un modo per farlo
raccogliere funzionalità.
Diamo una rapida occhiata a tutti e quattro questi casi e ai rispettivi mixin.
Pcap Tracciato Dispositivo Helpers
L'obiettivo di questi helper è quello di semplificare l'aggiunta di una struttura di tracciamento pcap coerente a un
NS-3 dispositivo. Vogliamo che tutti i vari tipi di tracciamento pcap funzionino allo stesso modo su
tutti i dispositivi, quindi i metodi di questi helper vengono ereditati dagli helper dei dispositivi. Guarda
at src/network/helper/trace-helper.h se vuoi seguire la discussione mentre guardi
codice reale.
La classe PcapHelperForDevice è un mixin fornisce la funzionalità di alto livello per l'utilizzo
tracciamento pcap in un NS-3 dispositivo. Ogni dispositivo deve implementare un unico metodo virtuale
ereditato da questa classe.:
void virtuale EnablePcapInternal (std::string prefisso, Ptr nd, bool promiscuo) = 0;
La firma di questo metodo riflette la visione incentrata sul dispositivo della situazione in questo momento
livello. Tutti i metodi pubblici ereditati dalla classe PcapUserHelperForDevice ridurre a
chiamando questo singolo metodo di implementazione dipendente dal dispositivo. Ad esempio, il livello più basso
metodo pcap,:
void EnablePcap (std::string prefix, Ptr nd, bool promiscuus = false, bool esplicitoFilename = false);
chiamerà l'implementazione del dispositivo di AbilitaPcapInternal direttamente. Tutti gli altri pcap pubblici
i metodi di tracciamento si basano su questa implementazione per fornire un livello utente aggiuntivo
funzionalità. Ciò che questo significa per l'utente è che tutti gli assistenti del dispositivo nel sistema lo faranno
avere tutti i metodi di tracciamento pcap disponibili; e questi metodi funzioneranno tutti allo stesso modo
attraverso i dispositivi se il dispositivo implementa AbilitaPcapInternal correttamente.
Pcap Tracciato Dispositivo Aiutante Metodi
void EnablePcap (std::string prefisso, Ptr e,
bool promiscuous = false, bool explicitFilename = false);
void EnablePcap (std::string prefisso, std::string ndName,
bool promiscuous = false, bool explicitFilename = false);
void EnablePcap (std::string prefisso, NetDeviceContainer d,
bool promiscuo = falso);
void EnablePcap (std::string prefisso, NodeContainer n,
bool promiscuo = falso);
void EnablePcap (std::string prefisso, uint32_t nodeid, uint32_t deviceid,
bool promiscuo = falso);
void EnablePcapAll (std::string prefisso, bool promiscuo = false);
In ciascuno dei metodi mostrati sopra, c'è un parametro predefinito chiamato promiscuo che
il valore predefinito è falso. Questo parametro indica che la traccia non deve essere raccolta in
modalità promiscua. Se vuoi che le tue tracce includano tutto il traffico visto dal dispositivo
(e se il dispositivo supporta una modalità promiscua) aggiungi semplicemente un parametro true a uno qualsiasi dei file
chiamate di cui sopra. Ad esempio:
pt nd;
...
helper.EnablePcap ("prefisso", nd, true);
abiliterà le acquisizioni in modalità promiscua su NetDevice specificato da nd.
I primi due metodi includono anche un parametro predefinito chiamato nomefile esplicito quella volontà
essere discusso di seguito.
Ti invitiamo a consultare il Doxygen per la classe PcapHelperForDevice per trovare i dettagli
di questi metodi; ma per riassumere ...
È possibile abilitare il tracciamento pcap su una particolare coppia nodo/dispositivo di rete fornendo un
pt a un AbilitaPcap metodo. Il Ptr è implicito dal dispositivo di rete
deve appartenere esattamente a uno Nodo. Per esempio,:
pt nd;
...
helper.EnablePcap ("prefisso", nd);
È possibile abilitare il tracciamento pcap su una particolare coppia nodo/dispositivo di rete fornendo un
std::stringa che rappresenta una stringa di servizio del nome oggetto in un AbilitaPcap metodo. Il
pt viene cercato dalla stringa del nome. Ancora una volta, il è implicito poiché il
il dispositivo di rete denominato deve appartenere esattamente a uno Nodo. Per esempio,:
Nomi::Add ("server" ...);
Nomi::Add ("server/eth0" ...);
...
helper.EnablePcap ("prefisso", "server/ath0");
È possibile abilitare il tracciamento pcap su una raccolta di coppie nodo/dispositivo di rete fornendo un
NetDeviceContenitore. Per ciascuno NetDevice nel contenitore viene verificato il tipo. Per ciascuno
dispositivo del tipo corretto (lo stesso tipo gestito dall'helper del dispositivo), la traccia è
abilitato. Ancora una volta, il è implicito poiché il dispositivo di rete trovato deve appartenere esattamente a
prima Nodo. Per esempio,:
NetDeviceContainer d = ...;
...
helper.EnablePcap ("prefisso", d);
È possibile abilitare il tracciamento pcap su una raccolta di coppie nodo/dispositivo di rete fornendo un
NodoContenitore. Per ciascuno Nodo nella NodoContenitore è allegato NetDevice sono iterati.
Per ciascun NetDevice collegato a ciascun nodo nel contenitore, il tipo di quel dispositivo è
controllato. Per ogni dispositivo del tipo corretto (lo stesso tipo gestito dal dispositivo
helper), il tracciamento è abilitato.:
NodoContenitore n;
...
helper.EnablePcap ("prefisso", n);
È possibile abilitare il tracciamento pcap sulla base dell'ID nodo e dell'ID dispositivo, nonché con esplicito
Ptr. Ogni Nodo nel sistema ha un ID nodo intero e ogni dispositivo è connesso a un nodo
ha un ID dispositivo intero.:
helper.EnablePcap ("prefisso", 21, 1);
Infine, puoi abilitare il tracciamento pcap per tutti i dispositivi nel sistema, con lo stesso tipo di
gestito dall'helper del dispositivo.:
helper.EnablePcapAll ("prefisso");
Pcap Tracciato Dispositivo Aiutante Nome del file Selezione
Implicita nelle descrizioni del metodo di cui sopra è la costruzione di un nome file completo da parte di
il metodo di implementazione. Per convenzione, pcap traccia nel NS-3 sistema sono della forma
- id>- id>.pcap
Come accennato in precedenza, ogni nodo nel sistema avrà un ID nodo assegnato dal sistema; e
ogni dispositivo avrà un indice di interfaccia (chiamato anche ID dispositivo) relativo al suo nodo.
Per impostazione predefinita, quindi, un file di traccia pcap creato come risultato dell'abilitazione della traccia sul primo
il dispositivo del nodo 21 che utilizza il prefisso "prefisso" sarebbe prefisso-21-1.pcap.
Puoi sempre usare il file NS-3 servizio nome oggetto per renderlo più chiaro. Ad esempio, se
si utilizza il servizio di nomi di oggetti per assegnare il nome "server" al nodo 21, il pcap risultante
il nome del file di traccia diventerà automaticamente, prefisso-server-1.pcap e se assegni anche il
nome "eth0" al dispositivo, il nome del file pcap lo rileverà automaticamente e sarà
detto prefisso-server-eth0.pcap.
Infine, due dei metodi mostrati sopra:
void EnablePcap (std::string prefix, Ptr nd, bool promiscuus = false, bool esplicitoFilename = false);
void EnablePcap (std::string prefix, std::string ndName, bool promiscuous = false, bool esplicitoFilename = false);
avere un parametro predefinito chiamato nomefile esplicito. Quando è impostato su true, questo parametro
disabilita il meccanismo di completamento automatico del nome file e consente di creare un file esplicito
nome file. Questa opzione è disponibile solo nei metodi che abilitano il tracciamento pcap su un
unico dispositivo.
Ad esempio, per fare in modo che un helper del dispositivo crei un singolo pcap promiscuo
file di acquisizione di un nome specifico (il mio-pcap-file.pcap) su un dato dispositivo, si potrebbe:
pt nd;
...
helper.EnablePcap ("my-pcap-file.pcap", nd, true, true);
Il primo vero parametro abilita tracce in modalità promiscua e il secondo dice all'helper
interpretare il prefisso parametro come un nome file completo.
Ascii Tracciato Dispositivo Helpers
Il comportamento dell'helper di tracciamento ASCII mixin è sostanzialmente simile alla versione pcap.
Dare un'occhiata a src/network/helper/trace-helper.h se vuoi seguire la discussione
mentre guardi il codice reale.
La classe AsciiTraceHelperForDevice aggiunge la funzionalità di alto livello per l'utilizzo di ascii
tracciamento a una classe helper del dispositivo. Come nel caso pcap, ogni dispositivo deve implementare un
singolo metodo virtuale ereditato dalla traccia ascii mixin.:
void virtuale EnableAsciiInternal (Ptr flusso, prefisso std::string, Ptr nd) = 0;
La firma di questo metodo riflette la visione incentrata sul dispositivo della situazione in questo momento
livello; e anche il fatto che l'helper possa scrivere su un flusso di output condiviso. Tutto di
i metodi pubblici correlati a ascii-trace ereditati dalla classe AsciiTraceHelperForDevice
ridurre a chiamare questo singolo metodo di implementazione dipendente dal dispositivo. Ad esempio, il
metodi di tracciamento ASCII di livello più basso:
void EnableAscii (std::string prefisso, Ptr nd);
void EnableAscii (Ptr ruscello, pt nd);
chiamerà l'implementazione del dispositivo di AbilitaAsciiInternal direttamente, fornendo a
prefisso o flusso valido. Tutti gli altri metodi di tracciamento ASCII pubblici si baseranno su questi
funzioni di basso livello per fornire funzionalità aggiuntive a livello utente. Cosa significa questo per l'
l'utente è che tutti gli helper del dispositivo nel sistema avranno tutti i metodi di tracciamento ASCII
a disposizione; e questi metodi funzioneranno tutti allo stesso modo su tutti i dispositivi se i dispositivi
realizzare Abilitazione interna correttamente.
Ascii Tracciato Dispositivo Aiutante Metodi
void EnableAscii (std::string prefisso, Ptr nd);
void EnableAscii (Ptr ruscello, pt nd);
void EnableAscii (std::string prefisso, std::string ndName);
void EnableAscii (Ptr stream, std::string ndName);
void EnableAscii (std::string prefisso, NetDeviceContainer d);
void EnableAscii (Ptr flusso, NetDeviceContainer d);
void EnableAscii (std::string prefisso, NodeContainer n);
void EnableAscii (Ptr flusso, NodeContainer n);
void EnableAscii (std::string prefix, uint32_t nodeid, uint32_t deviceid);
void EnableAscii (Ptr stream, uint32_t nodeid, uint32_t deviceid);
void EnableAsciiAll (std::string prefisso);
void EnableAsciiAll (Ptr flusso);
Ti invitiamo a consultare il Doxygen per la classe TraceHelperForDevice per trovare l'
dettagli di questi metodi; ma per riassumere...
Ci sono il doppio dei metodi disponibili per il tracciamento ASCII rispetto a quelli disponibili per il PCap
tracciamento. Questo perché, oltre al modello in stile pcap in cui le tracce da ogni
la coppia univoca di nodo/dispositivo viene scritta in un file univoco, supportiamo un modello in cui trace
le informazioni per molte coppie nodo/dispositivo vengono scritte in un file comune. Ciò significa che il
- - il meccanismo di generazione del nome file è sostituito da un meccanismo a
fare riferimento a un file comune; e il numero di metodi API è raddoppiato per consentire tutto
combinazioni.
Proprio come nel tracciamento pcap, è possibile abilitare il tracciamento ascii su una particolare coppia nodo/dispositivo di rete
fornendo un pt a un Abilita Ascii metodo. Il Ptr è implicito poiché
il dispositivo di rete deve appartenere esattamente a uno Nodo. Per esempio,:
pt nd;
...
helper.EnableAscii ("prefisso", nd);
In questo caso, nessun contesto di traccia viene scritto nel file di traccia ascii poiché sarebbero
ridondante. Il sistema selezionerà il nome del file da creare utilizzando le stesse regole di
descritto nella sezione pcap, tranne per il fatto che il file avrà il suffisso ".tr" invece di
".pcap".
Se si desidera abilitare il tracciamento ASCII su più di un dispositivo di rete e inviare tutte le tracce
a un singolo file, puoi farlo anche utilizzando un oggetto per fare riferimento a un singolo file:
pt nd1;
pt nd2;
...
pt stream = asciiTraceHelper.CreateFileStream ("trace-file-name.tr");
...
helper.EnableAscii (flusso, nd1);
helper.EnableAscii (flusso, nd2);
In questo caso, i contesti di traccia vengono scritti nel file di traccia ASCII poiché sono necessari
per disambiguare le tracce dai due dispositivi. Si noti che poiché l'utente è completamente
specificando il nome del file, la stringa dovrebbe includere ".tr" per coerenza.
È possibile abilitare il tracciamento ASCII su una particolare coppia nodo/dispositivo di rete fornendo un
std::stringa che rappresenta una stringa di servizio del nome oggetto in un AbilitaPcap metodo. Il
pt viene cercato dalla stringa del nome. Ancora una volta, il è implicito poiché il
il dispositivo di rete denominato deve appartenere esattamente a uno Nodo. Per esempio,:
Nomi::Add ("client" ...);
Nomi::Add ("client/eth0" ...);
Nomi::Add ("server" ...);
Nomi::Add ("server/eth0" ...);
...
helper.EnableAscii ("prefisso", "client/eth0");
helper.EnableAscii ("prefisso", "server/eth0");
Ciò si tradurrebbe in due file denominati prefisso-client-eth0.tr e prefisso-server-eth0.tr con
tracce per ogni dispositivo nel rispettivo file di traccia. Poiché tutti i file EnableAscii
le funzioni sono sovraccaricate per accettare un wrapper di flusso, puoi usare anche quella forma:
Nomi::Add ("client" ...);
Nomi::Add ("client/eth0" ...);
Nomi::Add ("server" ...);
Nomi::Add ("server/eth0" ...);
...
pt stream = asciiTraceHelper.CreateFileStream ("trace-file-name.tr");
...
helper.EnableAscii (stream, "client/eth0");
helper.EnableAscii (stream, "server/eth0");
Ciò risulterebbe in un unico file di traccia chiamato nome-file-traccia.tr che contiene tutto
gli eventi di traccia per entrambi i dispositivi. Gli eventi sarebbero disambiguati dal contesto di traccia
stringhe.
È possibile abilitare il tracciamento ASCII su una raccolta di coppie nodo/dispositivo di rete fornendo un
NetDeviceContenitore. Per ciascuno NetDevice nel contenitore viene verificato il tipo. Per ciascuno
dispositivo del tipo corretto (lo stesso tipo gestito dall'helper del dispositivo), la traccia è
abilitato. Ancora una volta, il è implicito poiché il dispositivo di rete trovato deve appartenere esattamente a
prima Nodo. Per esempio,:
NetDeviceContainer d = ...;
...
helper.EnableAscii ("prefisso", d);
Ciò comporterebbe la creazione di un certo numero di file di traccia ASCII, ognuno dei quali segue
il - - convenzione .tr. Combinando tutte le tracce in a
singolo file viene eseguito in modo simile agli esempi precedenti:
NetDeviceContainer d = ...;
...
pt stream = asciiTraceHelper.CreateFileStream ("trace-file-name.tr");
...
helper.EnableAscii (flusso, d);
È possibile abilitare il tracciamento ASCII su una raccolta di coppie nodo/dispositivo di rete fornendo un
NodoContenitore. Per ciascuno Nodo nella NodoContenitore è allegato NetDevice sono iterati.
Per ciascun NetDevice collegato a ciascun nodo nel contenitore, il tipo di quel dispositivo è
controllato. Per ogni dispositivo del tipo corretto (lo stesso tipo gestito dal dispositivo
helper), il tracciamento è abilitato.:
NodoContenitore n;
...
helper.EnableAscii ("prefisso", n);
Ciò comporterebbe la creazione di un certo numero di file di traccia ASCII, ognuno dei quali segue
il - - convenzione .tr. Combinando tutte le tracce in a
singolo file viene eseguito in modo simile agli esempi precedenti:
È possibile abilitare il tracciamento pcap sulla base dell'ID nodo e dell'ID dispositivo, nonché con esplicito
Ptr. Ogni Nodo nel sistema ha un ID nodo intero e ogni dispositivo è connesso a un nodo
ha un ID dispositivo intero.:
helper.EnableAscii ("prefisso", 21, 1);
Naturalmente, le tracce possono essere combinate in un unico file come mostrato sopra.
Infine, puoi abilitare il tracciamento pcap per tutti i dispositivi nel sistema, con lo stesso tipo di
gestito dall'helper del dispositivo.:
helper.EnableAsciiAll ("prefisso");
Ciò comporterebbe la creazione di un certo numero di file di traccia ASCII, uno per ogni dispositivo in
il sistema del tipo gestito dall'helper. Tutti questi file seguiranno il
- - Convenzione .tr. Combinazione di tutte le tracce in un unico
il file viene eseguito in modo simile agli esempi precedenti.
Ascii Tracciato Dispositivo Aiutante Nome del file Selezione
Implicita nelle descrizioni del metodo in stile prefisso di cui sopra è la costruzione del completo
nomi di file in base al metodo di implementazione. Per convenzione, le tracce ASCII nel NS-3 il sistema è
del modulo - id>- id>.tr.
Come accennato in precedenza, ogni nodo nel sistema avrà un ID nodo assegnato dal sistema; e
ogni dispositivo avrà un indice di interfaccia (chiamato anche ID dispositivo) relativo al suo nodo.
Per impostazione predefinita, quindi, un file di traccia ascii creato come risultato dell'abilitazione della tracciatura sul primo
dispositivo del nodo 21, utilizzando il prefisso "prefisso", sarebbe prefisso-21-1.tr.
Puoi sempre usare il file NS-3 servizio nome oggetto per renderlo più chiaro. Ad esempio, se
si utilizza il servizio di denominazione degli oggetti per assegnare il nome "server" al nodo 21, il risultato
il nome del file di traccia ascii diventerà automaticamente, prefisso-server-1.tr e se assegni anche tu
il nome "eth0" al dispositivo, il nome del file di traccia ASCII lo rileverà automaticamente
ed essere chiamato prefisso-server-eth0.tr.
Pcap Tracciato Protocollo Helpers
L'obiettivo di questi mixin è quello di semplificare l'aggiunta di una struttura di tracciamento pcap coerente a
protocolli. Vogliamo che tutti i vari tipi di tracciamento pcap funzionino allo stesso modo su tutti
protocolli, quindi i metodi di questi helper vengono ereditati dagli stack helper. Date un'occhiata al
src/network/helper/trace-helper.h se vuoi seguire la discussione mentre guardi
codice reale.
In questa sezione illustreremo i metodi applicati al protocollo Ipv4. A
specificare le tracce in protocolli simili, basta sostituire il tipo appropriato. Per esempio,
Usa un pt invece di a pt e chiama AbilitaPcapIpv6 invece di AbilitaPcapIpv4.
La classe PcapHelperForIpv4 fornisce la funzionalità di alto livello per l'utilizzo del tracciamento pcap
nella Ipv4 protocollo. Ciascun helper di protocollo che abilita questi metodi deve implementarne uno
metodo virtuale ereditato da questa classe. Ci sarà un'implementazione separata per
Ipv6, ad esempio, ma l'unica differenza sarà nei nomi e nelle firme dei metodi.
Sono necessari nomi di metodi diversi per disambiguare la classe Ipv4 da Ipv6 che sono entrambi
derivato da classe Oggettoe metodi che condividono la stessa firma.:
void virtuale EnablePcapIpv4Internal (std::string prefisso, Ptr interfaccia ipv4, uint32_t) = 0;
La firma di questo metodo riflette il protocollo e la vista incentrata sull'interfaccia di
situazione a questo livello. Tutti i metodi pubblici ereditati dalla classe PcapHelperForIpv4
ridurre a chiamare questo singolo metodo di implementazione dipendente dal dispositivo. Ad esempio, il
metodo pcap di livello più basso:
void EnablePcapIpv4 (std::string prefisso, Ptr interfaccia ipv4, uint32_t);
chiamerà l'implementazione del dispositivo di AbilitaPcapIpv4Internal direttamente. Tutti gli altri pubblici
I metodi di tracciamento pcap si basano su questa implementazione per fornire ulteriori funzionalità a livello utente
funzionalità. Ciò significa per l'utente che tutti gli helper del protocollo nel sistema saranno
avere tutti i metodi di tracciamento pcap disponibili; e questi metodi funzioneranno tutti allo stesso modo
modo attraverso i protocolli se l'helper implementa AbilitaPcapIpv4Internal correttamente.
Pcap Tracciato Protocollo Aiutante Metodi
Questi metodi sono progettati per essere in corrispondenza uno a uno con l' Nodo- E
NetDevice- versioni centrate delle versioni del dispositivo. Invece di Nodo e NetDevice (coppia),
vincoli, utilizziamo vincoli di protocollo e di interfaccia.
Nota che, proprio come nella versione del dispositivo, ci sono sei metodi:
void EnablePcapIpv4 (std::string prefisso, Ptr interfaccia ipv4, uint32_t);
void EnablePcapIpv4 (std::string prefisso, std::string ipv4Name, uint32_t interfaccia);
void EnablePcapIpv4 (std::prefisso di stringa, Ipv4InterfaceContainer c);
void EnablePcapIpv4 (std::prefisso di stringa, NodeContainer n);
void EnablePcapIpv4 (std::string prefisso, uint32_t nodeid, uint32_t interfaccia);
void EnablePcapIpv4All (std::prefisso stringa);
Ti invitiamo a consultare il Doxygen per la classe PcapHelperForIpv4 per trovare i dettagli
di questi metodi; ma per riassumere ...
È possibile abilitare il tracciamento pcap su una particolare coppia protocollo/interfaccia fornendo un
pt e interfaccia a un AbilitaPcap metodo. Ad esempio:
pt ipv4 = nodo->GetObject ();
...
helper.EnablePcapIpv4 ("prefisso", ipv4, 0);
È possibile abilitare il tracciamento pcap su una particolare coppia nodo/dispositivo di rete fornendo un
std::stringa che rappresenta una stringa di servizio del nome oggetto in un AbilitaPcap metodo. Il
pt viene ricercato a partire dalla stringa del nome. Ad esempio:
Nomi::Add ("serverIPv4" ...);
...
helper.EnablePcapIpv4 ("prefisso", "serverIpv4", 1);
È possibile abilitare il tracciamento pcap su una raccolta di coppie protocollo/interfaccia fornendo un
Contenitore di interfaccia IPv4. Per ciascuno Ipv4 / coppia di interfacce nel contenitore il tipo di protocollo
è controllato. Per ogni protocollo del tipo corretto (lo stesso tipo gestito dal
helper del dispositivo), il tracciamento è abilitato per l'interfaccia corrispondente. Ad esempio:
nodi NodeContainer;
...
Dispositivi NetDeviceContainer = deviceHelper.Install (nodi);
...
Ipv4AddressHelper ipv4;
ipv4.SetBase ("10.1.1.0", "255.255.255.0");
Interfacce Ipv4InterfaceContainer = ipv4.Assign (dispositivi);
...
helper.EnablePcapIpv4 ("prefisso", interfacce);
È possibile abilitare il tracciamento pcap su una raccolta di coppie protocollo/interfaccia fornendo un
NodoContenitore. Per ciascuno Nodo nella NodoContenitore si trova il protocollo appropriato. Per
ogni protocollo, le sue interfacce vengono enumerate e il tracciamento è abilitato sul risultato
coppie. Ad esempio:
NodoContenitore n;
...
helper.EnablePcapIpv4 ("prefisso", n);
È possibile abilitare il tracciamento pcap anche in base all'ID del nodo e all'interfaccia. In questo caso,
il node-id viene tradotto in un Ptr e il protocollo appropriato viene ricercato nel
nodo. Il protocollo e l'interfaccia risultanti vengono utilizzati per specificare la traccia risultante
fonte.:
helper.EnablePcapIpv4 ("prefisso", 21, 1);
Infine, è possibile abilitare il tracciamento pcap per tutte le interfacce nel sistema, con i relativi
protocollo dello stesso tipo di quello gestito dal device helper.:
helper.EnablePcapIpv4All ("prefisso");
Pcap Tracciato Protocollo Aiutante Nome del file Selezione
Implicita in tutte le descrizioni del metodo di cui sopra è la costruzione del completo
nomi di file in base al metodo di implementazione. Per convenzione, le tracce pcap vengono prese per i dispositivi in
, il NS-3 sistema sono della forma - id>- id>.pcap. In caso di
tracce di protocollo, esiste una corrispondenza biunivoca tra protocolli e Nodes. Questo è
perché il protocollo Oggetti sono aggregati a Nodo OggettiPoiché non esiste un protocollo globale
id nel sistema, utilizziamo l'id del nodo corrispondente nella denominazione dei file. Pertanto esiste un
possibilità di collisioni di nomi di file nei nomi di file di traccia scelti automaticamente. Per questo
motivo per cui la convenzione del nome file è cambiata per le tracce del protocollo.
Come accennato in precedenza, ogni nodo del sistema avrà un ID nodo assegnato dal sistema.
Poiché esiste una corrispondenza uno a uno tra le istanze del protocollo e le istanze del nodo
usiamo l'ID del nodo. Ogni interfaccia ha un ID di interfaccia relativo al suo protocollo. Usiamo
la convenzione" -n -io .pcap" per la denominazione dei file di traccia in
aiutanti del protocollo.
Pertanto, per impostazione predefinita, un file di traccia pcap creato come risultato dell'abilitazione della traccia su
l'interfaccia 1 del protocollo IPv4 del nodo 21 utilizzando il prefisso "prefisso" sarebbe
"prefisso-n21-i1.pcap".
Puoi sempre usare il file NS-3 servizio nome oggetto per renderlo più chiaro. Ad esempio, se
si utilizza il servizio di denominazione degli oggetti per assegnare il nome "serverIpv4" al Ptr sul nodo
21, il nome del file di traccia pcap risultante diventerà automaticamente,
"prefisso-nserverIpv4-i1.pcap".
Ascii Tracciato Protocollo Helpers
Il comportamento degli helper di tracciamento ASCII è sostanzialmente simile al caso pcap. Prendi un
guardare src/network/helper/trace-helper.h se vuoi seguire la discussione mentre
guardando il codice reale.
In questa sezione illustreremo i metodi applicati al protocollo Ipv4. A
specificare le tracce in protocolli simili, basta sostituire il tipo appropriato. Per esempio,
Usa un pt invece di a pt e chiama Abilita AsciiIpv6 invece di
Abilita AsciiIpv4.
La classe AsciiTraceHelperForIpv4 aggiunge la funzionalità di alto livello per l'utilizzo di ascii
tracciamento a un assistente di protocollo. Ogni protocollo che abilita questi metodi deve implementare a
singolo metodo virtuale ereditato da questa classe.:
void virtuale EnableAsciiIpv4Internal (Ptr flusso, prefisso std::string,
Ptr interfaccia ipv4, uint32_t) = 0;
La firma di questo metodo riflette la vista incentrata sul protocollo e sull'interfaccia di
situazione a questo livello; e anche il fatto che l'assistente possa scrivere a una persona condivisa
flusso di uscita. Tutti i metodi pubblici ereditati dalla classe
PcapAndAsciiTraceHelperForIpv4 ridurre a chiamare questo singolo dispositivo dipendente
metodo di implementazione. Ad esempio, i metodi di tracciamento ASCII di livello più basso:
void EnableAsciiIpv4 (std::string prefisso, Ptr interfaccia ipv4, uint32_t);
void EnableAsciiIpv4 (Ptr ruscello, pt ipv4, interfaccia uint4_t);
chiamerà l'implementazione del dispositivo di AbilitaAsciiIpv4Internal direttamente, fornendo entrambi
il prefisso o il flusso. Tutti gli altri metodi di tracciamento ASCII pubblici si baseranno su questi
funzioni di basso livello per fornire funzionalità aggiuntive a livello utente. Cosa significa questo per l'
l'utente è che tutti gli helper del dispositivo nel sistema avranno tutti i metodi di tracciamento ASCII
a disposizione; e questi metodi funzioneranno tutti allo stesso modo su tutti i protocolli se il
implementano i protocolli Abilitazione interna correttamente.
Ascii Tracciato Dispositivo Aiutante Metodi
void EnableAsciiIpv4 (std::string prefisso, Ptr interfaccia ipv4, uint32_t);
void EnableAsciiIpv4 (Ptr ruscello, pt ipv4, interfaccia uint4_t);
void EnableAsciiIpv4 (std::string prefisso, std::string ipv4Name, uint32_t interfaccia);
void EnableAsciiIpv4 (Ptr stream, std::string ipv4Name, interfaccia uint32_t);
void EnableAsciiIpv4 (std::prefisso di stringa, Ipv4InterfaceContainer c);
void EnableAsciiIpv4 (Ptr flusso, Ipv4InterfaceContainer c);
void EnableAsciiIpv4 (std::prefisso di stringa, NodeContainer n);
void EnableAsciiIpv4 (Ptr flusso, NodeContainer n);
void EnableAsciiIpv4 (std::string prefix, uint32_t nodeid, uint32_t deviceid);
void EnableAsciiIpv4 (Ptr stream, uint32_t nodeid, interfaccia uint32_t);
void EnableAsciiIpv4All (std::prefisso di stringa);
void EnableAsciiIpv4All (Ptr flusso);
Ti invitiamo a consultare il Doxygen per la classe PcapAndAsciiHelperForIpv4 per trovare l'
dettagli di questi metodi; ma per riassumere...
Ci sono il doppio dei metodi disponibili per il tracciamento ASCII rispetto a quelli disponibili per il PCap
tracciamento. Questo perché, oltre al modello in stile pcap in cui le tracce da ogni
la coppia protocollo/interfaccia univoca viene scritta in un file univoco, supportiamo un modello in cui
le informazioni di traccia per molte coppie protocollo/interfaccia vengono scritte in un file comune. Questo
significa che il -N - il meccanismo di generazione del nome del file è sostituito
tramite un meccanismo per fare riferimento a un file comune; e il numero di metodi API è raddoppiato per
consentire tutte le combinazioni.
Proprio come nel tracciamento pcap, è possibile abilitare il tracciamento ascii su un particolare protocollo/interfaccia
coppia fornendo a pt e interfaccia a un Abilita Ascii metodo. Ad esempio:
pt ipv4;
...
helper.EnableAsciiIpv4 ("prefisso", ipv4, 1);
In questo caso, nessun contesto di traccia viene scritto nel file di traccia ascii poiché sarebbero
ridondante. Il sistema selezionerà il nome del file da creare utilizzando le stesse regole di
descritto nella sezione pcap, tranne per il fatto che il file avrà il suffisso ".tr" invece di
".pcap".
Se si desidera abilitare il tracciamento ASCII su più di un'interfaccia e inviare tutte le tracce a
un singolo file, puoi farlo anche usando un oggetto per fare riferimento a un singolo file. Noi
abbiamo già qualcosa di simile nell'esempio "cwnd" qui sopra:
pt protocollo4 = nodo1->GetObject ();
pt protocollo4 = nodo2->GetObject ();
...
pt stream = asciiTraceHelper.CreateFileStream ("trace-file-name.tr");
...
helper.EnableAsciiIpv4 (flusso, protocollo1, 1);
helper.EnableAsciiIpv4 (flusso, protocollo2, 1);
In questo caso, i contesti di traccia vengono scritti nel file di traccia ASCII poiché sono necessari
per disambiguare le tracce dalle due interfacce. Si noti che poiché l'utente è completamente
specificando il nome del file, la stringa dovrebbe includere ".tr" per coerenza.
È possibile abilitare il tracciamento ASCII su un protocollo particolare fornendo un std::stringa
che rappresenta una stringa di servizio del nome oggetto in un AbilitaPcap metodo. Il pt is
alzato lo sguardo dalla stringa del nome. Il nei nomi dei file risultanti è implicito poiché
esiste una corrispondenza uno a uno tra istanze di protocollo e nodi, ad esempio:
Nomi::Add ("node1Ipv4" ...);
Nomi::Add ("node2Ipv4" ...);
...
helper.EnableAsciiIpv4 ("prefisso", "node1Ipv4", 1);
helper.EnableAsciiIpv4 ("prefisso", "node2Ipv4", 1);
Ciò risulterebbe in due file denominati "prefix-nnode1Ipv4-i1.tr" e
"prefix-nnode2Ipv4-i1.tr" con tracce per ciascuna interfaccia nel rispettivo file di traccia.
Poiché tutte le funzioni EnableAscii sono sovraccaricate per accettare un wrapper di flusso, è possibile
usa anche quel modulo:
Nomi::Add ("node1Ipv4" ...);
Nomi::Add ("node2Ipv4" ...);
...
pt stream = asciiTraceHelper.CreateFileStream ("trace-file-name.tr");
...
helper.EnableAsciiIpv4 (flusso, "node1Ipv4", 1);
helper.EnableAsciiIpv4 (flusso, "node2Ipv4", 1);
Ciò si tradurrebbe in un singolo file di traccia denominato "trace-file-name.tr" che contiene tutti i
gli eventi di traccia per entrambe le interfacce. Gli eventi sarebbero disambiguati dal contesto di traccia
stringhe.
È possibile abilitare il tracciamento ASCII su una raccolta di coppie protocollo/interfaccia fornendo un
Contenitore di interfaccia IPv4Per ogni protocollo del tipo appropriato (lo stesso tipo gestito
dall'helper del dispositivo), il tracciamento è abilitato per l'interfaccia corrispondente. Di nuovo, il
è implicito poiché esiste una corrispondenza uno a uno tra ciascun protocollo e
il suo nodo. Ad esempio:
nodi NodeContainer;
...
Dispositivi NetDeviceContainer = deviceHelper.Install (nodi);
...
Ipv4AddressHelper ipv4;
ipv4.SetBase ("10.1.1.0", "255.255.255.0");
Interfacce Ipv4InterfaceContainer = ipv4.Assign (dispositivi);
...
...
helper.EnableAsciiIpv4 ("prefisso", interfacce);
Ciò comporterebbe la creazione di un certo numero di file di traccia ASCII, ognuno dei quali segue
il -n -io convenzione .tr. Combinando tutte le tracce in a
singolo file viene eseguito in modo simile agli esempi precedenti:
nodi NodeContainer;
...
Dispositivi NetDeviceContainer = deviceHelper.Install (nodi);
...
Ipv4AddressHelper ipv4;
ipv4.SetBase ("10.1.1.0", "255.255.255.0");
Interfacce Ipv4InterfaceContainer = ipv4.Assign (dispositivi);
...
pt stream = asciiTraceHelper.CreateFileStream ("trace-file-name.tr");
...
helper.EnableAsciiIpv4 (stream, interfacce);
È possibile abilitare il tracciamento ASCII su una raccolta di coppie protocollo/interfaccia fornendo un
NodoContenitore. Per ciascuno Nodo nella NodoContenitore si trova il protocollo appropriato. Per
ogni protocollo, le sue interfacce vengono enumerate e il tracciamento è abilitato sul risultato
coppie. Ad esempio:
NodoContenitore n;
...
helper.EnableAsciiIpv4 ("prefisso", n);
Ciò comporterebbe la creazione di un certo numero di file di traccia ASCII, ognuno dei quali segue
il - - convenzione .tr. Combinando tutte le tracce in a
singolo file viene eseguito in modo simile agli esempi precedenti:
È possibile abilitare il tracciamento pcap anche in base all'ID del nodo e all'ID del dispositivo. In questo caso,
il node-id viene tradotto in un Ptr e il protocollo appropriato viene ricercato nel
nodo. Il protocollo e l'interfaccia risultanti vengono utilizzati per specificare la traccia risultante
fonte.:
helper.EnableAsciiIpv4 ("prefisso", 21, 1);
Naturalmente, le tracce possono essere combinate in un unico file come mostrato sopra.
Infine, è possibile abilitare il tracciamento ASCII per tutte le interfacce nel sistema, con i relativi
protocollo dello stesso tipo di quello gestito dal device helper.:
helper.EnableAsciiIpv4All ("prefisso");
Ciò comporterebbe la creazione di un certo numero di file di traccia ASCII, uno per ogni interfaccia
nel sistema relativo a un protocollo del tipo gestito dall'helper. Tutti questi file
seguirà il -N -io
in un unico file viene eseguito in modo simile agli esempi precedenti.
Ascii Tracciato Dispositivo Aiutante Nome del file Selezione
Implicita nelle descrizioni del metodo in stile prefisso di cui sopra è la costruzione del completo
nomi di file in base al metodo di implementazione. Per convenzione, le tracce ASCII nel NS-3 il sistema è
della forma " - - .tr."
Come accennato in precedenza, ogni nodo del sistema avrà un ID nodo assegnato dal sistema.
Poiché esiste una corrispondenza uno-a-uno tra protocolli e nodi, utilizziamo node-id
per identificare l'identità del protocollo. Ogni interfaccia su un determinato protocollo avrà un
indice di interfaccia (chiamato anche semplicemente interfaccia) relativo al suo protocollo. Per impostazione predefinita,
quindi, un file di traccia ascii creato come risultato dell'abilitazione della traccia sul primo dispositivo di
il nodo 21, utilizzando il prefisso "prefisso", sarebbe "prefisso-n21-i1.tr". Utilizzare il prefisso per
disambiguare più protocolli per nodo.
Puoi sempre usare il file NS-3 servizio nome oggetto per renderlo più chiaro. Ad esempio, se
si utilizza il servizio di denominazione degli oggetti per assegnare il nome "serverIpv4" al protocollo sul nodo
21 e specifica anche l'interfaccia uno, il nome del file di traccia ASCII risultante verrà automaticamente
diventa "prefix-nserverIpv4-1.tr".
Tracciato implementazione dettagli
Dati Collezione
Questo capitolo descrive il ns-3 Data Collection Framework (DCF), che fornisce
capacità di ottenere dati generati da modelli nel simulatore, per eseguire on-line
riduzione ed elaborazione dei dati, e per marshalling dati grezzi o trasformati in vari output
formati.
Il framework attualmente supporta le esecuzioni ns-3 standalone che non si basano su alcun elemento esterno
controllo dell'esecuzione del programma. Gli oggetti forniti dalla DCF possono essere agganciati a NS-3 tracciare
fonti per consentire l'elaborazione dei dati.
Il codice sorgente per le classi risiede nella directory src/statistiche.
Questo capitolo è organizzato come segue. Innanzitutto, una panoramica dell'architettura è
presentata. Successivamente, vengono presentati gli helper per queste classi; questo trattamento iniziale
dovrebbe consentire l'uso di base del quadro di raccolta dati per molti casi d'uso. Utenti che
desiderano produrre output al di fuori dello scopo degli attuali aiutanti, o che desiderano creare
i propri oggetti di raccolta dati, dovrebbe leggere il resto del capitolo, che va
in dettaglio su tutti i tipi di oggetti DCF di base e fornisce la codifica di basso livello
esempi.
Progettazione
Il DCF si compone di tre classi fondamentali:
· Campione è un meccanismo per strumentare e controllare l'output dei dati di simulazione che è
utilizzato per monitorare eventi interessanti. Produce output sotto forma di uno o più NS-3
fonti di traccia. Gli oggetti sonda sono collegati a una o più tracce lavelli (chiamato
Collezionisti), che elaborano i campioni in linea e li preparano per l'output.
· Collettore consuma i dati generati da uno o più oggetti Probe. si esibisce
trasformazioni sui dati, come la normalizzazione, la riduzione e il calcolo di
statistiche di base. Gli oggetti Collector non producono dati che vengono direttamente emessi dal
ns-3 corsa; invece, emettono i dati a valle su un altro tipo di oggetto, chiamato
Aggregator, che svolge tale funzione. In genere, i raccoglitori emettono i loro dati in
anche la forma di fonti di tracce, che consentono ai collezionisti di essere concatenati in serie.
· Aggregator è il punto finale dei dati raccolti da una rete di Probe e Collector.
La responsabilità principale dell'Aggregatore è quella di organizzare i dati e il loro corrispondente
metadati, in diversi formati di output come file di testo normale, file di fogli di calcolo o
banche dati.
Tutte e tre queste classi offrono la capacità di accendersi o spegnersi in modo dinamico
durante una simulazione.
Qualsiasi standalone NS-3 l'esecuzione di simulazione che utilizza il DCF in genere ne creerà almeno uno
istanza di ciascuna delle tre classi precedenti.
[immagine] Panoramica del quadro di raccolta dati.UNINDENT
Il flusso complessivo del trattamento dei dati è rappresentato in Dati Collezione Contesto panoramica.
Sul lato sinistro, una corsa NS-3 simulazione è rappresentata. Nel corso dell'esecuzione del
simulazione, i dati sono resi disponibili dai modelli tramite fonti di traccia o tramite altri mezzi.
Il diagramma mostra che le sonde possono essere collegate a queste sorgenti di traccia per ricevere dati
in modo asincrono o le sonde possono eseguire il polling dei dati. I dati vengono quindi passati a un oggetto di raccolta
che trasforma i dati. Infine, un aggregatore può essere collegato alle uscite del
collector, per generare grafici, file o database.
[immagine] Data Collection Framework aggregation.UNINDENT
Una variazione alla figura sopra è fornita in Dati Collezione Contesto aggregazione.
Questa seconda figura illustra che gli oggetti DCF possono essere concatenati in un modo
che gli oggetti a valle ricevano input da più oggetti a monte. La figura
mostra concettualmente che più sonde possono generare un output che viene alimentato in un singolo
collettore; ad esempio, un collettore che emette un rapporto di due contatori sarebbe
in genere acquisiscono i dati di ciascun contatore da sonde separate. Anche più collezionisti possono
alimentare in un unico aggregatore, che (come suggerisce il nome) può raccogliere una serie di dati
flussi per l'inclusione in un singolo grafico, file o database.
Dati Collezione Helpers
La piena flessibilità del quadro di raccolta dati è fornita dall'interconnessione
di sonde, collettori e aggregatori. L'esecuzione di tutte queste interconnessioni porta a
molte istruzioni di configurazione nei programmi utente. Per facilità d'uso, alcuni dei più comuni
le operazioni possono essere combinate e incapsulate in funzioni di supporto. Inoltre, alcuni
dichiarazioni che coinvolgono NS-3 le fonti di traccia non hanno collegamenti Python, a causa di limitazioni in
le legature.
Dati Collezione Helpers Panoramica
In questa sezione, forniamo una panoramica di alcune classi di supporto che sono state create per
facilitare la configurazione del framework di raccolta dati per alcuni casi d'uso comuni. Il
gli helper consentono agli utenti di formare operazioni comuni con solo poche istruzioni nel loro C++ o
Programmi Python. Ma questa facilità d'uso ha un costo notevolmente inferiore
flessibilità rispetto a quella che può fornire la configurazione di basso livello e la necessità di codificare esplicitamente
supporto per nuovi tipi di sonda negli helper (per aggirare un problema descritto di seguito).
L'enfasi sugli attuali aiutanti è quella di smistare i dati da NS-3 rintracciare le fonti in
gnuplot grafici o file di testo, senza un alto grado di personalizzazione dell'output o statistica
elaborazione (inizialmente). Inoltre, l'uso è vincolato ai tipi di sonda disponibili in
NS-3. Le sezioni successive di questa documentazione andranno più in dettaglio sulla creazione di nuovi
Tipi di sonda, nonché dettagli sull'aggancio di sonde, collettori e aggregatori
in accordi personalizzati.
Ad oggi sono stati implementati due aiutanti di Data Collection:
· GnuplotHelper
· FileHelper
GnuplotHelper
GnuplotHelper è una classe di supporto per la produzione di file di output utilizzati per creare gnuplot. Il
l'obiettivo generale è fornire la possibilità agli utenti di creare rapidamente grafici dai dati esportati
in NS-3 fonti di traccia. Per impostazione predefinita, viene eseguita una quantità minima di trasformazione dei dati;
l'obiettivo è generare grafici con il minor numero di istruzioni di configurazione (predefinite) come
possibile.
GnuplotHelper Panoramica
GnuplotHelper creerà 3 file diversi alla fine della simulazione:
· Un file di dati gnuplot separato da spazi
· Un file di controllo gnuplot
· Uno script di shell per generare gnuplot
Sono necessarie due istruzioni di configurazione per produrre grafici. Il primo
l'istruzione configura la trama (nome file, titolo, legende e tipo di output, dove l'output
il tipo predefinito è PNG se non specificato):
void ConfigurePlot (const std::string &outputFileNameWithoutExtension,
const std::string &titolo,
const std::string &xLegend,
const std::string &yLegend,
const std::string &terminalType = ".png");
La seconda istruzione aggancia la fonte di interesse della traccia:
void PlotProbe (const std::string &typeId,
const std::string &percorso,
const std::string &probeTraceSource,
const std::string &titolo);
Gli argomenti sono i seguenti:
· typeId: Il NS-3 TypeId della sonda
· percorso: Il percorso nel NS-3 spazio dei nomi di configurazione a una o più origini di traccia
· probeTraceSource: quale uscita della sonda (a sua volta una sorgente di traccia) deve essere tracciata
· titolo: il titolo da associare al set di dati (nella legenda di gnuplot)
Una variante del PlotProbe sopra è specificare un quinto argomento opzionale che controlli
dove nella trama è posizionata la chiave (leggenda).
Un esempio completamente funzionante (da settimo.cc) è mostrato di seguito:
// Crea l'helper gnuplot.
GnuplotHelper plotHelper;
// Configura la trama.
// Configura la trama. Il primo argomento è il prefisso del nome del file
// per i file di output generati. Il secondo, il terzo e il quarto
// gli argomenti sono, rispettivamente, il titolo del grafico, le etichette dell'asse x e dell'asse y
plotHelper.ConfigurePlot ("settimo-pacchetto-byte-count",
"Conteggio byte pacchetto vs. tempo",
"Tempo (secondi)",
"Conteggio byte pacchetto",
"png");
// Specifica il tipo di sonda, traccia il percorso di origine (nello spazio dei nomi di configurazione) e
// sonda l'origine della traccia dell'output ("OutputBytes") da tracciare. Il quarto argomento
// specifica il nome dell'etichetta della serie di dati sul grafico. L'ultimo
// argomento formatta il grafico specificando dove deve essere posizionata la chiave.
plotHelper.PlotProbe (probeType,
tracePath,
"Output Byte",
"Conteggio byte pacchetto",
GnuplotAggregator::KEY_BELOW);
In questo esempio, il tipo sonda e tracePath sono i seguenti (per IPv4):
probeType = "ns3::Ipv4PacketProbe";
tracePath = "/NodeList/*/$ns3::Ipv4L3Protocol/Tx";
Il probeType è un parametro chiave per il funzionamento di questo helper. Questo TypeId deve essere registrato
nel sistema e la firma sul sink di traccia della sonda deve corrispondere a quella della traccia
fonte a cui è collegato. I tipi di sonda sono predefiniti per un numero di tipi di dati
corrispondente NS-3 valori tracciati e per alcune altre firme di origine di traccia come
la fonte di traccia 'Tx' di ns3::Protocollo IPv4L3 classe.
Si noti che il percorso di origine della traccia specificato può contenere caratteri jolly. In questo caso, multiplo
i set di dati sono tracciati su un grafico; uno per ogni percorso abbinato.
L'output principale prodotto sarà tre file:
settimo-pacchetto-byte-count.dat
settimo-pacchetto-byte-count.plt
settimo-pacchetto-byte-count.sh
A questo punto, gli utenti possono modificare manualmente il file .plt per ulteriori personalizzazioni, oppure
basta eseguirlo tramite gnuplot. In esecuzione sh settimo-pacchetto-byte-count.sh esegue semplicemente la trama
tramite gnuplot, come mostrato di seguito.
[immagine] 2-D Gnuplot Creato da seventh.cc Esempio..UNINDENT
Si può notare che gli elementi chiave (leggenda, titolo, posizionamento legenda, xlabel, ylabel,
e percorso per i dati) sono tutti posizionati sul grafico. Dato che c'erano due partite per il
percorso di configurazione fornito, vengono mostrate le due serie di dati:
· Packet Byte Count-0 corrisponde a /NodeList/0/$ns3::Ipv4L3Protocol/Tx
· Packet Byte Count-1 corrisponde a /NodeList/1/$ns3::Ipv4L3Protocol/Tx
GnuplotHelper ConfiguraTrama
Il GnuplotHelper's ConfiguraTrama() la funzione può essere utilizzata per configurare i grafici.
Ha il seguente prototipo:
void ConfigurePlot (const std::string &outputFileNameWithoutExtension,
const std::string &titolo,
const std::string &xLegend,
const std::string &yLegend,
const std::string &terminalType = ".png");
Ha i seguenti argomenti:
? ?
│Argomento │ Descrizione │
? ?
outputFileNameWithoutExtension │ Nome dei file relativi a gnuplot a │
│ scrivi senza estensione. ?
? ?
│title │ Traccia la stringa del titolo da usare per │
│ │ questa trama. ?
? ?
│xLegend │ La legenda per la x orizzontale │
│ asse. ?
? ?
│yLegend │ La legenda per la y verticale │
│ asse. ?
? ?
│terminalType │ Stringa di impostazione del tipo di terminale per │
│ uscita. Il terminale predefinito
│ il tipo è "png". ?
? ?
Il GnuplotHelper's ConfiguraTrama() la funzione configura i parametri relativi alla trama per questo
gnuplot helper in modo che crei un file di dati gnuplot separato da spazi chiamato
outputFileNameWithoutExtension + ".dat", un file di controllo gnuplot chiamato
outputFileNameWithoutExtension + ".plt" e uno script di shell per generare lo gnuplot denominato
outputNomeFileSenzaEstensione + ".sh".
Un esempio di come utilizzare questa funzione può essere visto nel settimo.cc codice sopra descritto
dove è stato utilizzato come segue:
plotHelper.ConfigurePlot ("settimo-pacchetto-byte-count",
"Conteggio byte pacchetto vs. tempo",
"Tempo (secondi)",
"Conteggio byte pacchetto",
"png");
GnuplotHelper PlotProbe
Il GnuplotHelper's Plotprobe() La funzione può essere utilizzata per tracciare i valori generati dalle sonde.
Ha il seguente prototipo:
void PlotProbe (const std::string &typeId,
const std::string &percorso,
const std::string &probeTraceSource,
const std::string &titolo,
enum GnuplotAggregator::KeyLocation keyLocation = GnuplotAggregator::KEY_INSIDE);
Ha i seguenti argomenti:
? ?
│Argomento │ Descrizione │
? ?
│typeId │ L'ID del tipo per la sonda │
│ creato da questo aiutante. ?
? ?
│percorso │ Percorso di configurazione per accedere alla traccia │
│ fonte. ?
? ?
│probeTraceSource │ La sorgente di traccia della sonda su │
│ accesso. ?
? ?
│titolo │ Il titolo da associare a │
│ │ questo set di dati │
? ?
│keyLocation │ La posizione della chiave nella │
│ trama. La posizione predefinita è │
│ dentro. ?
? ?
Il GnuplotHelper's Plotprobe() la funzione traccia un set di dati generato agganciando il NS-3
tracciare la sorgente con una sonda creata dall'helper e quindi tracciare i valori da
probeTraceSource. Il set di dati avrà il titolo fornito e sarà composto da
'newValue' ad ogni timestamp.
Se il percorso di configurazione ha più di una corrispondenza nel sistema perché è presente un carattere jolly, allora
verrà tracciato un set di dati per ogni partita. I titoli dei set di dati saranno suffissi con il
caratteri corrispondenti per ciascuno dei caratteri jolly nel percorso di configurazione, separati da spazi. Per
esempio, se il titolo del set di dati proposto è la stringa "byte" e sono presenti due caratteri jolly
nel percorso, saranno possibili titoli del set di dati come "bytes-0 0" o "byte-12 9" come
etichette per i set di dati che vengono tracciati.
Un esempio di come utilizzare questa funzione può essere visto nel settimo.cc codice sopra descritto
dove è stato utilizzato (con sostituzione di variabile) come segue:
plotHelper.PlotProbe ("ns3::Ipv4PacketProbe",
"/NodeList/*/$ns3::Ipv4L3Protocol/Tx",
"Output Byte",
"Conteggio byte pacchetto",
GnuplotAggregator::KEY_BELOW);
Altri Esempi
gnuplot Aiutante Esempio
Un esempio leggermente più semplice del settimo.cc esempio può essere trovato in
src/stats/examples/gnuplot-helper-example.cc. Il seguente gnuplot 2-D è stato creato utilizzando
l'esempio.
[immagine] Gnuplot 2-D Creato da gnuplot-helper-example.cc Esempio..UNINDENT
In questo esempio, c'è un oggetto Emettitore che incrementa il suo contatore secondo a
Processo di Poisson e quindi emette il valore del contatore come sorgente di traccia.
Ptr emettitore = CreaOggetto ();
Nomi::Aggiungi ("/Nomi/Emettitore", emettitore);
Si noti che poiché non ci sono caratteri jolly nel percorso utilizzato di seguito, solo 1 flusso di dati era
disegnato nella trama. Questo singolo flusso di dati nella trama è semplicemente etichettato "Conteggio emettitori",
senza suffissi aggiuntivi come si vedrebbe se ci fossero caratteri jolly nel percorso.
// Crea l'helper gnuplot.
GnuplotHelper plotHelper;
// Configura la trama.
plotHelper.ConfigurePlot ("gnuplot-helper-example",
"Conteggi emettitori rispetto al tempo",
"Tempo (secondi)",
"Conteggio emettitori",
"png");
// Traccia i valori generati dalla sonda. Il percorso che forniamo
// aiuta a disambiguare l'origine della traccia.
plotHelper.PlotProbe ("ns3::Uinteger32Probe",
"/Nomi/Emettitore/Contatore",
"Produzione",
"Conteggio emettitori",
GnuplotAggregator::KEY_INSIDE);
FileHelper
Il FileHelper è una classe di supporto utilizzata per inserire i valori dei dati in un file. L'obiettivo generale è
per fornire agli utenti la possibilità di creare rapidamente file di testo formattati dai dati esportati
in NS-3 fonti di traccia. Per impostazione predefinita, viene eseguita una quantità minima di trasformazione dei dati;
l'obiettivo è generare file con il minor numero di istruzioni di configurazione (predefinite) come
possibile.
FileHelper Panoramica
Il FileHelper creerà 1 o più file di testo alla fine della simulazione.
Il FileHelper può creare 4 diversi tipi di file di testo:
· Formattato
· Separati da spazi (impostazione predefinita)
· Separato da virgola
· Tab separato
I file formattati utilizzano stringhe di formato in stile C e la funzione sprintf() per stamparli
valori nel file in fase di scrittura.
Il seguente file di testo con 2 colonne di valori formattati denominati
settimo-pacchetto-byte-count-0.txt è stato creato utilizzando più nuovo codice che è stato aggiunto al
i NS-3 Codice dell'esempio tutorial. Vengono mostrate solo le prime 10 righe di questo file
qui per brevità.
Tempo (secondi) = 1.000e+00 Conteggio byte pacchetto = 40
Tempo (secondi) = 1.004e+00 Conteggio byte pacchetto = 40
Tempo (secondi) = 1.004e+00 Conteggio byte pacchetto = 576
Tempo (secondi) = 1.009e+00 Conteggio byte pacchetto = 576
Tempo (secondi) = 1.009e+00 Conteggio byte pacchetto = 576
Tempo (secondi) = 1.015e+00 Conteggio byte pacchetto = 512
Tempo (secondi) = 1.017e+00 Conteggio byte pacchetto = 576
Tempo (secondi) = 1.017e+00 Conteggio byte pacchetto = 544
Tempo (secondi) = 1.025e+00 Conteggio byte pacchetto = 576
Tempo (secondi) = 1.025e+00 Conteggio byte pacchetto = 544
...
Il seguente file di testo diverso con 2 colonne di valori formattati denominati
settimo-pacchetto-byte-count-1.txt è stato anche creato utilizzando lo stesso nuovo codice che è stato aggiunto a
l'originale NS-3 Codice dell'esempio tutorial. Vengono mostrate solo le prime 10 righe di questo file
qui per brevità.
Tempo (secondi) = 1.002e+00 Conteggio byte pacchetto = 40
Tempo (secondi) = 1.007e+00 Conteggio byte pacchetto = 40
Tempo (secondi) = 1.013e+00 Conteggio byte pacchetto = 40
Tempo (secondi) = 1.020e+00 Conteggio byte pacchetto = 40
Tempo (secondi) = 1.028e+00 Conteggio byte pacchetto = 40
Tempo (secondi) = 1.036e+00 Conteggio byte pacchetto = 40
Tempo (secondi) = 1.045e+00 Conteggio byte pacchetto = 40
Tempo (secondi) = 1.053e+00 Conteggio byte pacchetto = 40
Tempo (secondi) = 1.061e+00 Conteggio byte pacchetto = 40
Tempo (secondi) = 1.069e+00 Conteggio byte pacchetto = 40
...
Di seguito è riportato il nuovo codice che è stato aggiunto per produrre i due file di testo. Maggiori dettagli su
questa API sarà trattata in una sezione successiva.
Nota che poiché c'erano 2 corrispondenze per il carattere jolly nel percorso, 2 file di testo separati
sono stati creati. Il primo file di testo, denominato "seventh-packet-byte-count-0.txt",
corrisponde alla corrispondenza del carattere jolly con "*" sostituito da "0". Il secondo file di testo,
che si chiama "seventh-packet-byte-count-1.txt", corrisponde alla corrispondenza con caratteri jolly con
il "*" sostituito con "1". Si noti inoltre che la funzione chiamata a ScriviSonda() darà un
messaggio di errore se non ci sono corrispondenze per un percorso che contiene caratteri jolly.
// Crea l'helper file.
FileHelper fileHelper;
// Configura il file da scrivere.
fileHelper.ConfigureFile ("settimo-pacchetto-byte-count",
FileAggregator::FORMATTATO);
// Imposta le etichette per questo file di output formattato.
fileHelper.Set2dFormat ("Tempo (secondi) = %.3e\tConteggio byte pacchetto = %.0f");
// Scrive i valori generati dalla sonda.
fileHelper.WriteProbe ("ns3::Ipv4PacketProbe",
"/NodeList/*/$ns3::Ipv4L3Protocol/Tx",
"Output Byte");
FileHelper ConfiguraFile
Il FileHelper's ConfiguraFile() La funzione può essere utilizzata per configurare i file di testo.
Ha il seguente prototipo:
void ConfigureFile (const std::string &outputFileNameWithoutExtension,
enum FileAggregator::FileType fileType = FileAggregator::SPACE_SEPARATED);
Ha i seguenti argomenti:
? ?
│Argomento │ Descrizione │
? ?
outputFileNameWithoutExtension │ Nome del file di output da scrivere │
│ │ senza estensione. ?
? ?
fileType │ Tipo di file da scrivere. il
│ │ il tipo di file predefinito è lo spazio │
│ separato. ?
? ?
Il FileHelper's ConfiguraFile() la funzione configura i parametri relativi ai file di testo per il
file helper in modo che crei un file chiamato outputFileNameWithoutExtension plus
possibili informazioni extra dalle corrispondenze con caratteri jolly più ".txt" con valori stampati come
specificato da fileType. Il tipo di file predefinito è separato da spazi.
Un esempio di come utilizzare questa funzione può essere visto nel settimo.cc codice sopra descritto
dove è stato utilizzato come segue:
fileHelper.ConfigureFile ("settimo-pacchetto-byte-count",
FileAggregator::FORMATTATO);
FileHelper Scrivi Sonda
Il FileHelper's ScriviSonda() la funzione può essere utilizzata per scrivere i valori generati dalle sonde su
file di testo.
Ha il seguente prototipo:
void WriteProbe (const std::string &typeId,
const std::string &percorso,
const std::string &probeTraceSource);
Ha i seguenti argomenti:
? ?
│Argomento │ Descrizione │
? ?
typeId │ L'ID del tipo per la sonda da
│ creato. ?
? ?
│percorso │ Percorso di configurazione per accedere alla traccia │
│ fonte. ?
? ?
│probeTraceSource │ La sorgente di traccia della sonda su │
│ accesso. ?
? ?
Il FileHelper's ScriviSonda() la funzione crea file di testo di output generati agganciando il
ns-3 trace source con una sonda creata dall'helper e quindi scrivendo i valori da
probeTraceSource. I nomi dei file di output avranno il testo memorizzato nella variabile membro
m_outputFileNameWithoutExtension più ".txt", e consisterà in 'newValue' ad ogni
marca temporale.
Se il percorso di configurazione ha più di una corrispondenza nel sistema perché è presente un carattere jolly, allora
verrà creato un file di output per ogni corrispondenza. I nomi dei file di output conterranno il
testo in m_outputFileNameWithoutExtension più i caratteri corrispondenti per ciascuno dei
caratteri jolly nel percorso di configurazione, separati da trattini, più ".txt". Ad esempio, se il valore
in m_outputFileNameWithoutExtension c'è la stringa "packet-byte-count", e ce ne sono due
caratteri jolly nel percorso, quindi emettere nomi di file come "packet-byte-count-0-0.txt" o
"packet-byte-count-12-9.txt" sarà possibile come nomi per i file che verranno creati.
Un esempio di come utilizzare questa funzione può essere visto nel settimo.cc codice sopra descritto
dove è stato utilizzato come segue:
fileHelper.WriteProbe ("ns3::Ipv4PacketProbe",
"/NodeList/*/$ns3::Ipv4L3Protocol/Tx",
"Output Byte");
Altri Esempi
Compila il Aiutante Esempio
Un esempio leggermente più semplice del settimo.cc esempio può essere trovato in
src/stats/examples/file-helper-example.cc. Questo esempio utilizza solo FileHelper.
Il seguente file di testo con 2 colonne di valori formattati denominati file-helper-esempio.txt
è stato creato utilizzando l'esempio. Qui vengono mostrate solo le prime 10 righe di questo file per
brevità.
Tempo (secondi) = 0.203 Conteggio = 1
Tempo (secondi) = 0.702 Conteggio = 2
Tempo (secondi) = 1.404 Conteggio = 3
Tempo (secondi) = 2.368 Conteggio = 4
Tempo (secondi) = 3.364 Conteggio = 5
Tempo (secondi) = 3.579 Conteggio = 6
Tempo (secondi) = 5.873 Conteggio = 7
Tempo (secondi) = 6.410 Conteggio = 8
Tempo (secondi) = 6.472 Conteggio = 9
...
In questo esempio, c'è un oggetto Emettitore che incrementa il suo contatore secondo a
Processo di Poisson e quindi emette il valore del contatore come sorgente di traccia.
Ptr emettitore = CreaOggetto ();
Nomi::Aggiungi ("/Nomi/Emettitore", emettitore);
Si noti che poiché non sono presenti caratteri jolly nel percorso utilizzato di seguito, è stato utilizzato solo 1 file di testo
creato. Questo singolo file di testo si chiama semplicemente "file-helper-example.txt", senza extra
suffissi come vedresti se ci fossero caratteri jolly nel percorso.
// Crea l'helper file.
FileHelper fileHelper;
// Configura il file da scrivere.
fileHelper.ConfigureFile ("esempio-file-helper",
FileAggregator::FORMATTATO);
// Imposta le etichette per questo file di output formattato.
fileHelper.Set2dFormat ("Tempo (secondi) = %.3e\tCount = %.0f");
// Scrive i valori generati dalla sonda. Il percorso che noi
//fornire aiuta a disambiguare l'origine della traccia.
fileHelper.WriteProbe ("ns3::Uinteger32Probe",
"/Nomi/Emettitore/Contatore",
"Produzione");
Obbiettivo e Limiti
Attualmente, solo queste Probe sono state implementate e collegate a GnuplotHelper e
al FileHelper:
· Sonda Booleana
· Doppia sonda
· Uintero8Sonda
· Uintero16Sonda
· Uintero32Sonda
· Sonda temporale
·PacketProbe
· ApplicationPacketProbe
· Ipv4PacketProbe
Queste sonde, quindi, sono gli unici TypeId disponibili per essere utilizzati in Plotprobe() e
ScriviSonda().
Nelle prossime sezioni, tratteremo ciascuno dei tipi di oggetti fondamentali (Probe, Collector,
e Aggregator) in modo più dettagliato e mostra come possono essere collegati tra loro utilizzando
API di livello inferiore.
Sonde
Questa sezione descrive in dettaglio le funzionalità fornite dalla classe Probe ad un NS-3
simulazione e fornisce esempi su come codificarli in un programma. Questa sezione è pensata per
utenti interessati a sviluppare simulazioni con il NS-3 strumenti e utilizzo dei Dati
Collection Framework, di cui fa parte la classe Probe, per generare output di dati con
i risultati della loro simulazione.
Campione Panoramica
Si suppone che un oggetto Probe sia connesso a una variabile della simulazione i cui valori
durante l'esperimento sono rilevanti per l'utente. La sonda registrerà ciò che era
valori assunti dalla variabile durante la simulazione e passare tali dati a un altro
membro del Data Collection Framework. Anche se non rientra nell'ambito di questa sezione
discutere cosa succede dopo che la sonda produce il suo output, è sufficiente dire che, da
alla fine della simulazione, l'utente avrà informazioni dettagliate su quali valori erano
memorizzato all'interno della variabile da sondare durante la simulazione.
Tipicamente, una Sonda è collegata ad un NS-3 fonte di traccia. In questo modo, ogni volta che
trace source esporta un nuovo valore, la sonda consuma il valore (e lo esporta a valle
a un altro oggetto tramite la propria sorgente di traccia).
Il Probe può essere pensato come una sorta di filtro sulle sorgenti di traccia. Le ragioni principali per
eventualmente agganciarsi a un Probe piuttosto che direttamente a una sorgente di traccia sono i seguenti:
· Le sonde possono essere attivate e disattivate dinamicamente durante la simulazione con chiamate a Abilitare()
e Disattivare(). Ad esempio, l'emissione dei dati può essere disattivata durante il
fase di riscaldamento della simulazione.
· Le sonde possono eseguire operazioni sui dati per estrarre valori da più complicati
strutture; per esempio, emettendo il valore della dimensione del pacchetto da un ns3::Packet ricevuto.
· Le sonde registrano un nome nello spazio dei nomi ns3::Config (usando Nomi::Aggiungi ()) in modo che altro
gli oggetti possono fare riferimento ad essi.
· Le sonde forniscono un metodo statico che consente di manipolare una sonda per nome, come
cosa viene fatto in ns2measure [Cic06]
Stat::put ("my_metric", ID, campione);
L'equivalente ns-3 del codice ns2measure sopra è, ad es
DoubleProbe::SetValueByPath ("/percorso/a/probe", esempio);
coerenti
Nota che un oggetto della classe base Probe non può essere creato perché è una base astratta
class, cioè ha funzioni virtuali pure che non sono state implementate. Un oggetto di
il tipo DoubleProbe, che è una sottoclasse della classe Probe, verrà creato qui per mostrare
cosa bisogna fare.
Si dichiara un DoubleProbe in memoria dinamica usando la classe puntatore intelligente (Ptr ). a
creare un DoubleProbe in memoria dinamica con puntatori intelligenti, basta chiamare il
NS-3 metodo CreaOggetto():
Ptr myprobe = CreaOggetto ();
La dichiarazione precedente crea DoubleProbes utilizzando i valori predefiniti per i suoi attributi.
Ci sono quattro attributi nella classe DoubleProbe; due nell'oggetto della classe base
DataCollectionObject e due nella classe base Probe:
· "Nome" (DataCollectionObject), un valore String
· "Enabled" (DataCollectionObject), un valore booleano
· "Start" (Sonda), un TimeValue
· "Stop" (Sonda), un TimeValue
È possibile impostare tali attributi alla creazione dell'oggetto utilizzando il seguente metodo:
Ptr myprobe = CreateObjectWithAttributes (
"Nome", StringValue ("myprobe"),
"Abilitato", BooleanValue (falso),
"Start", TimeValue (secondi (100.0)),
"Stop", TimeValue (secondi (1000.0)));
Start e Stop sono variabili di Tempo che determinano l'intervallo di azione della Sonda. Il
La sonda emetterà dati solo se l'ora corrente della simulazione è all'interno di quella
intervallo. Il valore temporale speciale di 0 secondi per Stop disabiliterà questo attributo (es
tenere accesa la sonda per tutta la simulazione). Abilitato è un flag che accende la sonda o
off e deve essere impostato su true affinché la sonda possa esportare i dati. Il Nome è il nome dell'oggetto
nel quadro DCF.
Importazione e esportazione dati
NS-3 le sorgenti di traccia sono fortemente tipizzate, quindi i meccanismi per agganciare le sonde a una traccia
sorgente e per l'esportazione dei dati appartengono alle sue sottoclassi. Ad esempio, l'impostazione predefinita
distribuzione di NS-3 fornisce una classe DoubleProbe progettata per agganciarsi a una traccia
sorgente che esporta un valore doppio. Successivamente descriveremo in dettaglio il funzionamento di DoubleProbe e
poi discutere come altre classi Probe possono essere definite dall'utente.
Doppia sonda Panoramica
Il DoubleProbe si connette a un doppio valore NS-3 trace source e esporta esso stesso a
diverso valore doppio NS-3 fonte di traccia.
Il codice seguente, tratto da src/stats/examples/double-probe-example.cc, mostra la base
operazioni di inserimento del DoubleProbe in una simulazione, dove sta sondando un contatore
esportato da un oggetto emettitore (classe Emettitore).
Ptr emettitore = CreaOggetto ();
Nomi::Aggiungi ("/Nomi/Emettitore", emettitore);
...
Ptr probe1 = CreaOggetto ();
// Collega la sonda al contatore dell'emettitore
bool connesso = probe1->ConnectByObject ("Contatore", emettitore);
Il codice seguente sta sondando lo stesso contatore esportato dallo stesso oggetto emettitore. Questo
DoubleProbe, tuttavia, utilizza un percorso nello spazio dei nomi di configurazione per rendere il
connessione. Nota che l'emettitore si è registrato nello spazio dei nomi di configurazione dopo
è stato creato; in caso contrario, ConnectByPath non funzionerebbe.
Ptr probe2 = CreaOggetto ();
// Nota, qui non viene controllato alcun valore di ritorno
probe2->ConnectByPath ("/Nomi/Emettitore/Contatore");
Il prossimo DoubleProbe mostrato sotto avrà il suo valore impostato usando il suo percorso in
lo spazio dei nomi di configurazione. Nota che questa volta DoubleProbe si è registrato nel
spazio dei nomi di configurazione dopo che è stato creato.
Ptr probe3 = CreaOggetto ();
probe3->SetName ("StaticallyAccessedProbe");
// Dobbiamo aggiungerlo al database di configurazione
Names::Add ("/Names/Probes", probe3->GetName (), probe3);
La funzione Count() dell'emettitore è ora in grado di impostare il valore per questo DoubleProbe come
segue:
nulla
Emettitore:: Conteggio (vuoto)
{
...
m_contatore += 1.0;
DoubleProbe::SetValueByPath ("/Names/StaticallyAccessedProbe", m_counter);
...
}
L'esempio sopra mostra come il codice che chiama la sonda non debba avere un'esplicita
riferimento al Probe, ma può indirizzare l'impostazione del valore tramite lo spazio dei nomi Config.
Questo è simile nella funzionalità al Statistica::Messa metodo introdotto da ns2measure paper
[Cic06] e consente agli utenti di inserire temporaneamente istruzioni Probe come printf dichiarazioni
all'interno dell'esistente NS-3 Modelli. Nota che per poter utilizzare DoubleProbe in questo
esempio come questo, erano necessarie 2 cose:
1. il file di intestazione del modulo statistiche è stato incluso nel file .cc di esempio
2. l'esempio è stato reso dipendente dal modulo stats nel suo file wscript.
Analoghe operazioni devono essere fatte per aggiungere altre Probe in altri punti del NS-3
base di codice.
I valori per DoubleProbe possono essere impostati anche utilizzando la funzione DoubleProbe::SetValue(),
mentre i valori per il DoubleProbe possono essere ottenuti utilizzando la funzione
DoubleProbe::GetValue().
Il DoubleProbe esporta i valori double nella sua origine di traccia "Output"; un oggetto a valle
può agganciare un trace sink (NotifyViaProbe) a questo come segue:
connesso = probe1->TraceConnect ("Output", probe1->GetName (), MakeCallback (&NotifyViaProbe));
Altri sonde
Oltre al DoubleProbe, sono disponibili anche le seguenti Sonde:
· Uintger8Probe si connette a un NS-3 sorgente di traccia che esporta un uint8_t.
· Uintger16Probe si connette a un NS-3 sorgente di traccia che esporta un uint16_t.
· Uintger32Probe si connette a un NS-3 sorgente di traccia che esporta un uint32_t.
· PacketProbe si connette a un NS-3 sorgente di traccia che esporta un pacchetto.
· ApplicationPacketProbe si connette a un NS-3 trace source esportando un pacchetto e un socket
indirizzo.
· Ipv4PacketProbe si connette a un NS-3 trace source esportando un pacchetto, un oggetto IPv4 e
un'interfaccia.
Creazione nuovi Campione Tipi di
Per creare un nuovo tipo di sonda, è necessario eseguire i seguenti passaggi:
· Assicurati che la tua nuova classe Probe sia derivata dalla classe base Probe.
· Assicurati che le funzioni virtuali pure che la tua nuova classe Probe eredita dal
La classe base della sonda è implementata.
· Trova una classe Probe esistente che utilizza una sorgente di traccia che è più vicina nel tipo al
tipo di sorgente di traccia che utilizzerà la sonda.
· Copiare il file di intestazione della classe Probe esistente (.h) e il file di implementazione (.cc) su due
nuovi file con nomi corrispondenti alla tua nuova sonda.
· Sostituisci i tipi, gli argomenti e le variabili nei file copiati con l'appropriato
digita per la tua sonda.
· Apportare le modifiche necessarie per compilare il codice e farlo comportare come faresti tu
piace.
Esempi
Due esempi saranno discussi in dettaglio qui:
· Esempio di doppia sonda
· Esempio di grafico del pacchetto IPv4
Doppio Campione Esempio
L'esempio della doppia sonda è stato discusso in precedenza. Il programma di esempio può essere trovato
in src/stats/examples/double-probe-example.cc. Per riassumere ciò che accade in questo programma,
c'è un emettitore che esporta un contatore che incrementa secondo un processo di Poisson.
In particolare vengono mostrate due modalità di emissione dei dati:
1. tramite una variabile tracciata agganciata ad una Probe:
TracedValue m_contatore; // normalmente questo sarebbe di tipo intero
2. tramite un contatore il cui valore è inviato a una seconda sonda, referenziata dal suo nome in
il sistema di configurazione:
nulla
Emettitore:: Conteggio (vuoto)
{
NS_LOG_FUNCTION (questo);
NS_LOG_DEBUG ("Conteggio a " << Simulator::Now ().GetSeconds ());
m_contatore += 1.0;
DoubleProbe::SetValueByPath ("/Names/StaticallyAccessedProbe", m_counter);
Simulator::Schedule (Secondi (m_var->GetValue ()), &Emitter::Count, this);
}
Esaminiamo più attentamente la sonda. Le sonde possono ricevere i loro valori in multipli
modi:
1. dalla sonda accedendo direttamente alla sorgente di traccia e collegando un sink di traccia ad essa
2. dal Probe che accede all'origine della traccia tramite lo spazio dei nomi di configurazione e si connette a
traccia affondare ad esso
3. dal codice chiamante chiamando esplicitamente i Probe's Valore impostato() metodo
4. dal codice chiamante chiamando esplicitamente ImpostaValorePerPercorso
("/percorso/attraverso/Config/spazio dei nomi", ...)
Le prime due tecniche dovrebbero essere le più comuni. Anche nell'esempio, il
viene mostrato l'aggancio di una normale funzione di callback, come avviene tipicamente in NS-3. Questo
la funzione di callback non è associata a un oggetto Probe. Chiameremo questo caso 0) di seguito.
// Questa è una funzione per testare l'aggancio di una funzione non elaborata alla sorgente di traccia
nulla
NotifyViaTraceSource (std::string contesto, double oldVal, double newVal)
{
NS_LOG_DEBUG ("context: " << context << " old " << oldVal << " new " << newVal);
}
Innanzitutto, l'emettitore deve essere configurato:
Ptr emettitore = CreaOggetto ();
Nomi::Aggiungi ("/Nomi/Emettitore", emettitore);
// L'oggetto Emettitore non è associato a un nodo ns-3, quindi
// non si avvia automaticamente, quindi dobbiamo farlo da soli
Simulatore::Schedule (secondi (0.0), &Emitter::Start, emettitore);
I vari DoubleProbe interagiscono con l'emettitore nell'esempio come mostrato di seguito.
Caso 0):
// Quanto segue mostra la funzionalità tipica senza sonda
// (connette una funzione sink a una sorgente di traccia)
//
connesso = emettitore->TraceConnect ("Contatore", "contesto campione", MakeCallback (&NotifyViaTraceSource));
NS_ASSERT_MSG (connesso, "Sorgente di traccia non connesso");
caso 1):
//
// Probe1 verrà agganciata direttamente all'oggetto sorgente di traccia dell'emettitore
//
// probe1 verrà agganciato alla sorgente di traccia dell'emettitore
Ptr probe1 = CreaOggetto ();
// il nome della sonda può fungere da contesto nel tracciamento
probe1->SetName ("ObjectProbe");
// Collega la sonda al contatore dell'emettitore
connesso = probe1->ConnectByObject ("Contatore", emettitore);
NS_ASSERT_MSG (connesso, "Trace source non connesso a probe1");
caso 2):
//
// Probe2 verrà agganciata all'oggetto sorgente di traccia dell'emettitore da
// accedendovi tramite il nome del percorso nel database di configurazione
//
// Crea un'altra sonda simile; questo si collegherà tramite un percorso di configurazione
Ptr probe2 = CreaOggetto ();
probe2->SetName ("PathProbe");
// Nota, qui non viene controllato alcun valore di ritorno
probe2->ConnectByPath ("/Nomi/Emettitore/Contatore");
caso 4) (il caso 3 non è mostrato in questo esempio):
//
// La sonda 3 verrà chiamata dall'emettitore direttamente tramite il
// metodo statico SetValueByPath().
//
Ptr probe3 = CreaOggetto ();
probe3->SetName ("StaticallyAccessedProbe");
// Dobbiamo aggiungerlo al database di configurazione
Names::Add ("/Names/Probes", probe3->GetName (), probe3);
E infine, l'esempio mostra come le sonde possono essere agganciate per generare output:
// La sonda stessa dovrebbe generare un output. Il contesto che forniamo
// a questa sonda (in questo caso, il nome della sonda) aiuterà a disambiguare
// la fonte della traccia
connesso = probe3->TraceConnect ("Uscita",
"/Names/Probes/StaticallyAccessedProbe/Output",
MakeCallback (&NotifyViaProbe));
NS_ASSERT_MSG (connesso, "Trace source not .. connected to probe3 Output");
Il seguente callback è agganciato al Probe in questo esempio a scopo illustrativo;
normalmente, la sonda sarebbe agganciata a un oggetto Collector.
// Questa è una funzione per testare l'aggancio all'uscita della sonda
nulla
NotifyViaProbe (std::string contesto, double oldVal, double newVal)
{
NS_LOG_DEBUG ("context: " << context << " old " << oldVal << " new " << newVal);
}
IPv4 Pacchetto Terreno Esempio
L'esempio del grafico del pacchetto IPv4 si basa sul quinto esempio.cc del NS-3 Tutorial. Esso
possono essere trovati nell' src/stats/examples/ipv4-packet-plot-example.cc.
nodo 0 nodo 1
+----------------+ +----------------+
| ns-3TCP | | ns-3TCP |
+----------------+ +----------------+
| 10.1.1.1| | 10.1.1.2|
+----------------+ +----------------+
| punto-punto | | punto-punto |
+----------------+ +----------------+
| |
+----------------------+
Esamineremo solo la sonda, poiché illustra che le sonde possono anche decomprimere i valori da
strutture (in questo caso, pacchetti) e riportano quei valori come output della sorgente di traccia, piuttosto
piuttosto che passare attraverso lo stesso tipo di dati.
Ci sono altri aspetti di questo esempio che verranno spiegati più avanti nella documentazione.
I due tipi di dati che vengono esportati sono il pacchetto stesso (Uscita) e un conteggio di
numero di byte nel pacchetto (Byte di output).
ID tipo
Ipv4PacketProbe::GetTypeId ()
{
static TypeId tid = TypeId ("ns3::Ipv4PacketProbe")
.SetParent ()
.Aggiungi costruttore ()
.AddTraceSource ("Uscita",
"Il pacchetto più il suo oggetto IPv4 e l'interfaccia che servono come output per questa sonda",
MakeTraceSourceAccessor (&Ipv4PacketProbe::m_output))
.AddTraceSource ("OutputBytes",
"Il numero di byte nel pacchetto",
MakeTraceSourceAccessor (&Ipv4PacketProbe::m_outputBytes))
;
ritorno a mare;
}
Quando il sink di traccia della sonda riceve un pacchetto, se la sonda è abilitata, verrà emesso
il pacchetto sul suo Uscita trace source, ma produrrà anche il numero di byte sul
Byte di output fonte di traccia.
nulla
Ipv4PacketProbe::TraceSink (Parte) pacchetto, Ptr ipv4, interfaccia uint4_t)
{
NS_LOG_FUNCTION (questo << pacchetto << ipv4 << interfaccia);
if (è abilitato ())
{
m_packet = pacchetto;
m_ipv4 = ipv4;
m_interface = interfaccia;
m_output (pacchetto, ipv4, interfaccia);
uint32_t packetSizeNew = pacchetto->GetSize ();
m_outputBytes (m_packetSizeOld, packetSizeNew);
m_packetSizeOld = pacchettoSizeNew;
}
}
Bibliografia
[Cic06]
Claudio Cicconetti, Enzo Mingozzi, Giovanni Stea, "Un quadro integrato per
Consentire un'efficace raccolta dati e analisi statistica con ns2, Workshop on
ns-2 (WNS2), Pisa, Italia, ottobre 2006.
Collezionisti
Questa sezione è un segnaposto per dettagliare le funzionalità fornite dal Collector
classe ad an NS-3 simulazione e fornisce esempi su come codificarli in un programma.
Nota: A partire dal ns-3.18, i Collector sono ancora in fase di sviluppo e non sono ancora forniti come parte
del quadro.
Aggregatori
Questa sezione descrive in dettaglio le funzionalità fornite dalla classe Aggregator ad un NS-3
simulazione. Questa sezione è pensata per gli utenti interessati a sviluppare simulazioni con il
NS-3 strumenti e utilizzando il Data Collection Framework, di cui la classe Aggregator è una
parte, per generare dati in uscita con i risultati della loro simulazione.
Aggregator Panoramica
Si suppone che un oggetto Aggregator sia agganciato a una o più sorgenti di traccia per
ricevere input. Gli aggregatori sono il punto finale dei dati raccolti dalla rete di
Sonde e collettori durante la simulazione. È compito dell'aggregatore prenderli
valori e trasformarli nel loro formato di output finale come file di testo normale,
file di fogli di calcolo, grafici o database.
In genere, un aggregatore è connesso a uno o più Collector. In questo modo, ogni volta che
le sorgenti di traccia dei Collector esportano nuovi valori, l'Aggregator può elaborare il valore così
che può essere utilizzato nel formato di output finale in cui i valori dei dati risiederanno dopo il
simulazione.
Nota quanto segue sugli aggregatori:
· Gli aggregatori possono essere attivati e disattivati dinamicamente durante la simulazione con chiamate a
Abilitare() e Disattivare(). Ad esempio, l'aggregazione dei dati può essere disattivata durante
la fase di riscaldamento della simulazione, il che significa che quei valori non saranno inclusi nella finale
mezzo di uscita.
· Gli aggregatori ricevono i dati dai servizi di raccolta tramite callback. Quando un Collector è associato
a un aggregatore, viene effettuata una chiamata a TraceConnect per stabilire la traccia dell'aggregatore
sink come callback.
Ad oggi sono stati implementati due Aggregatori:
· Gnuplot Aggregatore
· FileAgregator
GnuplotAggregatore
GnuplotAggregator produce file di output usati per creare gnuplot.
Il GnuplotAggregator creerà 3 diversi file alla fine della simulazione:
· Un file di dati gnuplot separato da spazi
· Un file di controllo gnuplot
· Uno script di shell per generare gnuplot
coerenti
Qui verrà creato un oggetto di tipo GnuplotAggregator per mostrare cosa deve essere fatto.
Si dichiara un GnuplotAggregator nella memoria dinamica usando la classe puntatore intelligente
(Ptr ). Per creare un GnuplotAggregator in memoria dinamica con puntatori intelligenti, basta
deve chiamare il NS-3 metodo CreaOggetto(). Il seguente codice da
src/stats/examples/gnuplot-aggregator-example.cc mostra come farlo:
string fileNameWithoutExtension = "gnuplot-aggregator";
// Crea un aggregatore.
Ptr aggregatore =
CreaOggetto (nomefileSenzaEstensione);
Il primo argomento per il costruttore, fileNameWithoutExtension, è il nome del
file relativi a gnuplot da scrivere senza estensione. Questo GnuplotAggregator creerà un
file di dati gnuplot separato da spazi chiamato "gnuplot-aggregator.dat", un file di controllo di gnuplot
denominato "gnuplot-aggregator.plt", e uno script di shell per generare lo gnuplot denominato +
"gnuplot-aggregator.sh".
Lo gnuplot che viene creato può avere la sua chiave in 4 posizioni diverse:
· Nessuna chiave
· Chiave all'interno della trama (impostazione predefinita)
· Tasto sopra la trama
· Chiave sotto la trama
I seguenti valori enum della posizione della chiave gnuplot possono specificare la posizione della chiave:
enum posizione chiave {
NO_CHIAVE,
CHIAVE_INTERNA,
CHIAVE_SOPRA,
CHIAVE_SOTTO
};
Se si desiderava avere la chiave in basso anziché la posizione predefinita all'interno, allora
potresti fare quanto segue.
aggregatore->SetKeyLocation(GnuplotAggregator::KEY_BELOW);
Esempi
Un esempio sarà discusso in dettaglio qui:
· Esempio di aggregatore Gnuplot
gnuplot Aggregator Esempio
Un esempio che esercita GnuplotAggregator può essere trovato in
src/stats/examples/gnuplot-aggregator-example.cc.
Il seguente gnuplot 2-D è stato creato utilizzando l'esempio.
[immagine] Gnuplot 2-D Creato da gnuplot-aggregator-example.cc Esempio..UNINDENT
Questo codice dell'esempio mostra come costruire GnuplotAggregator come discusso
sopra.
void Create2dPlot()
{
usando lo spazio dei nomi std;
string fileNameWithoutExtension = "gnuplot-aggregator";
string plotTitle = "Gnuplot Aggregator Plot";
string plotXAxisHeading = "Tempo (secondi)";
string plotYAxisHeading = "Valori doppi";
string plotDatasetLabel = "Valori dati";
string datasetContext = "Set di dati/Contesto/Stringa";
// Crea un aggregatore.
Ptr aggregatore =
CreaOggetto (nomefileSenzaEstensione);
Sono impostati vari attributi di GnuplotAggregator, incluso il set di dati 2-D che sarà
tracciato.
// Imposta le proprietà dell'aggregatore.
aggregatore->SetTerminal ("png");
aggregatore->SetTitle (plotTitle);
aggregatore->SetLegend (plotXAxisHeading, plotYAxisHeading);
// Aggiunge un set di dati all'aggregatore.
aggregatore->Add2dDataset (datasetContext, plotDatasetLabel);
// l'aggregatore deve essere acceso
aggregatore->Abilita ();
Successivamente, vengono calcolati i valori 2-D e ciascuno di essi viene scritto individualmente nel
GnuplotAggregator utilizzando il Scrivi2d() funzione.
doppio tempo;
doppio valore;
// Crea il set di dati 2D.
per (tempo = -5.0; tempo <= +5.0; tempo += 1.0)
{
// Calcola la curva 2D
//
// 2
// valore = tempo.
//
valore = tempo * tempo;
// Aggiungi questo punto alla trama.
aggregatore->Write2d (datasetContext, ora, valore);
}
// Disabilita la registrazione dei dati per l'aggregatore.
aggregatore->Disabilita ();
}
FileAggregatore
Il FileAggregator invia i valori che riceve a un file.
Il FileAggregator può creare 4 diversi tipi di file:
· Formattato
· Separati da spazi (impostazione predefinita)
· Separato da virgola
· Tab separato
I file formattati utilizzano stringhe di formato in stile C e la funzione sprintf() per stamparli
valori nel file in fase di scrittura.
coerenti
Un oggetto di tipo FileAggregator verrà creato qui per mostrare cosa deve essere fatto.
Si dichiara un FileAggregator nella memoria dinamica utilizzando la classe puntatore intelligente (Ptr ).
Per creare un FileAggregator in memoria dinamica con puntatori intelligenti, basta chiamare
, il NS-3 metodo CreateObject. Il seguente codice da
src/stats/examples/file-aggregator-example.cc mostra come farlo:
string fileName = "file-aggregator-formatted-values.txt";
// Crea un aggregatore che avrà valori formattati.
pt aggregatore =
Crea oggetto (NomeFile, AggregatoreFile::FORMATTATO);
Il primo argomento per il costruttore, filename, è il nome del file da scrivere; il
il secondo argomento, fileType, è il tipo di file da scrivere. Questo FileAggregator creerà a
file denominato "file-aggregator-formatted-values.txt" con i suoi valori stampati come specificato da
fileType, ovvero formattato in questo caso.
Sono consentiti i seguenti valori di enum del tipo di file:
enum TipoFile {
FORMATTATO,
SPAZIO_SEPARATO,
SEPARATO DA VIRGOLA,
TAB_SEPARATI
};
Esempi
Un esempio sarà discusso in dettaglio qui:
· Esempio di aggregatore di file
Compila il Aggregator Esempio
Un esempio che esercita il FileAggregator può essere trovato in
src/stats/examples/file-aggregator-example.cc.
Il seguente file di testo con 2 colonne di valori separati da virgole è stato creato utilizzando il file
esempio.
all'5,25 ottobre
all'4,16 ottobre
all'3,9 ottobre
all'2,4 ottobre
all'1,1 ottobre
0,0
1,1
2,4
3,9
4,16
5,25
Questo codice dell'esempio mostra come costruire FileAggregator come è stato discusso
sopra.
void CreateCommaSeparatedFile ()
{
usando lo spazio dei nomi std;
string fileName = "file-aggregator-comma-separated.txt";
string datasetContext = "Set di dati/Contesto/Stringa";
// Crea un aggregatore.
pt aggregatore =
Crea oggetto (nomefile, aggregatore file::COMMA_SEPARATED);
Gli attributi di FileAggregator sono impostati.
// l'aggregatore deve essere acceso
aggregatore->Abilita ();
Successivamente, vengono calcolati i valori 2-D e ciascuno di essi viene scritto individualmente nel
FileAggregator usando il Scrivi2d() funzione.
doppio tempo;
doppio valore;
// Crea il set di dati 2D.
per (tempo = -5.0; tempo <= +5.0; tempo += 1.0)
{
// Calcola la curva 2D
//
// 2
// valore = tempo.
//
valore = tempo * tempo;
// Aggiungi questo punto alla trama.
aggregatore->Write2d (datasetContext, ora, valore);
}
// Disabilita la registrazione dei dati per l'aggregatore.
aggregatore->Disabilita ();
}
Anche il seguente file di testo con 2 colonne di valori formattati è stato creato utilizzando il file
esempio.
Tempo = -5.000e+00 Valore = 25
Tempo = -4.000e+00 Valore = 16
Tempo = -3.000e+00 Valore = 9
Tempo = -2.000e+00 Valore = 4
Tempo = -1.000e+00 Valore = 1
Tempo = 0.000e+00 Valore = 0
Tempo = 1.000e+00 Valore = 1
Tempo = 2.000e+00 Valore = 4
Tempo = 3.000e+00 Valore = 9
Tempo = 4.000e+00 Valore = 16
Tempo = 5.000e+00 Valore = 25
Questo codice dell'esempio mostra come costruire FileAggregator come è stato discusso
sopra.
void CreateFormattedFile ()
{
usando lo spazio dei nomi std;
string fileName = "file-aggregator-formatted-values.txt";
string datasetContext = "Set di dati/Contesto/Stringa";
// Crea un aggregatore che avrà valori formattati.
pt aggregatore =
Crea oggetto (NomeFile, AggregatoreFile::FORMATTATO);
Gli attributi FileAggregator sono impostati, inclusa la stringa di formato C da utilizzare.
// Imposta il formato per i valori.
aggregatore->Set2dFormat ("Ora = %.3e\tValore = %.0f");
// l'aggregatore deve essere acceso
aggregatore->Abilita ();
Successivamente, vengono calcolati i valori 2-D e ciascuno di essi viene scritto individualmente nel
FileAggregator usando il Scrivi2d() funzione.
doppio tempo;
doppio valore;
// Crea il set di dati 2D.
per (tempo = -5.0; tempo <= +5.0; tempo += 1.0)
{
// Calcola la curva 2D
//
// 2
// valore = tempo.
//
valore = tempo * tempo;
// Aggiungi questo punto alla trama.
aggregatore->Write2d (datasetContext, ora, valore);
}
// Disabilita la registrazione dei dati per l'aggregatore.
aggregatore->Disabilita ();
}
Adattatori
Questa sezione descrive in dettaglio le funzionalità fornite dalla classe Adapter a un NS-3
simulazione. Questa sezione è pensata per gli utenti interessati a sviluppare simulazioni con il
NS-3 strumenti e utilizzando il Data Collection Framework, di cui fa parte la classe Adapter,
per generare dati in uscita con i risultati della loro simulazione.
Nota: il termine 'adattatore' può anche essere scritto 'adattatore'; abbiamo scelto l'ortografia allineata
con lo standard C++.
Adattatore Panoramica
Un adattatore viene utilizzato per effettuare connessioni tra diversi tipi di oggetti DCF.
Ad oggi è stato implementato un Adapter:
· Adattatore TimeSeries
Ora Campionati Adattatore
TimeSeriesAdaptor consente alle sonde di connettersi direttamente agli aggregatori senza bisogno di alcuno
Collezionista in mezzo.
Entrambi gli helper DCF implementati utilizzano TimeSeriesAdaptors per essere sondati
valori di diverso tipo ed emette l'ora corrente più il valore con entrambi convertiti
raddoppiare.
Il ruolo della classe TimeSeriesAdaptor è quello di un adattatore, che accetta valori non elaborati
sonda dati di diverso tipo ed emette una tupla di due doppi valori. Il primo è un
timestamp, che può essere impostato su risoluzioni diverse (es. Secondi, Millisecondi, ecc.) in
il futuro ma che attualmente è codificato in Seconds. La seconda è la conversione di a
valore non doppio a un valore doppio (possibilmente con perdita di precisione).
Ambito/Limitazioni
Questa sezione discute l'ambito e le limitazioni del Data Collection Framework.
Attualmente, solo queste sonde sono state implementate in DCF:
· Sonda Booleana
· Doppia sonda
· Uintero8Sonda
· Uintero16Sonda
· Uintero32Sonda
· Sonda temporale
·PacketProbe
· ApplicationPacketProbe
· Ipv4PacketProbe
Attualmente, nessun Collector è disponibile nel DCF, sebbene sia presente un BasicStatsCollector
.
Attualmente, solo questi Aggregatori sono stati implementati in DCF:
· Gnuplot Aggregatore
· FileAgregator
Attualmente, solo questo adattatore è stato implementato in DCF:
Adattatore per serie temporali.
Futuro Lavora
Questa sezione discute il lavoro futuro da svolgere sul quadro di raccolta dei dati.
Ecco alcune cose che devono ancora essere fatte:
· Collega più fonti di traccia in NS-3 codice per ottenere più valori dal simulatore.
· Implementare più tipi di sonde di quante ce ne siano attualmente.
· Implementa più del semplice raccoglitore 2-D corrente, BasicStatsCollector.
· Implementare più aggregatori.
· Implementa più che semplici adattatori.
Statistico Contesto
Questo capitolo descrive il lavoro sulla raccolta dei dati di simulazione e il quadro statistico per
ns-3.
Il codice sorgente per il framework statistico si trova nella directory src/statistiche.
Obiettivi
Gli obiettivi principali di questo sforzo sono i seguenti:
· Fornire funzionalità per registrare, calcolare e presentare dati e statistiche per l'analisi
di simulazioni di rete.
· Aumenta le prestazioni della simulazione riducendo la necessità di generare ampi log di tracciamento in
per raccogliere dati.
· Abilitare il controllo della simulazione tramite statistiche online, ad esempio terminando le simulazioni o
prove ripetute.
Gli obiettivi secondari derivati e altre caratteristiche target includono quanto segue:
· Integrazione con il sistema di tracciamento ns-3 esistente come quadro di strumentazione di base
del motore di simulazione interno, ad esempio stack di rete, dispositivi di rete e canali.
· Consentire agli utenti di utilizzare il framework statistico senza richiedere l'uso del tracciamento
.
· Aiutare gli utenti a creare, aggregare e analizzare i dati in più prove.
· Supporto per la strumentazione creata dall'utente, ad esempio di eventi specifici dell'applicazione e
provvedimenti.
· Basso sovraccarico di memoria e CPU quando il pacchetto non è in uso.
· Sfruttare il più possibile gli strumenti di analisi e output esistenti. Il framework potrebbe
fornire alcune statistiche di base, ma l'attenzione è rivolta alla raccolta dei dati e alla loro elaborazione
accessibile per la manipolazione con strumenti consolidati.
· L'eventuale supporto per la distribuzione di repliche indipendenti è importante ma non incluso
nel primo ciclo di lungometraggi.
Panoramica
Il framework statistico include le seguenti funzionalità:
· Il framework principale e due collettori di dati di base: un contatore e un min/max/media/totale
osservatore.
· Estensioni di questi per lavorare facilmente con orari e pacchetti.
· Output in testo normale formattato per OMNet++.
· Output del database utilizzando SQLite, un motore SQL autonomo, leggero e ad alte prestazioni.
· Metadati obbligatori e aperti per descrivere e lavorare con le esecuzioni.
· Un esempio basato sull'esperimento nozionale di esame delle proprietà di NS-3
Prestazioni WiFi ad hoc predefinite. Incorpora quanto segue:
· Costrutti di una rete WiFi ad hoc a due nodi, con i nodi a una distanza parametrizzata
a parte.
· Applicazioni di origine e di ricezione del traffico UDP con comportamento leggermente diverso e
ganci di misurazione rispetto alle classi di stock.
· Raccolta dati dal nucleo NS-3 tramite segnali di traccia esistenti, in particolare dati su
frame trasmessi e ricevuti dagli oggetti MAC WiFi.
· Strumentazione di applicazioni personalizzate collegando nuovi segnali di traccia allo stat
framework, nonché tramite aggiornamenti diretti. Vengono registrate informazioni sui pacchetti totali
inviati e ricevuti, byte trasmessi e ritardo end-to-end.
· Un esempio di utilizzo dei tag dei pacchetti per tracciare il ritardo end-to-end.
· Uno script di controllo semplice che esegue una serie di prove dell'esperimento a diverse velocità
distanze e interroga il database risultante per produrre un grafico utilizzando GNUPlot.
Fare
Gli elementi ad alta priorità includono:
· Inclusione di codice statistico online, ad esempio per intervalli di confidenza efficienti in termini di memoria.
· Disposizioni nei raccoglitori di dati per terminare le esecuzioni, vale a dire quando viene raggiunta una soglia o
la fiducia è soddisfatta.
· Raccoglitori di dati per la registrazione dei campioni nel tempo e l'output in vari formati.
· Dimostrare come scrivere un semplice collante per eventi ciclici per interrogare regolarmente un valore.
Ciascuno di questi aspetti dovrebbe risultare semplice da integrare nel quadro attuale.
Approccio
Il quadro si basa sui seguenti principi fondamentali:
· Un esperimento di prova viene condotto da un'istanza di un programma di simulazione, sia in
in parallelo o in serie.
· Uno script di controllo esegue istanze della simulazione, variando i parametri secondo necessità.
· I dati vengono raccolti e archiviati per la rappresentazione grafica e l'analisi mediante script esterni e
strumenti esistenti.
· Le misure all'interno del nucleo ns-3 vengono prese collegando il framework stat a quello esistente
segnali di traccia.
· I segnali di traccia o la manipolazione diretta del framework possono essere utilizzati per strumentare misure personalizzate
codice di simulazione.
Questi componenti di base del framework e le loro interazioni sono rappresentati nel
figura seguente. [immagine]
Esempio
Questa sezione illustra il processo di costruzione di un esperimento nel framework e
producendo dati per l'analisi (grafici) da esso, dimostrando la struttura e l'API insieme
la via.
La Domanda
''Quali sono le prestazioni (simulate) dei dispositivi di rete WiFi di ns-3 (utilizzando l'impostazione predefinita
impostazioni)? Quanto distanti possono essere i nodi wireless in una simulazione prima che non possano
comunicare in modo affidabile?''
· Ipotesi: sulla base della conoscenza delle prestazioni nella vita reale, i nodi dovrebbero comunicare
ragionevolmente bene ad almeno 100 m di distanza. La comunicazione oltre i 200 m non dovrebbe essere
fattibile.
Sebbene non sia una domanda molto comune nei contesti di simulazione, questa è una proprietà importante
di cui gli sviluppatori di simulazioni dovrebbero avere una conoscenza di base. È anche un argomento comune
studio effettuato su hardware attivo.
Simulazione Programma
La prima cosa da fare nell'implementazione di questo esperimento è sviluppare la simulazione
programma. Il codice per questo esempio può essere trovato in esempi/statistiche/wifi-example-sim.cc.
Esegue i seguenti passaggi principali.
· Dichiarazione dei parametri e analisi della riga di comando utilizzando ns3::Riga di comando.
doppia distanza = 50.0;
formato stringa ("OMNet++");
esperimento di stringa ("wifi-distance-test");
strategia stringa ("wifi-default");
stringa runID;
Riga di comando cmd;
cmd.AddValue("distance", "Distanza tra i nodi (in metri).", distance);
cmd.AddValue("format", "Formato da utilizzare per l'output dei dati.", format);
cmd.AddValue("esperimento", "Identificatore per l'esperimento.", esperimento);
cmd.AddValue("strategia", "Identificatore per la strategia.", strategia);
cmd.AddValue("run", "Identificatore per l'esecuzione.", runID);
cmd.Parse (argc, argv);
· Creazione di nodi e stack di rete utilizzando ns3::ContenitoreNodo, ns3::WiFiHelpere
ns3::InternetStackHelper.
nodi NodeContainer;
nodi.Crea(2);
WifiHelper wifi;
wifi.SetMac("ns3::AdhocWifiMac");
wifi.SetPhy("ns3::WifiPhy");
NetDeviceContainer nodeDevices = wifi.Install(nodes);
InternetStackHelper Internet;
internet.Install(nodi);
Ipv4AddressHelper ipAddrs;
ipAddrs.SetBase("192.168.0.0", "255.255.255.0");
ipAddrs.Assign(nodeDevices);
· Posizionamento dei nodi utilizzando ns3::MobilityHelperPer impostazione predefinita i nodi hanno statici
mobilità e non si muoveranno, ma devono essere posizionati alla distanza indicata. Ci sono
diversi modi per farlo; qui lo si fa usando ns3::ListPositionAllocator, che disegna
posizioni da un elenco dato.
MobilitàAiutante mobilità;
Ptr posizioneAlloc =
CreaOggetto ();
positionAlloc->Add(Vector(0.0, 0.0, 0.0));
positionAlloc->Add(Vector(0.0, distanza, 0.0));
mobilità.SetPositionAllocator(positionAlloc);
mobilità.Installa(nodi);
· Installazione di un generatore di traffico e di un dissipatore di traffico. Il magazzino Applicazioni potrebbe essere
utilizzato, ma l'esempio include oggetti personalizzati in src/test/test02-apps.(cc|h). Queste
hanno un comportamento semplice, generando un dato numero di pacchetti distanziati a un intervallo dato.
Poiché ce n'è solo uno di ciascuno, vengono installati manualmente; per un set più grande il
ns3::ApplicationHelper potrebbe essere utilizzata la classe. La classe commentata Config::Imposta cambiamenti di linea
la destinazione dei pacchetti, impostata per impostazione predefinita su broadcast in questo esempio. Nota che
in generale il WiFi può avere prestazioni diverse per i frame broadcast e unicast a causa di
diverse politiche di controllo della velocità e di ritrasmissione MAC.
Ptr appSource = NodeList::OttieniNodo(0);
Ptr mittente = CreateObject ();
appSource->AddApplication(mittente);
mittente->Avvio(secondi(1));
Ptr appSink = NodeList::OttieniNodo(1);
Ptr ricevitore = CreateObject ();
appSink->AddApplication(ricevitore);
ricevitore->Avvio(secondi(0));
// Config::Set("/NodeList/*/ApplicationList/*/$Sender/Destination",
// ValoreIndirizzoIpv4("192.168.0.2"));
· Configurazione dei dati e delle statistiche da raccogliere. Il paradigma di base è che un
ns3::DataCollector l'oggetto è creato per contenere informazioni su questa particolare esecuzione, per
quali osservatori e calcolatori sono collegati per generare effettivamente i dati. È importante notare che
le informazioni di esecuzione includono etichette per ''esperimento'', ''strategia'', ''input'' e
''run''. Vengono utilizzati per identificare e raggruppare facilmente i dati di più prove in un secondo momento.
· L'esperimento è lo studio di cui questa sperimentazione fa parte. Eccolo su WiFi
prestazioni e distanza.
· La strategia è il codice o i parametri esaminati in questa sperimentazione. In questo esempio
è stato risolto, ma un'estensione ovvia sarebbe quella di indagare su diversi bit WiFi
tariffe, ciascuna delle quali rappresenterebbe una strategia diversa.
· L'input è il problema specifico dato a questa prova. Qui è semplicemente il
distanza tra i due nodi.
· Il runID è un identificatore univoco per questa sperimentazione con cui vengono contrassegnate le sue informazioni
per l'identificazione in analisi successive. Se non viene fornito alcun ID di esecuzione, il programma di esempio esegue
un ID di esecuzione (debole) che utilizza l'ora corrente.
Questi quattro metadati sono obbligatori, ma potrebbero essere desiderati di più. Possono essere aggiunti
al record utilizzando il ns3::DataCollector::AddMetadata() metodo.
Dati DataCollector;
data.DescribeRun(esperimento, strategia, input, runID);
data.AddMetadata("autore", "tjkopena");
L'osservazione e il calcolo effettivi vengono effettuati tramite ns3::DataCalculator oggetti, di cui
Esistono diversi tipi. Questi vengono creati dal programma di simulazione, allegato a
codice di segnalazione o campionamento, e quindi registrato con il ns3::DataCollector così lo faranno
essere interrogati in seguito per il loro output. Un semplice meccanismo di osservazione è quello di utilizzare i dati esistenti
fonti di tracciamento, ad esempio per strumentare oggetti nel nucleo ns-3 senza modificarne
codice. Qui un contatore è collegato direttamente a un segnale di traccia nel livello MAC WiFi su
il nodo di destinazione.
Ptr totalRx = CreaOggetto ();
totalRx->SetKey("frame-rx-wifi");
Config::Connect("/NodeList/1/DeviceList/*/$ns3::WifiNetDevice/Rx",
MakeCallback(&PacketCounterCalculator::FrameUpdate, totalRx));
dati.AddDataCalculator(totalRx);
Le calcolatrici possono anche essere manipolate direttamente. In questo esempio, viene creato un contatore e
passato all'applicazione di assorbimento del traffico per essere aggiornato quando vengono ricevuti i pacchetti.
Ptr > appRx = CreaOggetto >();
appRx->SetKey("pacchetti-ricevitore-rx");
ricevitore->SetCounter(appRx);
dati.AddDataCalculator(appRx);
Per incrementare il conteggio, il codice di elaborazione dei pacchetti del sink chiama quindi uno dei
metodi di aggiornamento della calcolatrice.
m_calc->Aggiorna();
Il programma include anche molti altri esempi, utilizzando sia la primitiva
calcolatrici come ns3::CounterCalculator e quelli adattati per osservare i pacchetti e
volte. In src/test/test02-apps.(cc|h) crea anche un semplice tag personalizzato che utilizza
per tracciare il ritardo end-to-end per i pacchetti generati, segnalando i risultati a un
ns3::TimeMinMaxAvgTotalCalculator calcolatrice dati.
· Esecuzione della simulazione, che una volta realizzata è molto semplice.
Simulatore::Esegui();
· Generazione di entrambi OMNet++ or SQLite output, a seconda degli argomenti della riga di comando. Per
fai questo ns3::DataOutputInterface l'oggetto viene creato e configurato. Il tipo specifico
di questo determinerà il formato di output. A questo oggetto viene quindi assegnato il
ns3::DataCollector oggetto che interroga per produrre l'output.
Ptr produzione;
se (formato == "OMNet++") {
NS_LOG_INFO("Creazione dell'output di dati formattati OMNet++.");
output = CreaOggetto ();
} Else {
# ifdef STAT_USE_DB
NS_LOG_INFO("Creazione di output di dati formattati in SQLite.");
output = CreaOggetto ();
# finisci se
}
output->Output(dati);
· Liberare la memoria utilizzata dalla simulazione. Questa operazione dovrebbe essere eseguita alla fine del file principale.
funzione per l'esempio.
Simulatore::Distruggi();
Registrazione
Per vedere in dettaglio cosa fanno il programma di esempio, le applicazioni e il framework stat, imposta
, il NS_LOG variabile in modo appropriato. Quanto segue fornirà un output copioso da tutti
tre.
$ export NS_LOG=WiFiDistanceExperiment:WiFiDistanceApps
Si noti che ciò rallenta notevolmente la simulazione.
Campione Uscita
La compilazione e la semplice esecuzione del programma di test aggiungeranno OMNet++ output formattato come
quanto segue a dati.sca.
corri corri-1212239121
esperimento attr "wifi-distance-test"
strategia attr "wifi-default"
attributo input "50"
descrizione attributo ""
attr "autore" "tjkopena"
conteggio dei frame wifi-tx scalari 30
conteggio dei frame wifi-rx scalari 30
conteggio scalare pacchetti mittente-trasmissione 30
conteggio dei pacchetti ricevitore-rx scalari 30
conteggio scalare tx-pkt-size 30
dimensione totale del pacchetto tx scalare 1920
dimensione media scalare tx-pkt 64
scalare tx-pkt-size max 64
scalar tx-pkt-size min 64
conteggio ritardo scalare 30
ritardo scalare totale 5884980ns
ritardo scalare medio 196166ns
ritardo scalare max 196166ns
ritardo scalare min 196166ns
Controllate Copione
Per automatizzare la raccolta dati su una varietà di input (distanze), un semplice Bash
Lo script viene utilizzato per eseguire una serie di simulazioni. Può essere trovato su
esempi/statistiche/wifi-example-db.shLo script è pensato per essere eseguito da esempi/statistiche/
directory.
Lo script attraversa una serie di distanze, raccogliendo i risultati in un SQLite
database. Ad ogni distanza vengono condotte cinque prove per dare un quadro migliore delle aspettative
prestazioni. L'intero esperimento richiede solo poche decine di secondi per essere eseguito a bassa velocità
macchina poiché non c'è output durante la simulazione e viene generato poco traffico.
#!/bin/sh
DISTANZE="25 50 75 100 125 145 147 150 152 155 157 160 162 165 167 170 172 175 177 180"
PROVE="1 2 3 4 5"
Esempio di esperimento Echo WiFi
se [ -e data.db ]
poi
echo Uccidere data.db?
leggi ANS
se [ "$ANS" = "sì" -o "$ANS" = "y" ]
poi
echo Eliminazione del database
rm data.db
fi
fi
per la prova in $TRIALS
do
per la distanza in $DISTANCES
do
echo Prova $prova, distanza $distanza
./bin/test02 --format=db --distance=$distance --run=run-$distance-$trial
fatto
fatto
Analisi e Conclusione
Una volta condotte tutte le prove, lo script esegue una semplice query SQL su
database utilizzando il SQLite programma da riga di comando. La query calcola la perdita media di pacchetti in
ogni serie di prove associata a ciascuna distanza. Non tiene conto di diversi
strategie, ma le informazioni sono presenti nel database per realizzare alcune semplici estensioni
e farlo. I dati raccolti vengono poi trasmessi a GNUPlot per la rappresentazione grafica.
CMD="seleziona exp.input,avg(100-((rx.value*100)/tx.value)) \
da Singletons rx, Singletons tx, Esperimenti exp \
dove rx.run = tx.run E \
rx.run = exp.run E \
rx.name='pacchetti-ricevitore-rx' E \
tx.name='pacchetti-trasmissione-mittente' \
raggruppa per exp.input \
ordina per abs(exp.input) ASC;"
sqlite3 -noheader data.db "$CMD" > wifi-default.data
sed -i "s/|/ /" wifi-default.data
gnuplot wifi-example.gnuplot
Lo script GNUPlot trovato su esempi/statistiche/wifi-example.gnuplot definisce semplicemente l'output
formato e alcune formattazioni di base per il grafico.
imposta terminale postscript ritratto migliorato lw 2 "Helvetica" 14
imposta dimensione 1.0, 0.66
#-------------------------------------------------------
impostare "wifi-default.eps"
#set title "Perdita di pacchetti a distanza"
imposta xlabel "Distanza (m) --- media di 5 prove per punto"
imposta xrange [0:200]
imposta ylabel "% Perdita di pacchetti"
imposta yrange [0:110]
tracciare "wifi-default.data" con le linee intitolate "WiFi Defaults"
Fine Risultato
Il grafico risultante non fornisce alcuna prova che le prestazioni del modello WiFi predefinito siano
necessariamente irragionevole e conferisce una certa fiducia ad una fedeltà almeno simbolica a
realtà. Ancora più importante, questa semplice indagine è stata portata avanti fino in fondo
utilizzando il framework statistico. Successo! [immagine]
Tempo reale
NS-3 è stato progettato per l'integrazione in ambienti di testbed e macchine virtuali. Per
integrarsi con stack di rete reali ed emettere/consumare pacchetti, uno scheduler in tempo reale è
necessario provare a bloccare l'orologio di simulazione con l'orologio hardware. Descriviamo qui un
componente di questo: lo scheduler RealTime.
Lo scopo dello scheduler in tempo reale è quello di causare la progressione dell'orologio di simulazione
per verificarsi in modo sincrono rispetto ad una base temporale esterna. Senza la presenza di
una base temporale esterna (orologio da parete), il tempo di simulazione salta istantaneamente da uno simulato
tempo al prossimo.
Comportamento
Quando si utilizza uno scheduler non in tempo reale (l'impostazione predefinita in NS-3), il simulatore avanza il
tempo di simulazione per il prossimo evento programmato. Durante l'esecuzione dell'evento, il tempo di simulazione è
congelato. Con lo scheduler in tempo reale, il comportamento è simile dal punto di vista di
modelli di simulazione (vale a dire, il tempo di simulazione è congelato durante l'esecuzione dell'evento), ma tra
eventi, il simulatore tenterà di mantenere l'orologio di simulazione allineato con la macchina
orologio.
Quando un evento termina l'esecuzione e lo scheduler passa all'evento successivo, il
lo scheduler confronta il tempo di esecuzione del prossimo evento con l'orologio della macchina. Se il prossimo
l'evento è programmato per un momento futuro, il simulatore rimane inattivo finché non viene raggiunto il tempo reale
e quindi esegue l'evento successivo.
Può accadere che, a causa dell'elaborazione insita nell'esecuzione degli eventi di simulazione,
che il simulatore non riesce a tenere il passo con il tempo reale. In tal caso, spetta all'utente
configurazione cosa fare. Ci sono due NS-3 attributi che governano il comportamento. Il
il primo è ns3::RealTimeSimulatorImpl::SynchronizationModeLe due voci possibili per
questo attributo sono Miglior sforzo (predefinito) o Limite rigidoIn modalità "BestEffort", il
il simulatore cercherà semplicemente di recuperare il tempo reale eseguendo eventi finché non raggiunge un
punto in cui il prossimo evento è nel futuro (in tempo reale), altrimenti la simulazione termina. In
Modalità BestEffort, quindi, è possibile che la simulazione consumi più tempo del
tempo dell'orologio a muro. L'altra opzione "HardLimit" causerà l'interruzione della simulazione se
soglia di tolleranza è superata. Questo attributo è ns3::RealTimeSimulatorImpl::HardLimit
e il valore predefinito è 0.1 secondi.
Una modalità di funzionamento diversa è quella in cui il tempo simulato è non è un congelato durante un evento
esecuzione. Questa modalità di simulazione in tempo reale è stata implementata ma rimossa dal NS-3 albero
a causa di dubbi sulla sua utilità. Se gli utenti sono interessati a un tempo reale
simulatore per il quale il tempo di simulazione non si blocca durante l'esecuzione dell'evento (ad esempio, ogni
chiama a Simulatore::Adesso() restituisce l'ora corrente dell'orologio a muro, non l'ora in cui il
l'evento è iniziato), contattare la mailing list ns-developers.
Impiego
Dal punto di vista dello scripting, l'utilizzo del simulatore in tempo reale è semplice.
Gli utenti devono solo impostare l'attributo SimulatorImplementationType al tempo reale
simulatore, come ad esempio:
GlobalValue::Bind ("SimulatorImplementationType",
StringValue ("ns3::RealtimeSimulatorImpl"));
C'è una sceneggiatura in esempi/tempo reale/realtime-udp-echo.cc che ha un esempio di come
configura il comportamento in tempo reale. Prova:
$ ./waf --run realtime-udp-echo
È stabilito se il simulatore funzionerà secondo una politica di massimo sforzo o di limite rigido.
dagli attributi spiegati nella sezione precedente.
Implementazione/Attuazione
L'implementazione è contenuta nei seguenti file:
· src/core/model/realtime-simulator-impl.{cc,h}
· src/core/model/wall-clock-synchronizer.{cc,h}
Per creare uno scheduler in tempo reale, in prima approssimazione, vuoi solo causare
salti temporali di simulazione per consumare tempo reale. Proponiamo di farlo utilizzando una combinazione di
attese sleep e busy. Le attese sleep fanno sì che il processo chiamante (thread) produca
processore per un certo periodo di tempo. Anche se questo periodo di tempo specificato può essere superato
alla risoluzione di nanosecondi, viene effettivamente convertito in una granularità specifica del sistema operativo. In
In Linux, la granularità è chiamata Jiffy. In genere questa risoluzione è insufficiente per
le nostre esigenze (nell'ordine di dieci millisecondi), quindi arrotondando per difetto e dormendo per un po'
numero minore di Jiffies. Il processo viene quindi risvegliato dopo il numero specificato di
Jiffies è passato. Al momento, abbiamo ancora un po' di tempo da aspettare. Questa volta è
generalmente inferiore al tempo minimo di sonno, quindi aspettiamo indaffarati per il resto del
tempo. Ciò significa che il thread si trova semplicemente in un ciclo for consumando cicli finché il
arriva l'ora desiderata. Dopo la combinazione di attese di sonno e di attività, il tempo reale trascorso
(l'orologio da parete) dovrebbe concordare con l'ora di simulazione del prossimo evento e la simulazione
provento.
Helpers
I capitoli precedenti ti hanno introdotto a vari NS-3 concetti di programmazione come smart
puntatori per la gestione della memoria con conteggio dei riferimenti, attributi, namespace, callback, ecc.
Gli utenti che lavorano su questa API di basso livello possono interconnettersi NS-3 oggetti con granulometria fine.
Tuttavia, un programma di simulazione scritto interamente utilizzando l'API di basso livello sarebbe piuttosto lungo
e noioso da programmare. Per questo motivo, è stata sovrapposta una cosiddetta "API helper" separata
sul nucleo NS-3 API. Se hai letto il NS-3 tutorial, avrai già familiarità
con l'API helper, poiché è l'API a cui solitamente vengono presentati per primi i nuovi utenti.
In questo capitolo, introduciamo la filosofia di progettazione dell'API helper e la confrontiamo con
l'API di basso livello. Se diventi un utente assiduo di NS-3, probabilmente ti muoverai avanti e indietro
tra queste API anche nello stesso programma.
L'API helper ha alcuni obiettivi:
1. il resto di src / non ha dipendenze dall'API helper; tutto ciò che può essere fatto con
l'API helper può essere codificata anche nell'API di basso livello
2. Contenitore: Spesso le simulazioni dovranno eseguire un certo numero di azioni identiche sui gruppi
di oggetti. L'API helper fa ampio uso di contenitori di oggetti simili a cui
è possibile eseguire operazioni simili o identiche.
3. L'API helper non è generica; non si sforza di massimizzare il riutilizzo del codice. Quindi,
costrutti di programmazione come il polimorfismo e i modelli che consentono il riutilizzo del codice sono
non così prevalenti. Ad esempio, ci sono helper CsmaNetDevice separati e
Helper PointToPointNetDevice ma non derivano da una base NetDevice comune
classe.
4. L'API helper in genere funziona con oggetti allocati nello stack (rispetto a quelli allocati nell'heap). Per
alcuni programmi, NS-3 gli utenti potrebbero non doversi preoccupare di alcun oggetto di basso livello creato o
Gestione Ptr; possono cavarsela con contenitori di oggetti e helper allocati nello stack
che operano su di loro.
L'API helper è in realtà tutta incentrata sulla creazione NS-3 programmi più facili da scrivere e leggere, senza
eliminando la potenza dell'interfaccia di basso livello. Il resto di questo capitolo fornisce alcuni
esempi delle convenzioni di programmazione dell'API helper.
Fare Terreni utilizzando , il gnuplot Classe
Esistono 2 metodi comuni per creare un grafico utilizzando NS-3 e gnuplot (‐
http://www.gnuplot.info):
1. Creare un file di controllo gnuplot utilizzando NS-3Classe Gnuplot di.
2. Creare un file di dati gnuplot utilizzando i valori generati da NS-3.
Questa sezione riguarda il metodo 1, ovvero come creare un grafico utilizzando NS-3Gnuplot di
classe. Se sei interessato al metodo 2, consulta la sottosezione "Un esempio reale" sotto
Sezione "Tracciamento" nella NS-3 Tutorial.
Creazione Terreni utilizzando , il gnuplot Classe
Per creare un grafico utilizzando NS-3Classe Gnuplot di:
1. Modifica il codice in modo che utilizzi la classe Gnuplot e le sue funzioni.
2. Esegui il codice in modo che crei un file di controllo gnuplot.
3. Chiamare gnuplot con il nome del file di controllo gnuplot.
4. Visualizza il file grafico prodotto nel tuo visualizzatore grafico preferito.
Per i dettagli sul passaggio 1, vedere il codice dei grafici di esempio discussi di seguito.
An Esempio Programma che si utilizza , il gnuplot Classe
Un programma di esempio che utilizza NS-3La classe Gnuplot di può essere trovata qui:
src/stats/examples/gnuplot-example.cc
Per eseguire questo esempio, procedere come segue:
$ ./waf shell
$ cd build/debug/src/stats/examples
$ ./gnuplot-example
Ciò dovrebbe produrre i seguenti file di controllo gnuplot nella directory in cui si trova l'esempio
si trova:
plot-2d.plt
plot-2d-con-barre-di-errore.plt
plot-3d.plt
Per elaborare questi file di controllo gnuplot, procedere come segue:
$ gnuplot plot-2d.plt
$ gnuplot plot-2d-with-error-bars.plt
$ gnuplot plot-3d.plt
Ciò dovrebbe produrre i seguenti file grafici nella directory in cui si trova l'esempio
situato:
plot-2d.png
plot-2d-with-error-bars.png
plot-3d.png
Puoi visualizzare questi file grafici nel tuo visualizzatore grafico preferito. Se hai GIMP
installato sul tuo computer, ad esempio, puoi fare questo:
$ gimp plot-2d.png
$ gimp plot-2d-with-error-bars.png
$ gimp plot-3d.png
An Esempio 2 dimensionale Terreno
Il seguente grafico bidimensionale
[immagine]
è stato creato utilizzando il seguente codice da gnuplot-example.cc:
usando lo spazio dei nomi std;
string fileNameWithNoExtension = "plot-2d";
string graphicsFileName = fileNameWithNoExtension + ".png";
string plotFileName = fileNameWithNoExtension + ".plt";
string plotTitle = "Grafico 2D";
string dataTitle = "Dati 2-D";
// Crea un'istanza del grafico e impostane il titolo.
Gnuplot plot (graphicsFileName);
plot.SetTitle (plotTitle);
// Crea il file grafico che il file di tracciamento creerà quando
// viene utilizzato con Gnuplot, è un file PNG.
plot.SetTerminal ("png");
// Imposta le etichette per ciascun asse.
plot.SetLegend ("Valori X", "Valori Y");
// Imposta l'intervallo per l'asse x.
plot.AppendExtra ("imposta xrange [-6:+6]");
// Crea un'istanza del set di dati, imposta il suo titolo e rendi i punti
// tracciato insieme alle linee di collegamento.
Insieme di dati Gnuplot2dDataset;
dataset.SetTitle (dataTitle);
set di dati.SetStyle (Gnuplot2dDataset::LINES_POINTS);
doppia x;
doppia y;
// Crea il set di dati 2D.
per (x = -5.0; x <= +5.0; x += 1.0)
{
// Calcola la curva 2D
//
// 2
// y = x .
//
y = x * x;
// Aggiungi questo punto.
set di dati.Aggiungi (x, y);
}
// Aggiungi il set di dati al grafico.
plot.AddDataset (set di dati);
// Apri il file del grafico.
ofstream plotFile (plotFileName.c_str());
// Scrivere il file del grafico.
plot.GenerateOutput (plotFile);
// Chiude il file del grafico.
plotFile.close ();
An Esempio 2 dimensionale Terreno con Errore I bar
Il seguente grafico bidimensionale con barre di errore nelle direzioni x e y
[immagine]
è stato creato utilizzando il seguente codice da gnuplot-example.cc:
usando lo spazio dei nomi std;
string fileNameWithNoExtension = "plot-2d-with-error-bars";
string graphicsFileName = fileNameWithNoExtension + ".png";
string plotFileName = fileNameWithNoExtension + ".plt";
string plotTitle = "Grafico 2D con barre di errore";
string dataTitle = "Dati 2D con barre di errore";
// Crea un'istanza del grafico e impostane il titolo.
Gnuplot plot (graphicsFileName);
plot.SetTitle (plotTitle);
// Crea il file grafico che il file di tracciamento creerà quando
// viene utilizzato con Gnuplot, è un file PNG.
plot.SetTerminal ("png");
// Imposta le etichette per ciascun asse.
plot.SetLegend ("Valori X", "Valori Y");
// Imposta l'intervallo per l'asse x.
plot.AppendExtra ("imposta xrange [-6:+6]");
// Crea un'istanza del set di dati, imposta il suo titolo e rendi i punti
// tracciato senza linee di collegamento.
Insieme di dati Gnuplot2dDataset;
dataset.SetTitle (dataTitle);
set di dati.SetStyle (Gnuplot2dDataset::POINTS);
// Fai in modo che il set di dati abbia barre di errore in entrambe le direzioni x e y.
set di dati.SetErrorBars (Gnuplot2dDataset::XY);
doppia x;
doppio xErrorDelta;
doppia y;
doppio yErrorDelta;
// Crea il set di dati 2D.
per (x = -5.0; x <= +5.0; x += 1.0)
{
// Calcola la curva 2D
//
// 2
// y = x .
//
y = x * x;
// Rendi costante l'incertezza nella direzione x e rendi
// l'incertezza nella direzione y sia una frazione costante di
// valore di y.
xErrorDelta = 0.25;
yErrorDelta = 0.1 * y;
// Aggiungi questo punto con incertezze sia in x che in y
// direzione.
set di dati.Aggiungi (x, y, xErrorDelta, yErrorDelta);
}
// Aggiungi il set di dati al grafico.
plot.AddDataset (set di dati);
// Apri il file del grafico.
ofstream plotFile (plotFileName.c_str());
// Scrivere il file del grafico.
plot.GenerateOutput (plotFile);
// Chiude il file del grafico.
plotFile.close ();
An Esempio 3 dimensionale Terreno
Il seguente grafico bidimensionale
[immagine]
è stato creato utilizzando il seguente codice da gnuplot-example.cc:
usando lo spazio dei nomi std;
string fileNameWithNoExtension = "plot-3d";
string graphicsFileName = fileNameWithNoExtension + ".png";
string plotFileName = fileNameWithNoExtension + ".plt";
string plotTitle = "Grafico 3D";
string dataTitle = "Dati 3-D";
// Crea un'istanza del grafico e impostane il titolo.
Gnuplot plot (graphicsFileName);
plot.SetTitle (plotTitle);
// Crea il file grafico che il file di tracciamento creerà quando
// viene utilizzato con Gnuplot, è un file PNG.
plot.SetTerminal ("png");
// Ruota il grafico di 30 gradi attorno all'asse x e poi ruotalo
// traccia un tracciato di 120 gradi attorno al nuovo asse z.
plot.AppendExtra ("imposta vista 30, 120, 1.0, 1.0");
// Fai in modo che lo zero per l'asse z sia sul piano degli assi x e y.
plot.AppendExtra ("imposta ticslevel 0");
// Imposta le etichette per ciascun asse.
plot.AppendExtra ("set xlabel 'Valori X'");
plot.AppendExtra ("imposta ylabel 'Valori Y'");
plot.AppendExtra ("set zlabel 'Valori Z'");
// Imposta gli intervalli per gli assi x e y.
plot.AppendExtra ("imposta xrange [-5:+5]");
plot.AppendExtra ("imposta yrange [-5:+5]");
// Crea un'istanza del set di dati, imposta il suo titolo e rendi i punti
// collegati da linee.
Insieme di dati Gnuplot3dDataset;
dataset.SetTitle (dataTitle);
dataset.SetStyle ("con linee");
doppia x;
doppia y;
doppia z;
// Crea il set di dati 3D.
per (x = -5.0; x <= +5.0; x += 1.0)
{
per (y = -5.0; y <= +5.0; y += 1.0)
{
// Calcola la superficie 3D
//
// 2 2
// z = x * y .
//
z = x * x * y * y;
// Aggiungi questo punto.
set di dati.Aggiungi (x, y, z);
}
// La riga vuota è necessaria alla fine di ogni dato del valore x
// punti per il funzionamento della griglia di superficie 3D.
dataset.AddEmptyLine ();
}
// Aggiungi il set di dati al grafico.
plot.AddDataset (set di dati);
// Apri il file del grafico.
ofstream plotFile (plotFileName.c_str());
// Scrivere il file del grafico.
plot.GenerateOutput (plotFile);
// Chiude il file del grafico.
plotFile.close ();
utilizzando Python a Correre NS-3
I binding Python consentono il codice C++ in NS-3 da chiamare da Python.
Questo capitolo mostra come creare uno script Python che può essere eseguito NS-3 e anche la
processo di creazione di associazioni Python per un C++ NS-3 modulo.
Introduzione
L'obiettivo dei binding Python per NS-3 sono duplici:
1. Consentire al programmatore di scrivere script di simulazione completi in Python (‐
http://www.python.org);
2. Prototipare nuovi modelli (ad esempio protocolli di routing).
Per il momento, l'attenzione primaria degli attacchi è rivolta al primo obiettivo, ma il secondo
Anche l'obiettivo sarà eventualmente supportato. Associazioni Python per NS-3 sono in fase di sviluppo
utilizzando un nuovo strumento chiamato PyBindGen (http://code.google.com/p/pybindgen).
An Esempio Python Copione che Esegue NS-3
Ecco un esempio di codice scritto in Python e che viene eseguito NS-3, che è scritto
in C++. Questo esempio Python può essere trovato in esempi/tutorial/first.py:
importa ns.applications
importa ns.core
importa ns.internet
importa ns.network
importa ns.point_to_point
ns.core.LogComponentEnable("UdpEchoClientApplication", ns.core.LOG_LEVEL_INFO)
ns.core.LogComponentEnable("UdpEchoServerApplication", ns.core.LOG_LEVEL_INFO)
nodi = ns.network.NodeContainer()
nodi.Crea(2)
pointToPoint = ns.point_to_point.PointToPointHelper()
pointToPoint.SetDeviceAttribute("DataRate", ns.core.StringValue("5Mbps"))
pointToPoint.SetChannelAttribute("Ritardo", ns.core.StringValue("2ms"))
dispositivi = pointToPoint.Install(nodi)
pila = ns.internet.InternetStackHelper()
stack.Install(nodi)
indirizzo = ns.internet.Ipv4AddressHelper()
indirizzo.SetBase(ns.network.Ipv4Address("10.1.1.0"), ns.network.Ipv4Mask("255.255.255.0"))
interfacce = indirizzo.Assegna (dispositivi);
echoServer = ns.applications.UdpEchoServerHelper(9)
serverApps = echoServer.Install(nodi.Ottieni(1))
serverApps.Start(ns.core.Seconds(1.0))
serverApps.Stop(ns.core.Seconds(10.0))
echoClient = ns.applications.UdpEchoClientHelper(interfacce.GetAddress(1), 9)
echoClient.SetAttribute("MaxPackets", ns.core.UintegerValue(1))
echoClient.SetAttribute("Intervallo", ns.core.TimeValue(ns.core.Secondi (1.0)))
echoClient.SetAttribute("Dimensione pacchetto", ns.core.UintegerValue(1024))
clientApps = echoClient.Install(nodi.Ottieni(0))
clientApps.Start(ns.core.Seconds(2.0))
clientApps.Stop(ns.core.Seconds(10.0))
ns.core.Simulator.Run()
ns.core.Simulator.Destroy()
corsa Python Script
waf contiene alcune opzioni che aggiornano automaticamente il percorso python per trovare ns3
modulo. Per eseguire programmi di esempio, ci sono due modi per usare waf per occuparsene. Uno
è quello di eseguire una shell waf; ad esempio:
$ ./waf --shell
$ python examples/wireless/mixed-wireless.py
e l'altro è usare l'opzione --pyrun per waf:
$ ./waf --pyrun esempi/wireless/mixed-wireless.py
Per eseguire uno script Python nel debugger C:
$ ./waf --shell
$ gdb --args python examples/wireless/mixed-wireless.py
Per eseguire il tuo script Python che chiama NS-3 e che ha questo percorso,
/percorso/verso/il/tuo/esempio/mio-script.py, Fare quanto segue:
$ ./waf --shell
$ python /percorso/del/tuo/esempio/mio-script.py
Avvertenze
Associazioni Python per NS-3 sono un lavoro in corso e alcune limitazioni sono note a
sviluppatori. Alcune di queste limitazioni (non tutte) sono elencate qui.
Incompleto Copertura
Innanzitutto, tieni presente che non il 100% delle API è supportato in Python. Alcune delle
le ragioni sono:
1. Alcune API coinvolgono puntatori, che richiedono la conoscenza del tipo di memoria
passaggio di semantica (chi possiede quale memoria). Tale conoscenza non fa parte della funzione
firme, ed è documentato o talvolta non documentato. Le annotazioni sono
necessario per associare tali funzioni;
2. A volte viene utilizzato un tipo di dati fondamentale insolito o un costrutto C++ che non è ancora
supportato da PyBindGen;
3. GCC-XML non segnala classi basate su template a meno che non siano istanziate.
La maggior parte delle API mancanti possono essere integrate, se si ha abbastanza tempo, pazienza e competenza, e
sarà probabilmente completato se vengono inviate segnalazioni di bug. Tuttavia, non inviare una segnalazione di bug
dicendo "le associazioni sono incomplete", perché non abbiamo manodopera per completare il 100% del
legature.
Conversione Costruttori
Conversione costruttori non sono ancora completamente supportati da PyBindGen e agiscono sempre come
costruttori espliciti quando si traduce un'API in Python. Ad esempio, in C++ è possibile fare
Questo:
Ipv4AddressHelper ipAddrs;
ipAddrs.SetBase ("192.168.0.0", "255.255.255.0");
ipAddrs.Assign (backboneDevices);
In Python, per il momento devi fare:
ipAddrs = ns3.Ipv4AddressHelper()
ipAddrs.SetBase(ns3.Ipv4Address("192.168.0.0"), ns3.Ipv4Mask("255.255.255.0"))
ipAddrs.Assign(backboneDevices)
Riga di comando
CommandLine::AddValue() funziona in modo diverso in Python rispetto a NS-3In Python, il
il primo parametro è una stringa che rappresenta il nome dell'opzione della riga di comando. Quando l'opzione
è impostato, un attributo con lo stesso nome del nome dell'opzione è impostato su Riga di comando()
oggetto. Esempio:
NUM_NODI_SIDE_DEFAULT = 3
cmd = ns3.CommandLine()
cmd.NumNodesSide = Nessuno
cmd.AddValue("NumNodesSide", "Numero di nodi sul lato della griglia (il numero totale di nodi sarà questo numero al quadrato)")
cmd.Parse(argv)
[...]
se cmd.NumNodesSide è None:
num_nodes_side = NUM_NODES_SIDE_DEFAULT
altro:
num_nodes_side = int(cmd.NumNodesSide)
Tracciato
Il tracciamento basato su callback non è ancora supportato correttamente per Python, come nuovo NS-3 L'API ha bisogno di
essere forniti affinché ciò sia supportato.
La scrittura di file Pcap è supportata tramite la normale API.
Il tracciamento ASCII è supportato da NS-3.4 tramite la normale API C++ tradotta in Python.
Tuttavia, il tracciamento ASCII richiede la creazione di un oggetto ostream da passare nell'ASCII
metodi di tracciamento. In Python, lo std::ofstream C++ è stato minimamente racchiuso per consentire
questo. Per esempio:
ascii = ns3.ofstream("wifi-ap.tr") # crea il file
ns3.YansWifiPhyHelper.EnableAsciiAll(ascii)
ns3.Simulator.Run()
ns3.Simulator.Destroy()
ascii.close() # chiude il file
C'è un avvertimento: non devi consentire che l'oggetto file venga garbage collection mentre NS-3
lo sta ancora utilizzando. Ciò significa che la variabile 'ascii' sopra non deve essere autorizzata ad andare
fuori dall'ambito, altrimenti il programma si bloccherà.
Cygwin limitazione
I binding Python non funzionano su Cygwin. Ciò è dovuto a un bug di gccxml.
Potresti farla franca rieseguendo la scansione delle definizioni API dall'interno di Cygwin
ambiente (./waf --python-scan). Tuttavia la soluzione più probabile sarà probabilmente quella di
è possibile che disabilitiamo i binding Python in CygWin.
Se ti interessano davvero i binding Python su Windows, prova a compilare con mingw e native
python invece. Oppure, per compilare senza binding python, disabilitare i binding python in
fase di configurazione:
$ ./waf configure --disable-python
lavoro con Python Associazioni
Attualmente ci sono due tipi di binding Python in NS-3:
1. I binding monolitici contengono definizioni API per tutti i moduli e possono essere trovati in
una singola directory, associazioni/python.
2. I binding modulari contengono definizioni API per un singolo modulo e possono essere trovati in ciascuno
moduli attacchi directory.
Python Associazioni Workflow
Il processo tramite il quale vengono gestiti i binding Python è il seguente:
1. Periodicamente uno sviluppatore utilizza un GCC-XML (http://www.gccxml.org) scansione API basata
script, che salva la definizione API scansionata come associazioni/python/ns3_module_*.py file
o come file Python in ogni modulo attacchi directory. Questi file sono conservati sotto
controllo della versione nel principale NS-3 deposito;
2. Altri sviluppatori clonano il repository e utilizzano le definizioni API già scansionate;
3. Durante la configurazione NS-3, pybindgen verrà scaricato automaticamente se non è già stato fatto
installato. Rilasciato NS-3 i tarball spediranno una copia di pybindgen.
Se qualcosa va storto durante la compilazione dei binding Python e vuoi semplicemente ignorarli
e procedi con C++, puoi disabilitare Python con:
$ ./waf --disable-python
Istruzioni da Manovrabilità New File or Cambiato API
Quindi hai cambiato l'esistente NS-3 Le API e i binding Python non vengono più compilati?
non disperare, puoi riesaminare i binding per creare nuovi binding che riflettano le modifiche
ai NS-3 API.
A seconda che si utilizzino binding monolitici o modulari, vedere le discussioni di seguito per
Scopri come eseguire una nuova scansione dei binding Python.
Monolitico Python Associazioni
Scansione , il Monolitico Python Associazioni
Per analizzare i binding monolitici di Python, procedere come segue:
$ ./waf --python-scan
Organizzazione of , il Monolitico Python Associazioni
Le definizioni monolitiche dell'API Python sono organizzate come segue. Per ciascuna NS-3 modulo
, il file associazioni/python/ns3_module_ .py descrive la sua API. Ognuna di queste
i file hanno 3 funzioni di primo livello:
1. def tipi_di_registrazione(modulo)(): questa funzione si occupa di registrare nuovi tipi (ad esempio
Classi C++, enumerazioni) definite nel modulo;
2. def metodi di registrazione(modulo)(): questa funzione chiama, per ogni classe , un altro
funzione register_methods_Ns3 (modulo). Queste ultime funzioni aggiungono il metodo
definizioni per ogni classe;
3. def funzioni di registro(modulo)(): questa funzione registra NS-3 funzioni che appartengono a
quel modulo.
componibile Python Associazioni
Panoramica
A partire dalla versione ns 3.11, vengono aggiunti i binding modulari, parallelamente al vecchio monolitico
legature.
I nuovi binding Python vengono generati in uno spazio dei nomi 'ns', invece di 'ns3' per il vecchio
associazioni. Esempio:
da ns.network importa Node
n1 = Nodo()
Con associazioni Python modulari:
1. Esiste un modulo di estensione Python separato per ciascuno NS-3 modulo;
2. La scansione delle definizioni API (apidef) viene eseguita su base per modulo ns;
3. I file apidefs di ciascun modulo sono memorizzati in una sottodirectory 'bindings' del modulo
elenco;
Scansione , il componibile Python Associazioni
Per analizzare i binding Python modulari per il modulo core, ad esempio, procedere come segue:
$ ./waf --apiscan=core
Per analizzare i binding Python modulari per tutti i moduli, procedere come segue:
$ ./waf --apiscan=all
Creazione a New Moduli
Se si aggiunge un nuovo modulo, i binding Python continueranno a compilarsi ma non
coprire il nuovo modulo.
Per coprire un nuovo modulo, è necessario creare un associazioni/python/ns3_module_ .py file,
simile a quanto descritto nelle sezioni precedenti, e registrarlo nella variabile
LOCAL_MODULES() in associazioni/python/ns3modulegen.py
Aggiunta componibile Associazioni A A Esistente Moduli
Per aggiungere il supporto per i binding modulari a un esistente NS-3 modulo, aggiungi semplicemente quanto segue
riga alla sua funzione wscript build():
bld.ns3_python_bindings()
Organizzazione of , il componibile Python Associazioni
. fonte/ /legami la directory può contenere i seguenti file, alcuni dei quali
facoltativo:
· callbacks_list.py: questo è un file scansionato, NON TOCCARE. Contiene un elenco di
Istanze del modello Callback<...> trovate nelle intestazioni scansionate;
· modulogen__gcc_LP64.py: questo è un file scansionato, NON TOCCARE. Definizioni API scansionate
per l'architettura GCC, LP64 (64 bit)
· modulogen__gcc_ILP32.py: questo è un file scansionato, NON TOCCARE. Definizioni API scansionate
per l'architettura GCC, ILP32 (32 bit)
· modulegen_customizations.py: puoi facoltativamente aggiungere questo file per personalizzare il
generazione di codice pybindgen
· scan-header.h: puoi facoltativamente aggiungere questo file per personalizzare il file di intestazione da scansionare
per il modulo. Fondamentalmente questo file viene scansionato al posto di ns3/ -modulo.h.
In genere, la prima istruzione è #include "ns3/ -module.h", più qualche altro
cose per forzare le istanziazioni dei template;
· modulo_helpers.cc: puoi aggiungere file aggiuntivi, come questo, da collegare a Python
modulo di estensione, ma devono essere registrati nel wscript. Guarda
src/core/wscript per un esempio di come farlo;
· .py: se questo file esiste, diventa il modulo python "frontend" per ns3
modulo e il modulo di estensione (file .so) diventa _ .quindi invece di .COSÌ.
IL Il file .py deve importare tutti i simboli dal modulo _ (questo è più
più complicato di quanto sembri, vedere src/core/bindings/core.py per un esempio), e quindi è possibile aggiungere
alcune definizioni aggiuntive in puro Python.
altro Informazioni da Sviluppatori
Se sei uno sviluppatore e hai bisogno di maggiori informazioni su NS-3Per i collegamenti Python di , vedere
Python Associazioni wiki pagina.
Test
Panoramica
Questo documento riguarda la verifica e la convalida di NS-3 Software.
Questo documento fornisce
· nozioni di base sulla terminologia e sui test del software (Capitolo 2);
· una descrizione del framework di test ns-3 (Capitolo 3);
· una guida per gli sviluppatori di modelli o per i nuovi contributori di modelli su come scrivere i test (Capitolo
4);
In breve, i primi tre capitoli dovrebbero essere letti dagli sviluppatori e dai collaboratori di ns che
è necessario capire come contribuire con codice di test e programmi convalidati, e il resto
del documento fornisce spazio alle persone per segnalare quali aspetti dei modelli selezionati
sono stati convalidati.
sfondo
Questo capitolo può be saltato by lettori familiare con , il "Basics" of Software test.
Scrivere software privo di difetti è un compito difficile. Ci sono molte dimensioni da considerare
problema e c'è molta confusione riguardo a cosa si intende con termini diversi in
diversi contesti. Abbiamo ritenuto utile dedicare un po' di tempo alla revisione del
soggetto e definizione di alcuni termini.
Il test del software può essere definito in senso lato come il processo di esecuzione di un programma con
intento di trovare errori. Quando si entra in una discussione riguardante i test del software,
diventa subito evidente che ci sono molte mentalità distinte con cui si può
avvicinarsi all'argomento.
Ad esempio, si potrebbe suddividere il processo in ampie categorie funzionali come
''test di correttezza'', ''test delle prestazioni'', ''test di robustezza'' e ''test di sicurezza''
test.'' Un altro modo per guardare al problema è attraverso il ciclo di vita: ''test dei requisiti,''
''test di progettazione'', ''test di accettazione'' e ''test di manutenzione''. Un altro punto di vista
è dall'ambito del sistema testato. In questo caso si può parlare di "test unitario",
''test dei componenti'', ''test di integrazione'' e ''test di sistema''. Questi termini sono
inoltre non standardizzati in alcun modo, e quindi ''test di manutenzione'' e ''regressione
"test" può essere inteso in modo intercambiabile. Inoltre, questi termini sono spesso utilizzati in modo improprio.
Esistono anche diversi approcci filosofici al test del software. Per
Ad esempio, alcune organizzazioni consigliano di scrivere programmi di test prima di implementarli effettivamente
il software desiderato, producendo uno "sviluppo basato sui test". Alcune organizzazioni sostengono
testare dal punto di vista del cliente il prima possibile, seguendo un parallelo con il
processo di sviluppo agile: ''testare presto e testare spesso.'' Questo a volte è chiamato
''test agile''. Sembra che ci sia almeno un approccio al test per ogni
metodologia di sviluppo.
. NS-3 Il progetto non ha come obiettivo quello di promuovere nessuno di questi processi, ma
il progetto nel suo complesso ha requisiti che aiutano a informare il processo di test.
Come tutti i principali prodotti software, NS-3 ha una serie di qualità che devono essere presenti per
il prodotto per avere successo. Dal punto di vista dei test, alcune di queste qualità che devono essere
indirizzati sono quelli NS-3 deve essere ''corretto'', ''robusto'', ''performante'' e
''mantenibile''. Idealmente dovrebbero esserci delle metriche per ciascuna di queste dimensioni che siano
controllato dai test per identificare quando il prodotto non soddisfa le aspettative /
requisiti.
Correttezza
Lo scopo essenziale del test è determinare che un pezzo di software si comporta
''correttamente.'' Per NS-3 questo significa che se simuliamo qualcosa, la simulazione dovrebbe
rappresentare fedelmente un'entità fisica o un processo con una precisione specificata e
precisione.
Sembra che esistano due prospettive da cui si può osservare la correttezza.
Verificare che un particolare modello sia implementato secondo le sue specifiche è
genericamente chiamato verificaIl processo di decisione che il modello è corretto per
il suo uso previsto è genericamente chiamato convalida.
Convalida e Convalida
Un modello informatico è una rappresentazione matematica o logica di qualcosa. Può
rappresentano un veicolo, un elefante (vedi David Di Harel parlare informazioni modellismo an elefante at
SIMUTools 2009o una scheda di rete. I modelli possono anche rappresentare processi come globali
riscaldamento, flusso del traffico autostradale o una specifica di un protocollo di rete. I modelli possono essere
rappresentazioni completamente fedeli di una specifica di processo logico, ma
necessariamente non può mai simulare completamente un oggetto fisico o un processo. Nella maggior parte dei casi, un
vengono apportate numerose semplificazioni al modello per rendere la simulazione computazionalmente
trattabile.
Ogni modello ha un bersaglio sistema che sta tentando di simulare. Il primo passo in
la creazione di un modello di simulazione consiste nell'identificare questo sistema target e il livello di dettaglio e
accuratezza che la simulazione desidera riprodurre. Nel caso di un processo logico,
il sistema di destinazione può essere identificato come ''TCP come definito da RFC 793.'' In questo caso,
sarà probabilmente auspicabile creare un modello che riproduca completamente e fedelmente RFC
793. Nel caso di un processo fisico questo non sarà possibile. Se, ad esempio,
se si desidera simulare una scheda di rete wireless, è possibile determinare che è necessario, ''un
implementazione accurata a livello MAC della specifica 802.11 e [...] un non così lento
Modello di livello PHY della specifica 802.11a.''
Una volta fatto questo, si può sviluppare un modello astratto del sistema di destinazione. Questo è
in genere un esercizio di gestione dei compromessi tra complessità, requisiti di risorse
e accuratezza. Il processo di sviluppo di un modello astratto è stato chiamato modello
maglieria in nella letteratura. Nel caso di un protocollo TCP, questo processo si traduce in un
progettare una raccolta di oggetti, interazioni e comportamenti che saranno pienamente implementati
RFC 793 in NS-3Nel caso della scheda wireless, questo processo si traduce in una serie di
compromessi per consentire la simulazione dello strato fisico e la progettazione di un dispositivo di rete
e canale per ns-3, insieme agli oggetti, alle interazioni e ai comportamenti desiderati.
Questo modello astratto viene poi sviluppato in un NS-3 modello che implementa l'astratto
modello come programma per computer. Il processo per ottenere l'implementazione in accordo con il
il modello astratto è chiamato modello verifica nella letteratura.
Finora il processo è a ciclo aperto. Resta da stabilire se un dato ns-3
il modello ha una qualche connessione con la realtà, ovvero che un modello è una rappresentazione accurata di
un sistema reale, sia esso un processo logico o un'entità fisica.
Se si vuole usare un modello di simulazione per cercare di prevedere come sta andando un sistema reale
per comportarsi, ci deve essere qualche ragione per credere ai tuoi risultati, cioè, ci si può fidare di ciò
un'inferenza fatta dal modello si traduce in una previsione corretta per il sistema reale.
Il processo per ottenere il comportamento del modello ns-3 in accordo con il sistema target desiderato
il comportamento definito dal processo di qualificazione del modello è chiamato modello convalida nella
letteratura. Nel caso di un'implementazione TCP, potresti voler confrontare il comportamento di
il tuo modello TCP ns-3 ad un'implementazione di riferimento per convalidare il tuo modello. In
nel caso di una simulazione di livello fisico wireless, potresti voler confrontare il comportamento di
il tuo modello a quello dell'hardware reale in un ambiente controllato,
. NS-3 l'ambiente di test fornisce strumenti per consentire sia la convalida del modello che
test e incoraggia la pubblicazione dei risultati di convalida.
Robustezza
La robustezza è la qualità di essere in grado di resistere alle sollecitazioni o ai cambiamenti ambientali,
input o calcoli, ecc. Un sistema o un progetto è "robusto" se può gestire tali
modifiche con una perdita minima di funzionalità.
Questo tipo di test viene solitamente eseguito con un focus particolare. Ad esempio, il sistema come
un intero può essere eseguito su molte configurazioni di sistema diverse per dimostrare che può
funzionare correttamente in un gran numero di ambienti.
Il sistema può anche essere stressato operando vicino o oltre la capacità generando
o simulare l'esaurimento delle risorse di vario genere. Questo genere di test è chiamato
''test di stress''
Il sistema e i suoi componenti possono essere esposti ai cosiddetti "test puliti" che dimostrano
un risultato positivo, ovvero che il sistema funziona correttamente in risposta a un grande
variazione delle configurazioni previste.
Il sistema e i suoi componenti possono anche essere esposti a ''test sporchi'' che forniscono input
al di fuori dell'intervallo previsto. Ad esempio, se un modulo si aspetta una stringa terminata da zero
rappresentazione di un intero, un test sporco potrebbe fornire una stringa non terminata di numeri casuali
caratteri per verificare che il sistema non si blocchi a causa di questo input imprevisto.
Sfortunatamente, rilevare tali input "sporchi" e adottare misure preventive per garantire la
affinché il sistema non si guasti in modo catastrofico può richiedere un'enorme quantità di risorse di sviluppo.
Per ridurre i tempi di sviluppo, è stata presa la decisione, fin dalle prime fasi del progetto, di
ridurre al minimo la quantità di convalida dei parametri e la gestione degli errori nel NS-3 base di codice. Per
Per questo motivo, non dedichiamo molto tempo ai test sporchi: ciò porterebbe solo alla scoperta
risultati della decisione progettuale che sappiamo di aver preso.
Vogliamo dimostrare che NS-3 il software funziona in alcune condizioni. Noi
prendiamo in prestito un paio di definizioni per restringere un po' il campo. dominio of applicabilità is
un insieme di condizioni prescritte per le quali il modello è stato testato, confrontato con
realtà nella misura del possibile, e giudicata idonea all'uso. La gamma of precisione offre
accordo tra il modello computerizzato e la realtà all'interno di un dominio di applicabilità.
. NS-3 l'ambiente di test fornisce strumenti per consentire l'impostazione e l'esecuzione dei test
ambienti su più sistemi (buildbot) e fornisce classi per incoraggiare la pulizia
test per verificare il funzionamento del sistema sul ''dominio di applicabilità'' previsto
e ''intervallo di precisione''.
potente
Ok, "performant" non è una parola inglese vera e propria. È, tuttavia, un neologismo molto conciso.
che è abbastanza spesso usato per descrivere ciò che vogliamo NS-3 essere: potente e abbastanza veloce da
finisci il lavoro.
Questo riguarda in realtà l'ampio argomento dei test delle prestazioni del software. Uno degli aspetti chiave
ciò che viene fatto è confrontare due sistemi per scoprire quale funziona meglio (cfr.
benchmark). Questo viene utilizzato per dimostrare che, ad esempio, NS-3 può eseguire un tipo di base
di simulazione almeno veloce quanto uno strumento concorrente, o può essere utilizzato per identificare parti di
il sistema che funziona male.
Nel NS-3 framework di test, forniamo supporto per la temporizzazione di vari tipi di test.
manutenibilità
Un prodotto software deve essere manutenibile. Questa è, ancora una volta, un'affermazione molto ampia, ma
Il framework di test può aiutare con il compito. Una volta che un modello è stato sviluppato, convalidato e
verificato, possiamo eseguire ripetutamente la serie di test per l'intero sistema per garantire
che rimanga valido e verificato per tutta la sua durata.
Quando una funzionalità smette di funzionare come previsto dopo qualche tipo di modifica al sistema
integrato, è chiamato genericamente un regressioneOriginariamente il termine regressione
si riferiva a un cambiamento che causava la ricomparsa di un bug precedentemente risolto, ma il termine è stato
evoluto per descrivere qualsiasi tipo di cambiamento che interrompe la funzionalità esistente. Ce ne sono molti
tipi di regressioni che possono verificarsi nella pratica.
A locale regressione è quello in cui un cambiamento influenza direttamente il componente modificato. Per
ad esempio, se un componente viene modificato per allocare e liberare memoria ma i puntatori obsoleti sono
utilizzato, il componente stesso si guasta.
A a distanza regressione è uno in cui una modifica a un componente interrompe la funzionalità in
un altro componente. Ciò riflette la violazione di un implicito ma possibilmente non riconosciuto
contratto tra componenti.
An smascherato regressione è quello che crea una situazione in cui un bug preesistente
che non ha avuto alcun effetto viene improvvisamente esposto nel sistema. Questo può essere semplice come fare esercizio
un percorso di codice per la prima volta.
A performance regressione è quello che fa sì che i requisiti di prestazione del sistema siano
essere violato. Ad esempio, eseguire un lavoro in una funzione di basso livello che può essere ripetuto
un numero elevato di volte può rendere improvvisamente il sistema inutilizzabile da determinate prospettive.
. NS-3 Il framework di test fornisce strumenti per automatizzare il processo utilizzato per convalidare e
verificare il codice in suite di test notturne per aiutare a identificare rapidamente possibili regressioni.
Testing contesto
ns-3 è costituito da un motore di simulazione centrale, un set di modelli, programmi di esempio e test.
Nel tempo, nuovi collaboratori contribuiscono con modelli, test ed esempi. Un programma di test Python
prova.py funge da gestore dell'esecuzione dei test; prova.py può eseguire codice di prova ed esempi per
cercare regressioni, può restituire i risultati in diversi formati e può gestire il codice
strumenti di analisi della copertura. Oltre a questo, aggiungiamo Buildbots che sono build automatizzate
robot che eseguono test di robustezza eseguendo il framework di test su sistemi diversi
e con diverse opzioni di configurazione.
BuildBots
Al livello più alto dei test ns-3 ci sono i buildbot (robot di costruzione). Se sei
non hai familiarità con questo sistema guarda http://djmitche.github.com/buildbot/docs/0.7.11/.
Si tratta di un sistema automatizzato open source che consente NS-3 da ricostruire e testare ogni
tempo qualcosa è cambiato. Eseguendo i buildbot su diversi sistemi,
può garantire che NS-3 viene compilato ed eseguito correttamente su tutti i sistemi supportati.
Gli utenti (e gli sviluppatori) in genere non interagiscono con il sistema buildbot se non per
leggere i suoi messaggi riguardanti i risultati dei test. Se viene rilevato un guasto in uno dei
processi di build e test automatizzati, il buildbot invierà un'e-mail al ns-sviluppatori
mailing list. Questa e-mail sarà simile a
Nell'URL completo mostrato nell'email, è possibile cercare la parola chiave mancato e
selezionare la stdio collegamento al passaggio corrispondente per visualizzare il motivo dell'errore.
Il buildbot svolgerà il suo lavoro in silenzio se non ci sono errori e il sistema subirà
cicli di costruzione e test ogni giorno per verificare che tutto vada bene.
Test.py
I buildbot utilizzano un programma Python, prova.py, che è responsabile dell'esecuzione di tutti i
test e la raccolta dei report risultanti in un formato leggibile dall'uomo. Questo programma è
disponibile anche per l'uso da parte di utenti e sviluppatori.
prova.py è molto flessibile nel consentire all'utente di specificare il numero e il tipo di test da
esecuzione; e anche la quantità e il tipo di output da generare.
Prima di correre prova.py, assicurati che gli esempi e i test di ns3 siano stati creati eseguendo
di test
$ ./waf configure --enable-examples --enable-tests
$ ./waf
Per impostazione predefinita, prova.py eseguirà tutti i test disponibili e ne riporterà lo stato in modo molto conciso
modulo. Esecuzione del comando
$ ./prova.py
si tradurrà in un numero di PASSAGGIO, FAIL, CRASH or SALTA indicazioni seguite dal tipo di
test eseguito e il suo nome visualizzato.
Waf: Accesso alla directory `/home/craigdo/repos/ns-3-allinone-test/ns-3-dev/build'
Waf: Uscita dalla directory `/home/craigdo/repos/ns-3-allinone-test/ns-3-dev/build'
'build' è terminato con successo (0.939s)
FALLITO: TestSuite ns3-wifi-propagation-loss-models
PASS: TestSuite object-name-service
PASS: TestSuite pcap-file-oggetto
PASS: TestSuite ns3-tcp-cwnd
...
PASS: TestSuite ns3-tcp-interoperability
PASS: Esempio csma-broadcast
PASS: Esempio csma-multicast
Questa modalità è pensata per essere utilizzata dagli utenti interessati a determinare se il loro
la distribuzione funziona correttamente e dagli sviluppatori interessati a determinare se
le modifiche apportate hanno causato delle regressioni.
Sono disponibili numerose opzioni per controllare il comportamento di prova.pyse corri
prova.py --Aiuto dovresti vedere un riepilogo dei comandi come:
Utilizzo: test.py [opzioni]
Opzioni:
-h, --help mostra questo messaggio di aiuto ed esce
-b PERCORSO DI COSTRUZIONE, --buildpath=PERCORSO DI COSTRUZIONE
specificare il percorso in cui è stato creato ns-3 (il valore predefinito è
directory di compilazione per la variante corrente)
-c TIPO, --constrain=TIPO
vincolare il test-runner in base al tipo di test
-e ESEMPIO, --example=ESEMPIO
specificare un singolo esempio da eseguire (non è presente alcun percorso relativo)
necessario)
-g, --grind esegue le suite di test e gli esempi utilizzando valgrind
-k, --kinds stampa i tipi di test disponibili
-l, --list stampa l'elenco dei test noti
-m, --multiple segnala più errori da suite di test e test
casi
-n, --nowaf non eseguire waf prima di iniziare il test
-p PYEXAMPLE, --pyexample=PYEXAMPLE
specificare un singolo esempio Python da eseguire (con relativo
percorso)
-r, --retain conservano tutti i file temporanei (che normalmente sono
cancellato)
-s SUITE-DI-TEST, --suite=SUITE-DI-TEST
specificare una singola suite di test da eseguire
-t FILE-TESTO, --text=FILE-TESTO
scrivere i risultati dettagliati dei test in TEXT-FILE.txt
-v, --verbose stampa messaggi informativi e di avanzamento
-w FILE HTML, --web=FILE HTML, --html=FILE HTML
scrivere i risultati dettagliati dei test in HTML-FILE.html
-x FILE XML, --xml=FILE XML
scrivere i risultati dettagliati dei test in XML-FILE.xml
Se si specifica uno stile di output facoltativo, è possibile generare descrizioni dettagliate dell'
test e stato. Gli stili disponibili sono testo e HTMLI buildbot selezioneranno l'HTML
opzione per generare report di test HTML per le build notturne utilizzando
$ ./test.py --html=nightly.html
In questo caso, verrebbe creato un file HTML denominato ''nightly.html'' con un bel riepilogo
dei test effettuati. Un formato ''leggibile dall'uomo'' è disponibile per gli utenti interessati
dettagli.
$ ./test.py --text=risultati.txt
Nell'esempio sopra, la suite di test che controlla il NS-3 perdita di propagazione del dispositivo wireless
modelli falliti. Per impostazione predefinita non vengono fornite ulteriori informazioni.
Per approfondire ulteriormente il fallimento, prova.py consente di specificare una singola suite di test.
Esecuzione del comando
$ ./test.py --suite=ns3-wifi-propagation-loss-models
o in modo equivalente
$ ./test.py -s ns3-wifi-propagation-loss-models
si traduce nell'esecuzione di quella singola suite di test.
FALLITO: TestSuite ns3-wifi-propagation-loss-models
Per trovare informazioni dettagliate riguardanti il guasto, è necessario specificare il tipo di output
desiderato. Ad esempio, la maggior parte delle persone sarà probabilmente interessata a un file di testo:
$ ./test.py --suite=ns3-wifi-propagation-loss-models --text=results.txt
Ciò comporterà l'esecuzione di quella singola suite di test con lo stato del test scritto su
file ''results.txt''.
Dovresti trovare qualcosa di simile a quanto segue in quel file
FALLITO: Test Suite ''ns3-wifi-propagation-loss-models'' (reale 0.02 utente 0.01 sistema 0.00)
PASS: Caso di test "Controlla ... Friis ... modello ..." (reale 0.01 utente 0.00 sistema 0.00)
FALLITO: Caso di test "Controlla ... Distanza registro ... modello" (reale 0.01 utente 0.01 sistema 0.00)
Caratteristiche:
Messaggio: Ho ricevuto un valore SNR inaspettato
Condizione: [lunga descrizione di ciò che ha effettivamente fallito]
Reale: 176.395
Limite: 176.407 +- 0.0005
File: ../src/test/ns3wifi/propagation-loss-models-test-suite.cc
Linea: 360
Si noti che la Test Suite è composta da due casi di test. Il primo caso di test ha verificato
Il modello di perdita di propagazione di Friis è stato superato. Il secondo caso di test non ha superato il controllo del registro
Modello di propagazione della distanza. In questo caso, è stato trovato un SNR di 176.395 e il test
previsto un valore di 176.407 corretto fino a tre cifre decimali. Il file che ha implementato
viene elencato il test non superato e la riga di codice che ha causato l'errore.
Se lo desideri, potresti facilmente scrivere un file HTML utilizzando --html opzione
come descritto sopra.
In genere un utente eseguirà tutti i test almeno una volta dopo aver scaricato NS-3 per garantire che
il suo ambiente è stato costruito correttamente e sta generando risultati corretti
in base alle suite di test. Gli sviluppatori in genere eseguono le suite di test prima e
dopo aver apportato una modifica per assicurarsi di non aver introdotto una regressione con il loro
modifiche. In questo caso, gli sviluppatori potrebbero non voler eseguire tutti i test, ma solo un sottoinsieme. Per
ad esempio, lo sviluppatore potrebbe voler eseguire i test unitari solo periodicamente durante la creazione
modifiche a un repository. In questo caso, prova.py può essere detto di limitare i tipi di
test eseguiti su una particolare classe di test. Il seguente comando produrrà solo
i test unitari in esecuzione:
$ ./test.py --constrain=unità
Allo stesso modo, il seguente comando eseguirà solo i test di fumo di esempio:
$ ./test.py --constrain=unità
Per visualizzare un elenco rapido dei tipi di vincoli legali, è possibile richiederne l'elenco.
Il seguente comando
$ ./test.py --kinds
verrà visualizzato il seguente elenco:
Waf: Accesso alla directory `/home/craigdo/repos/ns-3-allinone-test/ns-3-dev/build'
Waf: Uscita dalla directory `/home/craigdo/repos/ns-3-allinone-test/ns-3-dev/build'
'build' completato con successo (0.939 s)Waf: Accesso alla directory `/home/craigdo/repos/ns-3-allinone-test/ns-3-dev/build'
bvt: test di verifica della build (per verificare se la build è stata completata correttamente)
core: esegui tutti i test basati su TestSuite (esclusi gli esempi)
esempio: Esempi (per verificare se i programmi di esempio vengono eseguiti correttamente)
prestazioni: test delle prestazioni (verifica se il sistema è veloce come previsto)
sistema: test di sistema (si estende su più moduli per verificarne l'integrazione)
unità: test unitari (all'interno dei moduli per verificare le funzionalità di base)
Qualsiasi di questi tipi di test può essere fornito come vincolo utilizzando il --vincolo opzione.
Per visualizzare un elenco rapido di tutte le suite di test disponibili, puoi richiederne l'installazione
elencati. Il seguente comando,
$ ./test.py --list
verrà visualizzato un elenco della suite di test, simile a
Waf: Accesso alla directory `/home/craigdo/repos/ns-3-allinone-test/ns-3-dev/build'
Waf: Uscita dalla directory `/home/craigdo/repos/ns-3-allinone-test/ns-3-dev/build'
'build' è terminato con successo (0.939s)
istogramma
ns3-interferenza-wifi
ns3-tcp-cwnd
interoperabilità ns3-tcp
campione
dispositivi-mesh-fiamma
dispositivi-mesh-dot11s
dispositivi-mesh
...
servizio-nome-oggetto
richiama
gli attributi
config
valore globale
Da riga di comando
numero casuale di base
oggetto
Ognuna di queste suite elencate può essere selezionata per essere eseguita da sola utilizzando --suite opzione come
sopra riportati.
Analogamente alle suite di test, è possibile eseguire un singolo programma di esempio C++ utilizzando --esempio
opzione. Si noti che non è necessario includere il percorso relativo per l'esempio e che
gli eseguibili creati per gli esempi C++ non hanno estensioni. Inserendo
$ ./test.py --esempio=udp-echo
si traduce nell'esecuzione di quell'unico esempio.
PASS: Esempio examples/udp/udp-echo
È possibile specificare la directory in cui è stato compilato ns-3 utilizzando --percorso di compilazione opzione come
segue.
$ ./test.py --buildpath=/home/craigdo/repos/ns-3-allinone-test/ns-3-dev/build/debug --example=wifi-simple-adhoc
È possibile eseguire un singolo programma di esempio Python utilizzando --pyexample opzione. Nota che il
il percorso relativo per l'esempio deve essere incluso e che gli esempi Python hanno bisogno del loro
estensioni. Entrando
$ ./test.py --pyexample=esempi/tutorial/first.py
si traduce nell'esecuzione di quell'unico esempio.
PASS: Esempio examples/tutorial/first.py
Poiché gli esempi Python non sono compilati, non è necessario specificare la directory in cui ns-3
è stato costruito per gestirli.
Solitamente, quando vengono eseguiti programmi di esempio, viene scritta una grande quantità di dati nel file di traccia.
Normalmente viene salvato nella directory di base della distribuzione (ad esempio,
/home/user/ns-3-dev). Quando prova.py fa un esempio, è davvero completamente indifferente
con i file di traccia. Vuole solo determinare se l'esempio può essere compilato ed eseguito
senza errori. Poiché questo è il caso, i file di traccia vengono scritti in un
/tmp/tracce-non-controllate directory. Se esegui l'esempio precedente, dovresti essere in grado di trovare
l'associato udp-echo.tr e udp-echo-n-1.pcap file lì.
L'elenco degli esempi disponibili è definito dal contenuto della directory ''examples'' in
la distribuzione. Se si seleziona un esempio per l'esecuzione utilizzando --esempio opzione,
prova.py non farà alcun tentativo di decidere se l'esempio è stato configurato o meno,
proverò semplicemente a eseguirlo e segnalerò il risultato del tentativo.
Quando prova.py viene eseguito, per impostazione predefinita assicurerà prima che il sistema sia stato completamente
costruito. Questo può essere sconfitto selezionando il --nowaf opzione.
$ ./test.py --list --nowaf
verrà visualizzato un elenco delle suite di test attualmente compilate, simile a:
modelli di perdita di propagazione ns3-wifi
ns3-tcp-cwnd
interoperabilità ns3-tcp
oggetto-file-pcap
servizio-nome-oggetto
generatori di numeri casuali
Notare l'assenza del Waf creare messaggi.
prova.py supporta anche l'esecuzione delle suite di test e degli esempi sotto valgrind. Valgrind è un
programma flessibile per il debug e la profilazione degli eseguibili Linux. Per impostazione predefinita, valgrind viene eseguito
uno strumento chiamato memcheck, che esegue una serie di funzioni di controllo della memoria, tra cui
rilevamento degli accessi alla memoria non inizializzata, uso improprio della memoria allocata (doppie liberazioni,
accesso dopo la liberazione, ecc.) e rilevamento delle perdite di memoria. Questo può essere selezionato utilizzando
--macinare opzione.
$ ./test.py --grind
Mentre corre, prova.py e i programmi che esegue indirettamente, generano un gran numero di
file temporanei. Di solito, il contenuto di questi file non è interessante, tuttavia in alcuni
In alcuni casi può essere utile (a fini di debug) visualizzare questi file. prova.py fornisce un
--conservare opzione che farà sì che questi file temporanei vengano conservati dopo l'esecuzione
completato. I file vengono salvati in una directory denominata testpy-output sotto una sottodirectory
denominato secondo l'attuale Tempo Coordinato Universale (noto anche come Greenwich Mean
Tempo).
$ ./test.py --retain
Infine, prova.py fornisce un --verboso opzione che stamperà grandi quantità di informazioni
sui suoi progressi. Non ci si aspetta che questo sarà terribilmente utile a meno che non ci sia
un errore. In questo caso, è possibile accedere all'output standard e all'errore standard
riportato eseguendo suite di test ed esempi. Selezionare "verbose" nel modo seguente:
$ ./test.py --verbose
Tutte queste opzioni possono essere combinate tra loro. Ad esempio, per eseguire tutti i core ns-3
suite di test sotto valgrind, in modalità dettagliata, durante la generazione di un file di output HTML, uno
farebbe:
$ ./test.py --verbose --grind --constrain=core --html=results.html
TestTassonomia
Come accennato in precedenza, i test sono raggruppati in una serie di classificazioni ampiamente definite per
consentire agli utenti di eseguire test in modo selettivo per affrontare i diversi tipi di test necessari
da fare.
· Test di verifica della build
· Test unitari
· Test di sistema
· Esempi
· Test delle prestazioni
BuildVerificationTests
Si tratta di test relativamente semplici che vengono costruiti insieme alla distribuzione e vengono utilizzati
per assicurarci che la build funzioni correttamente. I nostri attuali test unitari sono disponibili in
file sorgente del codice che testano e sono integrati nei moduli ns-3; e quindi si adattano al
Descrizione dei BVT. I BVT risiedono nello stesso codice sorgente integrato nel codice ns-3.
I nostri test attuali sono esempi di questo tipo di test.
Unità Test
I test unitari sono test più complessi che entrano nei dettagli per assicurarsi che un pezzo di codice
funziona come pubblicizzato in isolamento. Non c'è davvero motivo per cui questo tipo di test debba essere
integrato in un modulo ns-3. Si scopre, ad esempio, che l'unità esegue test per l'oggetto
I servizi di denominazione hanno all'incirca le stesse dimensioni del codice del servizio di denominazione dell'oggetto stesso. Test unitari
sono test che controllano un singolo bit di funzionalità che non è integrato nel codice ns-3,
ma risiedono nella stessa directory del codice che testa. È possibile che questi test
controllare anche l'integrazione di più file di implementazione in un modulo. Il file
src/core/test/names-test-suite.cc è un esempio di questo tipo di test. Il file
src/network/test/pcap-file-test-suite.cc è un altro esempio che utilizza un pcap noto e valido
file come file vettoriale di prova. Questo file è memorizzato localmente nella directory src/network.
Sistema Test
I test di sistema sono quelli che coinvolgono più di un modulo nel sistema. Ne abbiamo molti
questo tipo di test viene eseguito nel nostro attuale framework di regressione, ma in genere sono
esempi sovraccarichi. Forniamo un nuovo posto per questo tipo di test nella directory
origine/testIl file src/test/ns3tcp/ns3-interop-test-suite.cc è un esempio di questo tipo
di test. Utilizza NSC TCP per testare l'implementazione TCP ns-3. Spesso ci saranno test
vettori richiesti per questo tipo di test e sono memorizzati nella directory in cui si trova il
test di vita. Ad esempio, ns3tcp-interop-response-vectors.pcap è un file costituito da un
numero di intestazioni TCP utilizzate come risposte previste del TCP ns-3 in fase di test
a uno stimolo generato dal TCP NSC che viene utilizzato come implementazione ''nota bene''.
Esempi
Gli esempi vengono testati dal framework per assicurarsi che siano stati creati e che funzioneranno. Non c'è nulla
controllato, e attualmente i file pcap vengono semplicemente scritti in / Tmp da scartare. Se
gli esempi vengono eseguiti (non si bloccano) e superano questo test.
Cookie di prestazione Test
I test di prestazione sono quelli che esercitano una parte particolare del sistema e determinano
se i test sono stati completati in un tempo ragionevole.
corsa Test
I test vengono in genere eseguiti utilizzando il livello elevato prova.py programma. Per ottenere un elenco dei
opzioni della riga di comando disponibili, esegui prova.py --Aiuto
Il programma di prova prova.py eseguirà entrambi i test e gli esempi che sono stati aggiunti a
l'elenco da controllare. La differenza tra test ed esempi è la seguente. Test
in genere verificare che l'output o gli eventi specifici della simulazione siano conformi al comportamento previsto.
Al contrario, l'output degli esempi non viene controllato e il programma di test controlla semplicemente l'
stato di uscita del programma di esempio per assicurarsi che venga eseguito senza errori.
In breve, per eseguire tutti i test, prima è necessario configurare i test durante la fase di configurazione e
anche (facoltativamente) esempi se si vogliono controllare gli esempi:
$ ./waf --configure --enable-examples --enable-tests
Quindi, compila ns-3 e, dopo averlo compilato, eseguilo prova.py. prova.py -h mostrerà un numero
di opzioni di configurazione che modificano il comportamento di test.py.
Il programma prova.py richiama, per i test e gli esempi C++, un programma C++ di livello inferiore chiamato
corridore per eseguire effettivamente i test. Come discusso di seguito, questo corridore può essere un
un modo utile per eseguire il debug dei test.
Debug Test
Il debug dei programmi di test viene eseguito al meglio eseguendo il test-runner di basso livello
programma. Il test-runner è il ponte tra il codice Python generico e NS-3 codice. È
scritto in C++ e utilizza il processo di scoperta automatica dei test in NS-3 codice per trovare e
consentire l'esecuzione di tutti i vari test.
Il motivo principale per cui prova.py non è adatto per il debug è che non è consentito per
registrazione da attivare utilizzando NS_LOG variabile ambientale quando test.py viene eseguito. Questa
La limitazione non si applica all'eseguibile del test-runner. Pertanto, se si desidera visualizzare la registrazione
output dei test, devi eseguirli direttamente utilizzando il test-runner.
Per eseguire il test-runner, eseguilo come qualsiasi altro eseguibile ns-3, utilizzando
wafPer ottenere un elenco delle opzioni disponibili, puoi digitare:
$ ./waf --run "test-runner --help"
Dovresti vedere qualcosa di simile a quanto segue
Waf: Accesso alla directory `/home/craigdo/repos/ns-3-allinone-test/ns-3-dev/build'
Waf: Uscita dalla directory `/home/craigdo/repos/ns-3-allinone-test/ns-3-dev/build'
'build' è terminato con successo (0.353s)
--assert: indica ai test di eseguire segfault (come assert) se viene rilevato un errore
--basedir=dir: imposta la directory di base (dove trovare src) su ''dir''
--tempdir=dir: imposta la directory temporanea (dove trovare i file di dati) su ''dir''
--constrain=test-type: limita i controlli alle suite di test di tipo ''test-type''
--help: Stampa questo messaggio
--kinds: elenca tutti i tipi di test disponibili
--list: elenca tutte le suite di test (facoltativamente limitate dal tipo di test)
--out=file-name: imposta il file di output dello stato del test su ''file-name''
--suite=nome-suite: Esegui la suite di test denominata ''nome-suite''
--verbose: attiva i messaggi nelle suite di test di esecuzione
Ci sono una serie di cose a tua disposizione che ti saranno familiari se hai
guardò prova.pyCiò dovrebbe essere previsto poiché il test-runner è solo un'interfaccia
fra prova.py e NS-3Potresti notare che qui mancano i comandi correlati agli esempi.
Questo perché gli esempi non sono realmente NS-3 test. prova.py li gestisce come se fossero
per presentare un ambiente di test unificato, ma in realtà sono completamente diversi e non
si trova qui.
La prima nuova opzione che appare qui, ma non in test.py è la --affermare opzione. Questo
l'opzione è utile quando si esegue il debug di un caso di test quando si esegue con un debugger come gdb. Quando
selezionata, questa opzione indica al caso di test sottostante di causare una violazione della segmentazione se
viene rilevato un errore. Ciò ha il piacevole effetto collaterale di causare l'interruzione dell'esecuzione del programma
(entrare nel debugger) quando viene rilevato un errore. Se si utilizza gdb, è possibile utilizzare
questa opzione è qualcosa del tipo,
$ ./waf shell
$ cd build/debug/utils
$ gdb test-runner
$ run --suite=valore-globale --assert
Se viene quindi rilevato un errore nella suite di test del valore globale, verrà generato un segfault
e il debugger (a livello sorgente) si fermerebbe al NS_TEST_ASSERT_MSG che ha rilevato il
errore.
Un'altra nuova opzione che appare qui è la --basato opzione. Si scopre che alcuni
i test potrebbero dover fare riferimento alla directory di origine del NS-3 distribuzione per trovare locale
dati, quindi è sempre necessaria una directory di base per eseguire un test.
Se si esegue un test da test.py, il programma Python fornirà l'opzione basedir per
tu. Per eseguire uno dei test direttamente dal test-runner utilizzando waf, avrai bisogno di
specificare la suite di test da eseguire insieme alla directory di base. Quindi potresti usare la shell
e fai:
$ ./waf --run "test-runner --basedir=`pwd` --suite=pcap-file-object"
Notare le virgolette ''indietro'' sul pwd comando.
Se si esegue la suite di test da un debugger, può essere piuttosto doloroso ricordarlo
e digitare costantemente il percorso assoluto della directory di base della distribuzione. A causa di
questo, se ometti il basedir, il test-runner cercherà di capirne uno per te.
inizia nella directory di lavoro corrente e risale l'albero delle directory alla ricerca di un
file di directory con file denominati VERSIONE e LICENZA. Se ne trova uno, presume che
deve essere il baser e te lo fornisce.
Test produzione
Molte suite di test necessitano di scrivere file temporanei (come i file pcap) nel processo di
eseguendo i test. I test necessitano quindi di una directory temporanea in cui scrivere. Il Python
l'utilità di test (test.py) fornirà automaticamente un file temporaneo, ma se eseguito in modalità autonoma
questa directory temporanea deve essere fornita. Proprio come nel caso basedir, può essere
fastidioso dover continuamente fornire un --tempdir, quindi il test runner ne troverà uno
per te se non ne fornisci uno. Prima cerca le variabili d'ambiente denominate TMP
e TEMP e li usa. Se nessuno dei due TMP né TEMP sono definiti sceglie / Tmp. Il codice
quindi aggiunge un identificatore che indica cosa ha creato la directory (ns-3) quindi l'ora
(hh.mm.ss) seguito da un numero casuale elevato. Il test runner crea una directory di tale
nome da utilizzare come directory temporanea. I file temporanei vengono quindi inseriti in una directory che
sarà chiamato qualcosa del genere
/tmp/ns-3.10.25.37.61537845
L'ora è fornita come suggerimento in modo che tu possa ricostruire con relativa facilità cosa
directory è stata utilizzata se è necessario tornare indietro e guardare i file che sono stati inseriti in quella
directory.
Un'altra classe di output è l'output di test come le tracce pcap che vengono generate per il confronto con
output di riferimento. Il programma di test in genere li eliminerà dopo che tutte le suite di test
eseguire. Per disabilitare l'eliminazione dell'output del test, eseguire prova.py con l'opzione "mantieni":
$ ./test.py -r
e l'output del test può essere trovato in testpy-output/ directory.
Reportistica of test fallimenti
Quando si esegue una suite di test utilizzando test-runner, per impostazione predefinita il test verrà eseguito in modalità silenziosa.
L'unica indicazione che otterrai che il test è stato superato è il assenza di un messaggio
da waf dicendo che il programma ha restituito qualcosa di diverso da un codice di uscita zero. Per ottenere
alcuni output dal test, è necessario specificare un file di output in cui verranno salvati i test
scrivere il loro stato XML utilizzando il --fuori opzione. Bisogna fare attenzione nell'interpretare il
risultati perché le suite di test saranno aggiungere risultati su questo file. Prova,
$ ./waf --run "test-runner --basedir=`pwd` --suite=pcap-file-object --out=myfile.xml"
Se guardi il file miofile.xml dovresti vedere qualcosa del tipo,
oggetto-file-pcap
Controllare che PcapFile::Open con modalità ''w'' funzioni
PASSAGGIO
reale 0.00 utente 0.00 sistema 0.00
Controllare che PcapFile::Open con modalità ''r'' funzioni
PASSAGGIO
reale 0.00 utente 0.00 sistema 0.00
Controllare che PcapFile::Open con modalità ''a'' funzioni
PASSAGGIO
reale 0.00 utente 0.00 sistema 0.00
Verificare che PcapFileHeader sia gestito correttamente
PASSAGGIO
reale 0.00 utente 0.00 sistema 0.00
Verificare che PcapRecordHeader sia gestito correttamente
PASSAGGIO
reale 0.00 utente 0.00 sistema 0.00
Verificare che PcapFile possa leggere un file pcap noto e funzionante
PASSAGGIO
reale 0.00 utente 0.00 sistema 0.00
PASSAGGIO
reale 0.00 utente 0.00 sistema 0.00
Se hai familiarità con XML, questo dovrebbe essere abbastanza autoesplicativo. Inoltre, non è un
file XML completo poiché le suite di test sono progettate per avere il loro output aggiunto a un master
File di stato XML come descritto nel prova.py .
Debug test suite fallimenti
Per eseguire il debug di crash di test, come
CRASH: TestSuite ns3-wifi-interference
È possibile accedere al programma di esecuzione del test sottostante tramite gdb come segue, quindi passare il
Argomento "--basedir=`pwd`" da eseguire (è possibile passare anche altri argomenti se necessario, ma
basedir è il minimo necessario):
$ ./waf --command-template="gdb %s" --run "test-runner"
Waf: Accesso alla directory `/home/tomh/hg/sep09/ns-3-allinone/ns-3-dev-678/build'
Waf: Uscita dalla directory `/home/tomh/hg/sep09/ns-3-allinone/ns-3-dev-678/build'
'build' è terminato con successo (0.380s)
GNU gdb 6.8-debian
Copyright (C) 2008 Free Software Foundation, Inc.
L cense GPLv3+: GNU GPL versione 3 o successivahttp://gnu.org/licenses/gpl.html>
Questo è un software gratuito: sei libero di modificarlo e ridistribuirlo.
NON VI È ALCUNA GARANZIA, nella misura consentita dalla legge. Digitare "mostra copia"
e "mostra garanzia" per i dettagli.
Questo GDB è stato configurato come "x86_64-linux-gnu"...
(gdb) r --basedir=`pwd`
Avvio del programma: <..>/build/debug/utils/test-runner --basedir=`pwd`
[Debug dei thread tramite libthread_db abilitato]
asserzione fallita. file=../src/core/model/type-id.cc, riga=138, cond="uid <= m_information.size () && uid != 0"
...
Ecco un altro esempio di come utilizzare valgrind per risolvere un problema di memoria come:
VALGR: TestSuite devices-mesh-dot11s-regression
$ ./waf --command-template="valgrind %s --basedir=`pwd` --suite=devices-mesh-dot11s-regression" --run test-runner
Classe TestRunner
Gli eseguibili che eseguono programmi di test dedicati utilizzano una classe TestRunner. Questa classe
prevede la registrazione e l'elenco automatico dei test, nonché un modo per eseguirli
test individuali. Le suite di test individuali utilizzano costruttori globali C++ per aggiungersi a
una raccolta di suite di test gestite dal test runner. Il test runner viene utilizzato per elencare
tutti i test disponibili e selezionare un test da eseguire. Questa è una classe piuttosto semplice
che fornisce tre metodi statici per fornire o aggiungere e ottenere suite di test a un
raccolta di test. Vedi il doxygen per la classe ns3::TestRunner per i dettagli.
Test Suite
Tutti NS-3 I test sono classificati in Test Suite e Test Case. Una test suite è un
raccolta di casi di test che esercitano completamente un dato tipo di funzionalità. Come
descritto sopra, le suite di test possono essere classificate come,
· Test di verifica della build
· Test unitari
· Test di sistema
· Esempi
· Test delle prestazioni
Questa classificazione viene esportata dalla classe TestSuite. Questa classe è piuttosto semplice,
esistente solo come luogo per esportare questo tipo e accumulare casi di test. Da un utente
prospettiva, per creare una nuova TestSuite nel sistema è sufficiente definire un nuovo
classe che eredita dalla classe Suite di prova e svolgere questi due compiti.
Il codice seguente definirà una nuova classe che può essere eseguita da prova.py come test ''unitario''
con il nome visualizzato, nome-della-mia-suite-di-test.
classe MySuite : TestSuite pubblica
{
pubblico:
MyTestSuite ();
};
MyTestSuite::MyTestSuite ()
: TestSuite ("nome-della-mia-suite-di-test", UNITÀ)
{
AddTestCase (nuovo MyTestCase);
}
MyTestSuite myTestSuite;
La classe base si occupa di tutte le registrazioni e le segnalazioni necessarie per essere un buon
cittadino nel quadro di prova.
Test Custodie
I singoli test vengono creati utilizzando una classe TestCase. Modelli comuni per l'utilizzo di un test
caso includono "un caso di test per funzionalità" e "un caso di test per metodo". Le miscele di
questi modelli possono essere utilizzati.
Per creare un nuovo caso di test nel sistema, tutto ciò che si deve fare è ereditare dal
Caso di prova classe base, sovrascrivere il costruttore per dare un nome al caso di test e sovrascrivere
, il DoCorri metodo per eseguire il test.
classe MyTestCase : TestCase pubblico
{
IlMioCasoDiProva ();
vuoto virtuale DoRun (vuoto);
};
IlMioCasoDiTest::IlMioCasoDiTest ()
: TestCase ("Verifica alcune funzionalità")
{
}
nulla
MyTestCase::DoRun (vuoto)
{
NS_TEST_ASSERT_MSG_EQ (true, true, "Messaggio di errore");
}
Elettricita, Gas Ed Acqua
Ci sono una serie di utilità di vario genere che fanno anche parte del test
framework. Gli esempi includono un file pcap generalizzato utile per memorizzare vettori di test; un
contenitore generico utile per l'archiviazione temporanea dei vettori di test durante l'esecuzione del test; e
strumenti per generare presentazioni basate sui risultati dei test di convalida e verifica.
Queste utilità non sono documentate qui, ma ad esempio, vedere come vengono eseguiti i test TCP
trovato in origine/test/ns3tcp/ utilizzare file pcap e output di riferimento.
Come a scrivere test
Un obiettivo primario del progetto ns-3 è quello di aiutare gli utenti a migliorare la validità e
credibilità dei loro risultati. Ci sono molti elementi per ottenere modelli validi e
simulazioni e test sono una componente importante. Se contribuisci con modelli o esempi a
ns-3, potrebbe esserti chiesto di contribuire al codice di test. I modelli che contribuirai saranno utilizzati
per molti anni da altre persone, che probabilmente non hanno idea a prima vista se il
il modello è corretto. Il codice di test che scrivi per il tuo modello ti aiuterà a evitare futuri
regressioni nell'output e aiuteranno i futuri utenti a comprendere la verifica e
limiti di applicabilità dei tuoi modelli.
Esistono molti modi per verificare la correttezza dell'implementazione di un modello. In questo
sezione, speriamo di coprire alcuni casi comuni che possono essere utilizzati come guida per scrivere nuovi
test.
Campione Suite di prova scheletro
Quando si parte da zero (ovvero senza aggiungere un TestCase a una TestSuite esistente), questi
le cose devono essere decise in anticipo:
· Come verrà chiamata la suite di test
· Che tipo di test sarà (test di verifica della build, test unitario, test di sistema o
Test delle prestazioni)
· Dove risiederà il codice di prova (in un modulo ns-3 esistente o separatamente in
directory src/test/). Dovrai modificare il file wscript in quella directory per
compila il nuovo codice, se si tratta di un nuovo file.
Un programma chiamato src/crea-modulo.py è un buon punto di partenza. Questo programma può essere
invocato come crea-modulo.py router per un ipotetico nuovo modulo chiamato router. Una volta
se fai questo, vedrai un router directory e una test/router-test-suite.cc suite di prova.
Questo file può essere un punto di partenza per il test iniziale. Questa è una suite di test funzionante,
sebbene i test effettivamente eseguiti siano banali. Copialo nel test del tuo modulo
directory ed effettuare una sostituzione globale di "Router" in quel file per qualcosa di pertinente
al modello che vuoi testare. Puoi anche modificare cose come un testo più descrittivo
nome del caso di prova.
Devi anche aggiungere un blocco nel tuo wscript per compilare questo test:
modulo_test.source = [
'test/router-test-suite.cc',
]
Prima di iniziare a fare cose utili, potrebbe essere utile provare a eseguire il
scheletro. Assicurarsi che ns-3 sia stato configurato con l'opzione "--enable-tests".
Supponiamo che la tua nuova suite di test si chiami "router", come in questo caso:
RouterTestSuite::RouterTestSuite ()
: TestSuite ("router", UNITÀ)
Prova questo comando:
$ ./test.py -s router
Dovrebbe essere prodotto un output come quello riportato di seguito:
PASS: router TestSuite
1 test su 1 superato (1 superato, 0 saltati, 0 falliti, 0 bloccati, 0 errori valgrind)
Per un esempio pratico, vedere src/lte/test/test-lte-antenna.cc.
Test macro
Sono disponibili numerose macro per controllare l'output del programma di test con i valori previsti
output. Queste macro sono definite in src/core/model/test.h.
Il set principale di macro utilizzate include quanto segue:
NS_TEST_ASSERT_MSG_EQ(effettivo, limite, msg)
NS_TEST_ASSERT_MSG_NE(effettivo, limite, msg)
NS_TEST_ASSERT_MSG_LT(effettivo, limite, msg)
NS_TEST_ASSERT_MSG_GT(effettivo, limite, msg)
NS_TEST_ASSERT_MSG_EQ_TOL(effettivo, limite, toll, msg)
Il primo argomento presenti è il valore in prova, il secondo valore limitare è l'atteso
valore (o il valore da testare) e l'ultimo argomento msg è il messaggio di errore a
stampare se il test fallisce.
I primi quattro macro test sopra per uguaglianza, disuguaglianza, minore o maggiore di,
rispettivamente. La quinta macro sopra verifica l'uguaglianza, ma entro una certa tolleranza.
Questa variante è utile quando si testano i numeri in virgola mobile per verificarne l'uguaglianza rispetto a un limite,
quando si desidera evitare che il test fallisca a causa di errori di arrotondamento.
Infine, ci sono varianti di quanto sopra in cui la parola chiave AFFERMARE è sostituito da ASPETTARSI.
Queste varianti sono progettate appositamente per l'uso in metodi (in particolare callback) che restituiscono
void. Riserva il loro utilizzo per le callback che usi nei tuoi programmi di test; altrimenti, usa
, il AFFERMARE varianti.
Come a aggiungere an esempio Programma a , il test suite
Si può "testare" che gli esempi vengano compilati ed eseguiti correttamente fino al completamento (senza
perdite di memoria) utilizzando il esempi-da-eseguire.py script situato nella directory di test del modulo.
In breve, includendo un'istanza di questo file nella directory di test, è possibile causare il
test runner per eseguire gli esempi elencati. Di solito è meglio assicurarsi di
selezionare esempi che abbiano tempi di esecuzione ragionevolmente brevi in modo da non appesantire i test. Vedere
l'esempio in src/lte/test/ directory.
Testing da booleano risultati
Testing risultati quando casualità is coinvolto
Testing produzione dati contro di a conosciuto distribuzione
Fornitura non banale ingresso vettori of dati
Memorizzazione e riferimento non banale produzione dati
presentando il tuo produzione test dati
Assistenza
Creazione a nuovi NS-3 modello
Questo capitolo illustra il processo di progettazione di un NS-3 modello. In molti casi di ricerca,
gli utenti non si accontenteranno di adattare semplicemente i modelli esistenti, ma potrebbero voler estendere
nucleo del simulatore in un modo nuovo. Useremo l'esempio dell'aggiunta di un ErrorModel a un
semplice NS-3 link come esempio motivante di come si potrebbe affrontare questo problema e
procedere attraverso una progettazione e implementazione.
NOTA:
Documentazione
Qui ci concentriamo sul processo di creazione di nuovi modelli e nuovi moduli, e alcuni dei
scelte progettuali coinvolte. Per motivi di chiarezza, rimandiamo la discussione della meccanica
di documentazione dei modelli e del codice sorgente per il Documentazione capitolo.
Progettazione Approccio
Considera come vuoi che funzioni; cosa dovrebbe fare. Pensa a questi aspetti:
· funzionalità: Quali funzionalità dovrebbe avere? Quali attributi o configurazioni sono
esposto all'utente?
· riutilizzabilità: Quanto dovrebbero essere in grado gli altri di riutilizzare il mio progetto? Posso riutilizzare il codice da
NS-2 per iniziare? Come fa un utente a integrare il modello con il resto di un altro
simulazione?
· dipendenze: Come posso ridurre l'introduzione di dipendenze esterne nel mio nuovo codice?
il più possibile (per renderlo più modulare)? Ad esempio, dovrei evitare qualsiasi
dipendenza da IPv4 se voglio che venga utilizzato anche da IPv6? Dovrei evitare qualsiasi dipendenza
su IP?
Non esitate a contattare il ns-3-utenti or ns-sviluppatori elenco se hai domande.
In particolare, è importante pensare all'API pubblica del tuo nuovo modello e chiedere
feedback. È anche utile far conoscere il tuo lavoro agli altri nel caso in cui fossi interessato
collaboratori.
Esempio: ErrorModel
Esiste un modello di errore in NS-2. Consente di passare i pacchetti a un oggetto con stato che
determina, in base a una variabile casuale, se il pacchetto è corrotto. Il chiamante può
quindi decidere cosa fare con il pacchetto (buttarlo via, ecc.).
L'API principale del modello di errore è una funzione a cui passare un pacchetto e il valore di ritorno di
questa funzione è un valore booleano che indica al chiamante se si è verificato un danneggiamento. Nota
che a seconda del modello di errore, il buffer dei dati del pacchetto potrebbe essere danneggiato o meno.
Chiamiamo questa funzione "IsCorrupt()".
Finora, nel nostro progetto abbiamo:
classe ErrorModel
{
pubblico:
/ **
* \restituisce true se il pacchetto deve essere considerato come errato/corrotto
* \param pkt Pacchetto a cui applicare il modello di errore
*/
bool IsCorrupt (Ptr pacchetto);
};
Si noti che non passiamo un puntatore const, consentendo così alla funzione di modificare il
pacchetto se IsCorrupt() restituisce true. Non tutti i modelli di errore modificheranno effettivamente il pacchetto;
è necessario documentare se il buffer dei dati del pacchetto è danneggiato o meno.
Potremmo anche volere versioni specializzate di questo, come in NS-2, quindi anche se non è il
unica scelta progettuale per il polimorfismo, assumiamo che sottoclasseremo una classe base
ErrorModel per classi specializzate, come RateErrorModel, ListErrorModel, ecc., come
è fatto in NS-2.
A questo punto potresti pensare: "Perché non rendere IsCorrupt() un metodo virtuale?". Cioè
un approccio; l'altro è quello di rendere la funzione pubblica non virtuale indiretta attraverso un
funzione virtuale privata (in C++ è nota come idioma di interfaccia non virtuale ed è
adottato nel NS-3 Classe ErrorModel).
Quindi, questo dispositivo dovrebbe avere dipendenze da IP o altri protocolli? Non vogliamo
per creare dipendenze sui protocolli Internet (il modello di errore dovrebbe essere applicabile a
anche protocolli non Internet), quindi lo terremo a mente più avanti.
Un'altra considerazione è come gli oggetti includeranno questo modello di errore. Immaginiamo di mettere
un setter esplicito in alcune implementazioni di NetDevice, ad esempio:
/ **
* Allega un ErrorModel di ricezione a PointToPointNetDevice.
*
* PointToPointNetDevice può facoltativamente includere un ErrorModel in
* la catena di ricezione dei pacchetti.
*
* @vedi ErrorModel
* @param em Ptr su ErrorModel.
*/
void PointToPointNetDevice::SetReceiveErrorModel(Ptr em);
Di nuovo, questa non è l'unica scelta che abbiamo (i modelli di errore potrebbero essere aggregati a molti
altri oggetti), ma soddisfa il nostro caso d'uso primario, che è quello di consentire a un utente di forzare
errori su trasmissioni di pacchetti altrimenti riuscite, a livello di NetDevice.
Dopo aver riflettuto e osservato le soluzioni esistenti NS-2 codice, ecco un esempio di API di una base
classe e prima sottoclasse che potrebbero essere pubblicate per la revisione iniziale:
classe ErrorModel
{
pubblico:
Modello di errore ();
virtuale ~ErrorModel ();
bool IsCorrupt (Ptr pacchetto);
void Reimposta (void);
void Abilita (void);
void Disabilita (void);
bool IsEnabled (void) const;
privato:
bool virtuale DoCorrupt (Ptr pacchetto) = 0;
vuoto virtuale DoReset (void) = 0;
};
enum ErrorUnit
{
EU_BIT,
EU_BYTE,
EU_PKT
};
// Determina quali pacchetti sono in errore in corrispondenza di un sottostante
// distribuzione di variabili casuali, tasso di errore e unità per il tasso.
classe RateErrorModel : ErrorModel pubblico
{
pubblico:
ModelloErroreTariffa();
virtuale ~RateErrorModel ();
enum ErrorUnit GetUnit (void) const;
void SetUnit (enum ErrorUnit error_unit);
double GetRate (void) const;
void SetRate (tasso doppio);
void SetRandomVariable (const RandomVariable &ranvar);
privato:
bool virtuale DoCorrupt (Ptr pacchetto);
vuoto virtuale DoReset (vuoto);
};
impalcatura
Supponiamo che tu sia pronto per iniziare l'implementazione; hai un quadro abbastanza chiaro di
cosa vuoi costruire e potresti aver richiesto una revisione iniziale o dei suggerimenti da
l'elenco. Un modo per affrontare il passaggio successivo (implementazione) è quello di creare impalcature e
aggiungere i dettagli man mano che il progetto matura.
Questa sezione illustra molti dei passaggi che dovresti considerare per definire l'impalcatura, o
uno scheletro non funzionale di ciò che il tuo modello alla fine implementerà. Di solito è buono
pratica per non aspettare di integrare questi dettagli alla fine, ma invece di scandagliare un
scheletro del tuo modello nel sistema in anticipo e poi aggiungi funzioni in seguito una volta che l'API e
l'integrazione sembra giusta.
Tieni presente che dovrai modificare alcune cose nella presentazione sottostante per il tuo modello
poiché se segui alla lettera il modello di errore, il codice che produci entrerà in conflitto con il
modello di errore esistente. Di seguito è riportato solo uno schema di come è stato costruito ErrorModel che puoi
può adattarsi ad altri modelli.
Review , il NS-3 codifica Style funzionalità di
A questo punto, potresti voler fare una pausa e leggere il NS-3 documento di stile di codifica, in particolare
se stai pensando di contribuire con il tuo codice al progetto. Lo stile di codifica
il documento è collegato alla pagina principale del progetto: NS-3 codifica stile.
Decide Dove in , il Fonte Albero , il Modello Qualora Risiedere
Tutto il NS-3 il codice sorgente del modello si trova nella directory src /Dovrai scegliere quale
sottodirectory in cui risiede. Se si tratta di un nuovo codice modello di qualche tipo, ha senso metterlo
nella src / directory da qualche parte, in particolare per facilitare l'integrazione con la build
.
Nel caso del modello di errore, è strettamente correlato alla classe del pacchetto, quindi ha senso
per implementare questo nel src/rete/ modulo dove NS-3 i pacchetti sono implementati.
waf e WScript
NS-3 utilizza l' Waf sistema di compilazione. Vorrai integrare il tuo nuovo NS-3 usa il Waf
sistema di compilazione. Vorrai integrare i tuoi nuovi file sorgente in questo sistema. Questo
richiede che tu aggiunga i tuoi file al WScript file trovato in ogni directory.
Iniziamo con i file vuoti error-model.h e error-model.cc e aggiungiamo questo a
src/rete/wscript. Si tratta semplicemente di aggiungere il file .cc al resto del
file sorgente e il file .h all'elenco dei file di intestazione.
Ora, vai alla directory di livello superiore e digita "./test.py". Non dovresti aver interrotto
nulla tramite questa operazione.
Includere Guardie ✔
Ora aggiungiamo un po' includere guardie nel nostro file di intestazione:
#ifndef ERROR_MODEL_H
#define ERROR_MODEL_H
...
#endif
namespace ns3
NS-3 utilizza l' NS-3 namespace per isolare i suoi simboli da altri namespace. In genere, un
l'utente inserirà quindi un NS-3 blocco namespace sia nel file cc che h.:
spazio dei nomi ns3 {
...
}
A questo punto abbiamo alcuni file scheletrici in cui possiamo iniziare a definire le nostre nuove classi.
Il file di intestazione si presenta così:
#ifndef ERROR_MODEL_H
#define ERROR_MODEL_H
spazio dei nomi ns3 {
} // spazio dei nomi ns3
#endif
mentre error-model.cc il file appare semplicemente così:
#include "error-model.h"
spazio dei nomi ns3 {
} // spazio dei nomi ns3
Questi file dovrebbero compilarsi poiché non hanno alcun contenuto. Ora siamo pronti per
inizia ad aggiungere classi.
Iniziale Implementazione/Attuazione
A questo punto, stiamo ancora lavorando su alcune impalcature, ma possiamo iniziare a definire il nostro
classi, con funzionalità che verranno aggiunte in seguito.
Ereditare da , il Oggetto Classe?
Questo è un passaggio di progettazione importante; se utilizzare la classe Oggetto come classe base per il tuo nuovo
classi.
Come descritto nel capitolo sulla NS-3 Modello di oggetto, classi che ereditano dalla classe
Oggetto ottenere proprietà speciali:
· il NS-3 sistema di tipi e attributi (vedi Attributi)
· un sistema di aggregazione di oggetti
· un sistema di conteggio dei riferimenti a puntatore intelligente (classe Ptr)
Classi che derivano dalla classe BaseOggetto} ottenere le prime due proprietà sopra, ma non
ottenere puntatori intelligenti. Classi che derivano dalla classe RefCountBase ottieni solo lo smart-pointer
sistema di conteggio dei riferimenti.
In pratica, la classe Oggetto è la variante delle tre sopra che il NS-3 lo sviluppatore lo farà
che si incontrano più comunemente.
Nel nostro caso, vogliamo utilizzare il sistema di attributi e passeremo istanze
di questo oggetto attraverso il NS-3 API pubblica, quindi classe Oggetto è appropriato per noi.
Iniziale Classi
Un modo per procedere è iniziare definendo le funzioni minime e vedere se lo faranno
compilare. Vediamo cosa è necessario implementare quando si deriva dalla classe Object.:
#ifndef ERROR_MODEL_H
#define ERROR_MODEL_H
#include "ns3/object.h"
spazio dei nomi ns3 {
classe ErrorModel : oggetto pubblico
{
pubblico:
TypeId statico GetTypeId (vuoto);
Modello di errore ();
virtuale ~ErrorModel ();
};
classe RateErrorModel : ErrorModel pubblico
{
pubblico:
TypeId statico GetTypeId (vuoto);
ModelloErroreTariffa();
virtuale ~RateErrorModel ();
};
#endif
Alcune cose da notare qui. Dobbiamo includere oggetto.hLa convenzione in NS-3 è che se
il file di intestazione è collocato nella stessa directory, può essere incluso senza alcun percorso
prefisso. Pertanto, se stessimo implementando ErrorModel in src/core/modello directory, noi
avrebbe potuto semplicemente dire "#includere "oggetto.h"". Ma siamo dentro src/network/model, quindi dobbiamo
includerlo come "#includere "ns3/object.h"". Nota anche che questo va oltre lo spazio dei nomi
dichiarazione.
In secondo luogo, ogni classe deve implementare una funzione membro pubblica statica chiamata Ottieni IDTipo (vuoto).
In terzo luogo, è una buona idea implementare costruttori e distruttori piuttosto che lasciare che
il compilatore li genera e rende virtuale il distruttore. In C++, nota anche che la copia
l'operatore di assegnazione e i costruttori di copia vengono generati automaticamente se non sono definiti, quindi
se non li vuoi, dovresti implementarli come membri privati. Questo aspetto di
Il linguaggio C++ è trattato nel libro Effective C++ di Scott Meyers, punto 45.
Diamo ora un'occhiata al codice di implementazione scheletrica corrispondente nel file .cc:
#include "error-model.h"
spazio dei nomi ns3 {
NS_OBJECT_ENSURE_REGISTERED (ErrorModel);
TypeId ErrorModel::GetTypeId (void)
{
TypeId statico tid = TypeId ("ns3::ErrorModel")
.ImpostaParente ()
;
ritorno a mare;
}
ErrorModel::ErrorModel ()
{
}
ErrorModel::~ErrorModel ()
{
}
NS_OBJECT_ENSURE_REGISTERED (RateErrorModel);
TypeId RateErrorModel::GetTypeId (vuoto)
{
TypeId statico tid = TypeId ("ns3::RateErrorModel")
.ImpostaParente ()
.AddConstructor ()
;
ritorno a mare;
}
RateErrorModel::RateErrorModel ()
{
}
RateErrorModel::~RateErrorModel ()
{
}
Qual è la Ottieni IDTipo (vuoto) funzione? Questa funzione fa alcune cose. Registra un
stringa univoca nel sistema TypeId. Stabilisce la gerarchia degli oggetti nel
sistema di attributi (tramite Imposta genitore). Dichiara inoltre che alcuni oggetti possono essere creati tramite
il framework di creazione degli oggetti (Aggiungi Costruttore).
La macro NS_OBJECT_ENSURE_REGISTERED (nome della classe) è necessario anche una volta per ogni classe che
definisce un nuovo metodo GetTypeId e si occupa della registrazione effettiva della classe in
sistema. Il capitolo sul modello a oggetti ne parla più in dettaglio.
Compreso Esterno File
Registrazione Assistenza
Qui, scrivere a bit informazioni l'aggiunta di |ns3| registrazione macro. Note: che LOG_COMPONENT_DEFINE is
fatto al di fuori , il namespace ns3
Costruttore, Vuoto Funzione prototipi
Le Variabili (Predefinito Valori, Attributi)
Test Programma 1
Oggetto Contesto
Aggiunta a Campione Copione
A questo punto, si potrebbe provare a prendere l'impalcatura di base definita sopra e aggiungerla
nel sistema. L'esecuzione di questo passaggio consente ora di utilizzare un modello più semplice durante l'impianto idraulico
nel sistema e può anche rivelare se è necessario apportare modifiche al design o all'API
fatto. Una volta fatto questo, torneremo a sviluppare la funzionalità del
ErrorModels stessi.
Aggiungi Basic Assistenza in , il Classe
/* dispositivo-di-rete-punto-a-punto.h */
classe ErrorModel;
/ **
* Modello di errore per gli eventi di ricezione dei pacchetti
*/
Ptr m_receiveErrorModel;
Aggiungi Accessorio
nulla
PointToPointNetDevice::SetReceiveErrorModel (Ptr em)
{
NS_LOG_FUNCTION (questo << em);
m_receiveErrorModel = em;
}
.AddAttribute ("ReceiveErrorModel",
"Il modello di errore del ricevitore utilizzato per simulare la perdita di pacchetti",
ValorePuntatore (),
MakePointerAccessor (&PointToPointNetDevice::m_receiveErrorModel),
MakePointerChecker ())
Filo a piombo In , il Sistema
void PointToPointNetDevice::Receive (Ptr pacchetto)
{
NS_LOG_FUNCTION (questo << pacchetto);
protocollo uint16_t = 0;
se (m_receiveErrorModel && m_receiveErrorModel->IsCorrupt (pacchetto) )
{
//
// Se abbiamo un modello di errore e indica che è il momento di perdere un
// pacchetto corrotto, non inoltrare questo pacchetto, lascialo andare.
//
m_dropTrace (pacchetto);
}
altro
{
//
// Premi il gancio di tracciamento della ricezione, rimuovi l'intestazione del protocollo punto a punto
// e inoltra questo pacchetto nello stack del protocollo.
//
m_rxTrace (pacchetto);
ProcessHeader(pacchetto, protocollo);
m_rxCallback (questo, pacchetto, protocollo, GetRemote ());
se (!m_promiscCallback.IsNull ())
{ m_promiscCallback (questo, pacchetto, protocollo, GetRemote (),
OttieniIndirizzo (), NetDevice::PACKET_HOST);
}
}
}
Creare Nullo Cookie di funzionalità Copione
/* simple-error-model.cc */
// Modello di errore
// Vogliamo aggiungere un modello di errore al NetDevice del nodo 3
// Possiamo ottenere un handle per NetDevice tramite il canale e il nodo
// puntatori
Ptr nd3 = PointToPointTopology::GetNetDevice
(n3, canale2);
Ptr em = Crea ();
nd3->SetReceiveErrorModel (em);
bool
ErrorModel::DoCorrupt (Packet& p)
{
NS_LOG_FUNCTION;
NS_LOG_UNCOND("Danneggiato!");
return false;
}
A questo punto, possiamo eseguire il programma con il nostro banale ErrorModel inserito nella ricezione
percorso del PointToPointNetDevice. Stampa la stringa "Corrupt!" per ogni pacchetto
ricevuto al nodo n3. Successivamente, torniamo al modello di errore per aggiungere una sottoclasse che esegue
modellazione degli errori più interessante.
Aggiungi a sottoclasse
La banale classe base ErrorModel non fa nulla di interessante, ma fornisce un
utile interfaccia di classe base (Corrupt() e Reset()), inoltrata alle funzioni virtuali che
può essere sottoclassato. Consideriamo ora quello che chiamiamo un BasicErrorModel che si basa su
, il NS-2 Classe ErrorModel (in ns-2/queue/errmodel.{cc,h}).
Quali proprietà vogliamo che abbia, dal punto di vista dell'interfaccia utente? Vorremmo
affinché l'utente possa scambiare in modo banale il tipo di ErrorModel utilizzato in
NetDevice. Vorremmo anche la possibilità di impostare parametri configurabili.
Ecco alcuni semplici requisiti che prenderemo in considerazione:
· Possibilità di impostare la variabile casuale che regola le perdite (l'impostazione predefinita è UniformVariable)
· Possibilità di impostare l'unità (bit, byte, pacchetto, tempo) di granularità su cui vengono rilevati gli errori
applicato.
· Possibilità di impostare il tasso di errori (ad esempio 10^-3) corrispondente all'unità di misura sopra indicata
granularità.
· Possibilità di abilitare/disabilitare (l'impostazione predefinita è abilitata)
Come a sottoclasse
Dichiariamo BasicErrorModel come sottoclasse di ErrorModel come segue:
classe BasicErrorModel : pubblico ErrorModel
{
pubblico:
TypeId statico GetTypeId (vuoto);
...
privato:
// Implementa funzioni virtuali pure della classe base
bool virtuale DoCorrupt (Ptr P);
bool virtuale DoReset (vuoto);
...
}
e configurare la funzione GetTypeId della sottoclasse impostando una stringa TypeId univoca e
impostando il Parent su ErrorModel:
TypeId RateErrorModel::GetTypeId (vuoto)
{
TypeId statico tid = TypeId ("ns3::RateErrorModel")
.ImpostaParente ()
.AddConstructor ()
...
Silhouette Nucleo funzioni e Unità Test
Affermare Macro
scrittura Unità Test
Aggiunta a New Moduli a NS-3
Dopo aver creato un gruppo di classi, esempi e test correlati, questi possono essere
combinati insieme in un NS-3 modulo in modo che possano essere utilizzati con quelli esistenti NS-3 moduli
e da altri ricercatori.
Questo capitolo ti guida attraverso i passaggi necessari per aggiungere un nuovo modulo a NS-3.
step 0 - Moduli disposizione
Tutti i moduli possono essere trovati nel src directory. Ogni modulo può essere trovato in una directory
che ha lo stesso nome del modulo. Ad esempio, il spettro il modulo può essere trovato qui:
src/spettroCiteremo dal spettro modulo per l'illustrazione.
Un modulo prototipico ha la seguente struttura di directory e i file richiesti:
src /
nome-modulo/
legature/
documento/
esempi /
WScript
aiutante/
modello/
test/
esempi-da-eseguire.py
WScript
Non tutte le directory saranno presenti in ogni modulo.
step 1 - Creare a Moduli Scheletro
Nella directory sorgente è fornito un programma Python che creerà uno scheletro per un nuovo
modulo. Ai fini di questa discussione, presumeremo che il tuo nuovo modulo si chiami
nuovo modulo. Dal src directory, procedere come segue per creare il nuovo modulo:
$ ./create-module.py nuovo-modulo
Il prossimo, cd ai miglioramenti nuovo modulo; troverai questa disposizione delle directory:
$ cd nuovo-modulo
$ l
esempi di documenti modello di supporto test wscript
Più in dettaglio, il crea-modulo.py lo script creerà le directory e anche quelle iniziali
scheletro WScript, .h, . Cc e .primo file. Il modulo completo con i file scheletro sembra
come questo:
src /
nuovo-modulo/
documento/
nuovo-modulo.rst
esempi /
nuovo-modulo-esempio.cc
WScript
aiutante/
nuovo-modulo-helper.cc
nuovo-modulo-helper.h
modello/
nuovo-modulo.cc
nuovo-modulo.h
test/
new-module-test-suite.cc
WScript
(Se richiesto il legature/ directory elencata in Passo 0 verrà creato automaticamente durante
la costruzione.)
Ora vedremo come personalizzare questo modulo. Informando waf sui file che
la creazione del modulo avviene modificando i due WScript file. Passeremo in rassegna i
passaggi principali di questo capitolo.
Tutti NS-3 i moduli dipendono da core modulo e di solito su altri moduli. Questa dipendenza
è specificato nel WScript file (al livello superiore del modulo, non separato WScript
file nella Esempi directory!). Nello scheletro WScript la chiamata che dichiarerà il tuo
nuovo modulo per waf apparirà così (prima della modifica):
def build(bld):
modulo = bld.create_ns3_module('nuovo-modulo', ['core'])
Supponiamo che nuovo modulo dipende dal Internet, mobilitàe aodv moduli. Dopo
modificandolo WScript il file dovrebbe essere simile a:
def build(bld):
modulo = bld.create_ns3_module('nuovo-modulo', ['internet', 'mobilità', 'aodv'])
Si noti che devono essere elencate solo le dipendenze del modulo di primo livello, motivo per cui abbiamo rimosso
core; il Internet il modulo a sua volta dipende da core.
Il tuo modulo avrà molto probabilmente file sorgente del modello. Scheletri iniziali (che saranno
compilare con successo) vengono creati in modello/nuovo-modulo.cc e modello/nuovo-modulo.h.
Se il tuo modulo avrà file sorgente di supporto, allora andranno in aiutante/
directory; ancora una volta, gli scheletri iniziali vengono creati in quella directory.
Infine, è buona norma scrivere test ed esempi. Questi saranno quasi certamente
necessario affinché i nuovi moduli vengano accettati nell'ufficiale NS-3 albero sorgente. Uno scheletro
la suite di test e il caso di test vengono creati in test/ directory. La suite di test scheletrica sarà
contengono il costruttore sottostante, che dichiara un nuovo test unitario denominato nuovo modulo, Con
singolo caso di test costituito dalla classe NuovoModuloTestCase1:
NewModuleTestSuite::NewModuleTestSuite ()
: TestSuite ("nuovo-modulo", UNITÀ)
{
AddTestCase (nuovo NewModuleTestCase1);
}
step 3 - Dichiarare Fonte File
I file di intestazione pubblica e di codice sorgente per il nuovo modulo devono essere specificati in
WScript file modificandolo con il tuo editor di testo.
Ad esempio, dopo aver dichiarato il spettro modulo, il src/spectrum/wscript specifica il
file del codice sorgente con il seguente elenco:
def build(bld):
modulo = bld.create_ns3_module('spettro', ['internet', 'propagazione', 'antenna', 'applicazioni'])
modulo.sorgente = [
'modello/spettro-modello.cc',
'modello/valore-spettro.cc',
.
.
.
'modello/forno-a-microonde-spettro-valore-helper.cc',
'helper/spectrum-helper.cc',
'helper/adhoc-aloha-noack-ideal-phy-helper.cc',
'helper/waveform-generator-helper.cc',
'helper/analizzatore-di-spettro-helper.cc',
]
Gli oggetti risultanti dalla compilazione di queste sorgenti saranno assemblati in una libreria di collegamento,
che sarà collegato a tutti i programmi che si basano su questo modulo.
Ma come fanno questi programmi ad apprendere l'API pubblica del nostro nuovo modulo? Continua a leggere!
step 4 - Dichiarare Pubblico testata File
Anche i file di intestazione che definiscono l'API pubblica del tuo modello e degli helper dovrebbero essere
specificato in WScript file.
Continuando con il spettro illustrazione del modello, i file di intestazione pubblici sono specificati
con la seguente strofa. (Si noti che l'argomento al bld la funzione dice waf a
installare le intestazioni di questo modulo con le altre NS-3 intestazioni):
intestazioni = bld(features='ns3header')
headers.module = 'spettro'
intestazioni.origine = [
'modello/modello-spettro.h',
'modello/valore-spettro.h',
.
.
.
'modello/forno-a-microonde-spettro-valore-helper.h',
'helper/spectrum-helper.h',
'helper/adhoc-aloha-noack-ideal-phy-helper.h',
'helper/waveform-generator-helper.h',
'helper/analizzatore-di-spettro-helper.h',
]
Le intestazioni rese pubbliche in questo modo saranno accessibili agli utenti del tuo modello con include
affermazioni come
#include "ns3/spectrum-model.h"
Le intestazioni utilizzate esclusivamente internamente nella tua implementazione non dovrebbero essere incluse qui.
sono ancora accessibili alla tua implementazione tramite istruzioni include come
#include "my-module-implementation.h"
step 5 - Dichiarare Test
Se il tuo nuovo modulo ha dei test, allora devono essere specificati nel tuo WScript file per
modificandolo con il tuo editor di testo.
. spettro i test del modello sono specificati con la seguente strofa:
module_test = bld.create_ns3_module_test_library('spettro')
modulo_test.source = [
'test/spectrum-interference-test.cc',
'test/spectrum-value-test.cc',
]
See Test per maggiori informazioni su come scrivere casi di test.
step 6 - Dichiarare Esempi
Se il tuo nuovo modulo ha degli esempi, allora devono essere specificati nel tuo esempi/wscript
file. (Lo scheletro di primo livello WScript includerà ricorsivamente esempi/wscript solo se
(gli esempi sono stati abilitati al momento della configurazione.)
. spettro il modello definisce il suo primo esempio in src/spectrum/examples/wscript con
def build(bld):
obj = bld.create_ns3_program('adhoc-aloha-ideal-phy',
['spettro', 'mobilità'])
obj.source = 'adhoc-aloha-ideal-phy.cc'
Si noti che il secondo argomento della funzione crea_programma_ns3() è l'elenco dei moduli
da cui dipende il programma che si sta creando; ancora una volta, non dimenticare di includere nuovo modulo in
l'elenco. È buona norma elencare solo le dipendenze dirette del modulo e lasciare waf
dedurre l'albero delle dipendenze completo.
Occasionalmente, per chiarezza, potresti voler dividere l'implementazione del tuo esempio tra
diversi file sorgente. In questo caso, includi semplicemente quei file come informazioni esplicite aggiuntive
fonti dell'esempio:
obj = bld.create_ns3_program('nuovo-modulo-esempio', [nuovo-modulo])
obj.source = ['nuovo-modulo-esempio.cc', 'nuovo-modulo-esempio-parte.cc']
Gli esempi Python sono specificati utilizzando la seguente chiamata di funzione. Si noti che il secondo
argomento per la funzione register_ns3_script() è l'elenco dei moduli che Python
l'esempio dipende da:
bld.register_ns3_script('nuovo-modulo-esempio.py', ['nuovo-modulo'])
step 7 - Esempi Correre as Test
Oltre all'esecuzione di codice di test esplicito, il framework di test può anche essere strumentato per
eseguire programmi di esempio completi per cercare di catturare le regressioni negli esempi. Tuttavia, non tutti
gli esempi sono adatti per i test di regressione. Il file test/esempi-da-eseguire.py controlla il
invocazione degli esempi quando viene eseguito il framework di test.
. spettro esempi di modelli gestiti da prova.py sono specificati in
src/spectrum/test/esempi-da-eseguire.py utilizzando i seguenti due elenchi di C++ e Python
esempi:
# Un elenco di esempi C++ da eseguire per garantire che rimangano
# costruibile ed eseguibile nel tempo. Ogni tupla nell'elenco contiene
#
# (example_name, do_run, do_valgrind_run).
#
# Per maggiori informazioni, vedere test.py.
cpp_esempi = [
("adhoc-aloha-ideal-phy", "Vero", "Vero"),
("adhoc-aloha-ideal-phy-with-microwave-oven", "Vero", "Vero"),
("modello di perdita di propagazione della matrice fisica ideale-aloha ad hoc", "Vero", "Vero"),
]
# Un elenco di esempi Python da eseguire per garantire che rimangano
# eseguibile nel tempo. Ogni tupla nell'elenco contiene
#
# (nome_esempio, esegui_esecuzione).
#
# Per maggiori informazioni, vedere test.py.
esempi_python = [
("sample-simulator.py", "Vero"),
]
Come indicato nel commento, ogni voce nell'elenco C++ degli esempi da eseguire contiene
tupla (nome_esempio, esegui, do_valgrind_run)Durante la serata,
· nome_esempio è l'eseguibile da eseguire,
· esegui è una condizione in base alla quale eseguire l'esempio, e
· fai_valgrind_run è una condizione in base alla quale eseguire l'esempio in valgrind. (Questo
è necessario perché NSC provoca arresti anomali delle istruzioni illegali con alcuni test quando
vengono eseguiti con valgrind.)
Si noti che le due condizioni sono istruzioni Python che possono dipendere da waf configurazione
variabili. Ad esempio,
("tcp-nsc-lfn", "NSC_ENABLED == Vero", "NSC_ENABLED == Falso"),
Ogni voce nell'elenco Python degli esempi da eseguire contiene la tupla (nome_esempio,
esegui), dove, come per gli esempi C++,
· nome_esempio è lo script Python da eseguire e
· esegui è una condizione in base alla quale eseguire l'esempio.
Di nuovo, la condizione è un'istruzione Python che può dipendere da waf variabili di configurazione.
Per esempio,
("realtime-udp-echo.py", "ENABLE_REAL_TIME == False"),
step 8 - Configurazione e Silhouette
Ora puoi configurare, compilare e testare il tuo modulo normalmente. Devi riconfigurare il
progetto come primo passo affinché waf memorizza nella cache le nuove informazioni nella tua WScript file o
altrimenti il nuovo modulo non verrà incluso nella build.
$ ./waf configure --enable-examples --enable-tests
$ ./waf build
$ ./prova.py
Cerca la suite di test del tuo nuovo modulo (e i programmi di esempio, se il tuo modulo ne ha uno
abilitato) nell'output del test.
step 9 - Python Associazioni
L'aggiunta di binding Python al modulo è facoltativa e il passaggio è commentato da
predefinito nel crea-modulo.py script.
# bld.ns3_python_bindings()
Se vuoi includere i binding Python (necessari solo se vuoi scrivere Python ns-3
programmi invece dei programmi C++ ns-3), dovresti rimuovere il commento sopra e installare
Sistema di scansione dell'API Python (trattato altrove in questo manuale) ed esegui la scansione del tuo modulo per
generare nuovi binding.
Creazione Documentazione
NS-3 fornisce due tipi di documentazione: capitoli espositivi in stile "guida utente" e
documentazione API del codice sorgente.
I capitoli della "guida utente" sono scritti a mano in testoristrutturato formato (.primo), che è
elaborato dal sistema di documentazione Python Sfinge per generare pagine web e file PDF.
La documentazione API viene generata dal codice sorgente stesso, utilizzando Doxygen, generare
pagine web collegate tra loro. Entrambe sono importanti: i capitoli della Sfinge spiegano il perché
e panoramica dell'utilizzo di un modello; la documentazione API spiega il come dettagli.
Questo capitolo fornisce una rapida panoramica di questi strumenti, sottolineando l'uso preferito e
personalizzazioni per NS-3.
Per creare tutta la documentazione standard:
$ ./waf docs
Per opzioni più specializzate, continua a leggere.
Documentare con Sfinge
Usiamo Sfinge per generare capitoli espositivi che descrivono la progettazione e l'utilizzo di ciascuno
modulo. In questo momento stai leggendo il Documentazione Capitolo. Il Mostra Fonte anello della
la barra laterale mostrerà il codice sorgente reStructuredText per questo capitolo.
Aggiunta New capitoli
L'aggiunta di un nuovo capitolo richiede tre passaggi (descritti più dettagliatamente di seguito):
1. Scegliere Dove? i file di documentazione saranno attivi.
2. Link da una pagina esistente alla nuova documentazione.
3. Aggiungere il nuovo file al Makefile.
Dove?
Documentazione per un modulo specifico, foo, dovrebbe normalmente entrare src/foo/doc/. Per esempio
src/foo/doc/foo.rst sarebbe il documento di primo livello per il modulo. Il
src/crea-modulo.py lo script creerà questo file per te.
Alcuni modelli richiedono diversi .primo file e figure; tutto questo dovrebbe andare nel
src/foo/doc/ directory. La documentazione è in realtà compilata da un Makefile Sphinx. Per particolare
documentazione coinvolta, potrebbe essere utile avere un locale Makefile nella src/foo/doc/
directory per semplificare la creazione della documentazione per questo modulo (Antenna è un esempio).
L'impostazione non è particolarmente difficile, ma va oltre lo scopo di questo capitolo.
In alcuni casi, la documentazione si estende su più modelli; Network capitolo è un esempio. In
questi casi aggiungendo il .primo file direttamente a doc/modelli/sorgente/ potrebbe essere appropriato.
Link
La Sfinge deve sapere where dovrebbe apparire il tuo nuovo capitolo. Nella maggior parte dei casi, un nuovo modello
il capitolo dovrebbe apparire nel Modelli libro. Per aggiungere il tuo capitolo lì, modifica
doc/models/source/index.rst
.. toctree::
:maxdepth: 1
organizzazione
animazione
antenna
aodv
applicazioni
...
Aggiungi il nome del tuo documento (senza il .primo estensione) a questo elenco. Si prega di conservare
Capitoli modello in ordine alfabetico, per facilitare la scansione visiva di capitoli specifici.
Makefile
Devi anche aggiungere il tuo documento all'appropriato Makefile, Così make sa come controllarlo
per gli aggiornamenti. Il Makefile del libro Models è doc/modelli/Makefile, il Makefile del libro del manuale è
doc/manuale/Makefile.
# elenca tutti i file .rst della libreria modello che devono essere copiati in $SOURCETEMP
FONTI = \
sorgente/conf.py \
sorgente/_statico \
sorgente/indice.rst \
sorgente/sostituisci.txt \
origine/organizzazione.rst \
...
$(SRC)/antenna/doc/source/antenna.rst \
...
Aggiungi il tuo .primo file al FONTI variabile. Per aggiungere cifre, leggere i commenti nella
Makefile per vedere quale variabile dovrebbe contenere i tuoi file immagine. Ancora una volta, tienili
in ordine alfabetico.
Costruzione Sfinge Docs
Creare la documentazione di Sphinx è piuttosto semplice. Per creare tutta la documentazione di Sphinx
documentazione:
$ ./waf sfinge
Per creare solo la documentazione dei modelli:
$ make -C doc/modelli
Per vedere la documentazione generata punta il tuo browser su doc/modelli/build/html.
Come puoi vedere, Sphinx usa Make per guidare il processo. Il target predefinito compila tutto
moduli di output abilitati, che in NS-3 sono le multi-pagina html, pagina singola singolohtmle
pdf (latice). Per creare solo l'html multipagina, aggiungi html bersaglio:
$ make -C doc/modelli html
Ciò può essere utile per ridurre il tempo di compilazione (e la dimensione del chattering di compilazione) mentre
stai scrivendo il tuo capitolo.
Prima di inviare la documentazione al repository, verifica che venga compilata senza
errori o avvisi. Il processo di compilazione genera molti output (per lo più normali chiacchiere
da LaTeX), il che può rendere difficile vedere se ci sono avvisi Sphinx o
errori. Per trovare avvisi ed errori importanti, compila solo il html versione, quindi cerca
il registro di compilazione per identificazione dei warning or errore.
NS-3 Specifiche
La Sfinge documentazione e lezione sono piuttosto buoni. Non duplicheremo le basi
qui, invece di concentrarsi sull'uso preferito per NS-3.
· Inizia i documenti con queste due righe:
.. include:: replace.txt
.. evidenzia:: cpp
La prima riga consente alcune semplici sostituzioni. Ad esempio, digitando |ns3| rende come
NS-3Il secondo imposta esplicitamente il linguaggio di evidenziazione del codice sorgente predefinito per
file, poiché l'ipotesi del parser non è sempre accurata. (È anche possibile impostare il
linguaggio in modo esplicito per un singolo blocco di codice, vedere sotto.)
· Sezioni:
Sphinx è piuttosto liberale nel contrassegnare i titoli delle sezioni. Per convenzione, preferiamo questo
gerarchia:
.. gerarchia delle intestazioni:
------------- Capitolo
************* Sezione (#.#)
============= Sottosezione (#.#.#)
############# Sotto-sottosezione
· Evidenziazione della sintassi:
Per utilizzare l'evidenziatore di sintassi predefinito, è sufficiente avviare un blocco di codice sorgente:
┌─────────────────────────────────────── ──────┬──────────────────────────────────┐
│Sorgente Sphinx │ Output renderizzato │
├─────────────────────────────────────── ──────┼─────────────────────────────────┤
│ │ Il Frobnitz è accessibile tramite: │
│ Si accede al ``Frobnitz`` tramite:: │ │
│ │ Foo::Frobnitz frob; │
│ Foo::Frobnitz frob; │ frob.Set (...); │
│ frob.Set (...); │ │
└─────────────────────────────────────── ──────┴─────────────────────────────────┘
Per utilizzare un evidenziatore di sintassi specifico, ad esempio, bash comandi shell:
┌────────────────────────────────────┬───────────────────┐
│Sorgente Sphinx │ Output renderizzato │
├────────────────────────────────┼───────────────────┤
│ │ │
│ .. codice sorgente:: bash │ $ ls │
│ │ │
│ $ ls │ │
└────────────────────────────────────────────────┘
· Notazioni abbreviate:
Queste abbreviazioni sono definite:
┌──────────────────────────┬──────────────────┐
│Sorgente Sphinx │ Output renderizzato │
├───────────────────────┼──────────────────┤
│ │ NS-3 │
│ |ns3| │ │
├───────────────────────┼──────────────────┤
│ │ NS-2 │
│ |ns2| │ │
├───────────────────────┼──────────────────┤
│ │ │
│ |controlla| │ │
├───────────────────────┼──────────────────┤
│ │ RFC 6282 │
│ :rfc:`6282` │ │
└────────────────────────────┴────────────────┘
Documentare con Doxygen
Usiamo Doxygen generare esplorabile Documentazione API. Doxygen fornisce una serie di
caratteristiche utili:
· Tabella riassuntiva di tutti i membri della classe.
· Grafici di ereditarietà e collaborazione per tutte le classi.
· Link al codice sorgente che implementa ciascuna funzione.
· Link a tutti i luoghi in cui viene utilizzato un membro.
· Collegamenti a ogni oggetto utilizzato nell'implementazione di una funzione.
· Raggruppamento di classi correlate, come tutte le classi correlate a un protocollo specifico.
Inoltre, utilizziamo il ID tipo sistema per aggiungere alla documentazione per ogni classe
· Il Config percorsi attraverso i quali è possibile raggiungere tali oggetti.
· Documentazione per qualsiasi Attributi, di cui Attributi definiti nelle classi padre.
· Documentazione per qualsiasi Traccia fonti definite dalla classe.
Doxygen funziona scansionando il codice sorgente, cercando commenti appositamente contrassegnati.
crea anche un riferimento incrociato, indicando where ogni file, classe, metodo e variabile è
Usato.
Preferito Style
Lo stile preferito per i commenti Doxygen è lo stile JavaDoc:
/ **
* Breve descrizione di questa classe o metodo.
* Le righe adiacenti diventano un singolo paragrafo.
*
* Descrizione più lunga, con molti dettagli.
*
* Le righe vuote separano i paragrafi.
*
* Spiega cosa fa la classe o il metodo, utilizzando quale algoritmo.
* Spiega le unità degli argomenti e i valori restituiti.
*
* \note Nota eventuali limitazioni o insidie.
*
* (Per funzioni con argomenti o valori di ritorno:)
* \param foo Breve sintagma nominale che descrive questo argomento.
* \param bar Nota Maiuscole/minuscole nella frase e punto finale.
* \return Breve sintagma nominale che descrive il valore.
*
* \interno
*
* È possibile anche discutere i dettagli dell'implementazione interna.
* La comprensione di questo materiale non dovrebbe essere necessaria per l'utilizzo
* la classe o il metodo.
*/
classe Esempio
In questo stile il blocco di commento Doxygen inizia con due caratteri `*': / **, e precede
l'elemento che viene documentato.
Per gli articoli che necessitano solo di una breve descrizione, è appropriata una di queste forme abbreviate:
/** Implementazione del distruttore. */
void DoDispose ();
int m_count; //!< Conteggio di ...
Nota la forma speciale del commento di fine riga, //!, indicando che si riferisce al
precedente articolo.
Alcuni elementi da notare:
· Utilizzare la forma maiuscola, inclusa la lettera iniziale.
· Utilizzare la punteggiatura, in particolare `.' alla fine delle frasi o delle espressioni.
· Il \breve il tag non è necessario; la prima frase verrà utilizzata come brief
descrizione.
Ogni classe, metodo, typedef, variabile membro, argomento di funzione e valore di ritorno dovrebbe
essere documentato in tutti i file del codice sorgente che formano l'API formale e l'implementazione per
NS-3, come fonte/ /modello/*, fonte/ /aiutante/* e fonte/ /utils/*.
Documentazione per gli articoli in fonte/ /test/* e fonte/ /esempi/* è preferito,
ma non è obbligatorio.
Utile Caratteristiche
· I membri ereditati erediteranno automaticamente i documenti dal genitore (ma possono essere sostituiti
dalla documentazione locale).
1. Documentare la classe base.
2. Nella sottoclasse contrassegnare le funzioni ereditate con un commento ordinario:
// Metodi ereditati
vuoto virtuale FooBar (vuoto);
int virtuale BarFoo (doppio baz);
Si noti che le firme devono corrispondere esattamente, quindi includere l'argomento formale (vuoto)
Questo non funziona per le funzioni statiche; vedere Ottieni IDTipo, di seguito, per un esempio.
Costruzione Doxygen Docs
Creare la documentazione di Doxygen è piuttosto semplice:
$ ./waf doxygen
Questa build utilizza la configurazione predefinita, che genera sezioni di documentazione per
contro tutti i elementi, anche se non hanno blocchi di documentazione di commento espliciti. Questo ha il
effetto di soppressione degli avvisi per gli elementi non documentati, ma assicura che tutto appaia
nell'output generato.
Quando si scrive la documentazione, spesso è più utile vedere quali elementi vengono generati
avvisi, in genere relativi alla documentazione mancante. Per visualizzare l'elenco completo degli avvisi, utilizzare
doc/doxygen.warnings.report.sh sceneggiatura:
$ doc/doxygen.warnings.report.sh
Waf: Accesso alla directory `build'
...
Waf: Uscita dalla directory `build'
'build' è terminato con successo (3m24.094s)
Ricostruzione della documentazione doxygen con errori completi...Fatto.
Segnalazione di avvisi Doxygen
----------------------------------------
(Tutti i conteggi sono limiti inferiori.)
Avvisi per modulo/directory:
Elenco dei conteggi
----- ----------------------------------
3844 src/lte/modello
1718 src/wimax/modello
1423 src/core/model
....
138 parametri aggiuntivi non documentati.
----------------------------------------
15765 avvisi totali
126 directory con avvisi
Avvisi per file (in ordine alfabetico)
File di conteggio
----- ----------------------------------
17 doc/introspected-doxygen.h
15 esempi/routing/manet-routing-compare.cc
26 esempi/stats/wifi-example-apps.h
....
----------------------------------------
967 file con avvisi
Avvisi per file (numerici)
File di conteggio
----- ----------------------------------
374 src/lte/model/lte-asn1-header.h
280 src/lte/model/lte-rrc-sap.h
262 src/lte/model/lte-rrc-header.h
....
----------------------------------------
967 file con avvisi
Riepilogo degli avvisi di Doxygen
----------------------------------------
126 directory
File 967
15765 avvisi
Lo script modifica la configurazione per mostrare tutti gli avvisi e ridurre il tempo di esecuzione.
Come potete vedere, al momento della stesura di questo articolo abbiamo a lotto di articoli non documentati. Il rapporto
riassume gli avvisi per modulo origine/*/*e per file, in ordine alfabetico e numerico.
Lo script offre alcune opzioni per semplificare le cose e renderle più gestibili. Per assistenza,
Usa il -h opzione. Dopo averlo eseguito una volta per eseguire la build Doxygen e generare il file completo
registro degli avvisi, è possibile rielaborare il file di registro con vari "filtri", senza dover fare
la build completa di Doxygen, utilizzando ancora una volta il -s opzione. È possibile escludere gli avvisi da
*/esempi/* File (-e opzione), e/o */test/* File (-t).
Forse l'opzione più utile quando si scrivono commenti alla documentazione è -m , quale
limiterà il report solo ai file corrispondenti fonte/ /*, e seguire il rapporto con
le linee di avvertimento effettive. Combina con -e e puoi concentrarti sugli avvertimenti che sono
più urgenti in un singolo modulo:
$ doc/doxygen.warnings.report.sh -m mesh/helper
...
Riepilogo degli avvisi di Doxygen
----------------------------------------
1 directory
File 3
149 avvisi
Avvisi filtrati
========================================
src/mesh/helper/dot11s/dot11s-installer.h:72: avviso: il membro m_root (variabile) della classe ns3::Dot11sStack non è documentato.
src/mesh/helper/dot11s/dot11s-installer.h:35: avviso: il tipo di ritorno del membro ns3::Dot11sStack::GetTypeId non è documentato
src/mesh/helper/dot11s/dot11s-installer.h:56: avviso: il tipo di ritorno del membro ns3::Dot11sStack::InstallStack non è documentato
src/mesh/helper/flame/lfame-installer.h:40: avviso: il membro GetTypeId() (funzione) della classe ns3::FlameStack non è documentato.
src/mesh/helper/flame/flame-installer.h:60: avviso: il tipo di ritorno del membro ns3::FlameStack::InstallStack non è documentato
src/mesh/helper/mesh-helper.h:213: avviso: il membro m_nInterfaces (variabile) della classe ns3::MeshHelper non è documentato.
src/mesh/helper/mesh-helper.h:214: avviso: il membro m_spreadChannelPolicy (variabile) della classe ns3::MeshHelper non è documentato.
src/mesh/helper/mesh-helper.h:215: avviso: il membro m_stack (variabile) della classe ns3::MeshHelper non è documentato.
src/mesh/helper/mesh-helper.h:216: avviso: il membro m_stackFactory (variabile) della classe ns3::MeshHelper non è documentato.
src/mesh/helper/mesh-helper.h:209: avviso: i parametri del membro ns3::MeshHelper::CreateInterface non sono (tutti) documentati
src/mesh/helper/mesh-helper.h:119: avviso: i parametri del membro ns3::MeshHelper::SetStandard non sono (tutti) documentati
Ora si tratta solo di capire il codice e scrivere un po' di documentazione!
NS-3 Specifiche
Per quanto riguarda Sphinx, il Doxygen docs e riferimento sono piuttosto buoni. Non duplicheremo il
nozioni di base qui, concentrandosi invece sull'uso preferito per NS-3.
· Utilizzare Doxygen moduli per raggruppare elementi correlati.
Nell'intestazione principale di un modulo, crea un gruppo Doxgyen:
/ **
* \defgroup foo Protocollo Foo.
*/
Contrassegna ogni classe associata come appartenente al gruppo:
/ **
* \ingroup foo
*
* Tipo di pacchetto Foo.
*/
classe Foo
· Lo sapevate typedef può avere argomenti formali? Questo consente la documentazione della funzione
firme dei puntatori:
/ **
* Barra la firma della funzione di callback.
*
* \param ale La dimensione di una pinta di birra, in once imperiali.
*/
typedef void (* BarCallback)(const int ale);
· Copia il Attributo stringhe di aiuto dal Ottieni IDTipo metodo da utilizzare come brief
descrizioni dei membri associati.
· \bugid{298} creerà un collegamento al bug 298 nel nostro Bugzilla.
· \pname{pippo} in una descrizione verrà formattato foo come \parametro foo parametro, rendendolo chiaro
che ti riferisci a un argomento reale.
· \RFC{301} creerà un collegamento a RFC 301.
· \interno dovrebbe essere utilizzato solo per avviare una discussione sui dettagli di implementazione, non per
marchio un bagno funzioni (sono già contrassegnate, come un bagno!)
· Non creare classi con nomi banali, come classe A, anche nelle suite di test. Questi
fa sì che tutte le istanze del letterale del nome della classe `A' vengano renderizzate come collegamenti.
Come notato sopra, le funzioni statiche non ereditano la documentazione delle stesse funzioni in
la classe madre. NS-3 utilizza alcune funzioni statiche in modo ubiquo; il suggerito
il blocco di documentazione per questi casi è:
· Costruttore/distruttore predefinito:
MyClass (); //!< Costruttore predefinito
~MyClass (); //!< Distruttore
· Distruttore fittizio e DoDispose:
/** Distruttore fittizio, vedere DoDispose. */
~LaMiaClasse ();
/** Implementazione del distruttore */
vuoto virtuale DoDispose ();
· GetTypeId:
/ **
* Registra questo tipo.
* \return L'oggetto TypeId.
*/
TypeId statico GetTypeId (vuoto);
Abilitare Sottoinsiemi of NS-3 moduli
Come con la maggior parte dei progetti software, NS-3 sta diventando sempre più grande in termini di numero di moduli,
linee di codice e occupazione di memoria. Gli utenti, tuttavia, possono utilizzare solo alcuni di questi moduli
alla volta. Per questo motivo, gli utenti potrebbero voler abilitare esplicitamente solo il sottoinsieme di
possibile NS-3 moduli di cui hanno effettivamente bisogno per la loro ricerca.
Questo capitolo illustra come abilitare solo il NS-3 moduli a cui sei interessato
utilizzando.
Come a enable a sottoinsieme of NS-3's moduli
Se vengono create librerie condivise, l'abilitazione di un modulo causerà almeno una
biblioteca da costruire:
libns3-nomemodulo.so
Se il modulo ha una libreria di test e le librerie di test sono in fase di creazione, allora
libns3-nomemodulo-test.so
Verranno costruiti anche altri moduli da cui dipende il modulo e le relative librerie di test
verrà anche costruito.
Per impostazione predefinita, tutti i moduli sono integrati NS-3Esistono due modi per abilitare un sottoinsieme di questi
moduli:
1. Utilizzo dell'opzione --enable-modules di waf
2. Usando il NS-3 file di configurazione
Consentire a tutti moduli utilizzando waf's --abilita-moduli opzione
Per abilitare solo il modulo principale con esempi e test, ad esempio, prova questi comandi:
$ ./waf pulito
$ ./waf configure --enable-examples --enable-tests --enable-modules=core
$ ./waf build
$ cd build/debug/
$ l
e devono essere presenti le seguenti librerie:
associazioni libns3-core.so ns3 scratch utils
esempi libns3-core-test.so campioni src
Notare la ./waf cavedano il passaggio viene fatto qui solo per rendere più ovvio quali librerie di moduli
sono stati costruiti. Non devi farlo ./waf cavedano per abilitare sottoinsiemi di moduli.
L'esecuzione di test.py causerà l'esecuzione solo dei test che dipendono dal core del modulo:
24 test su 24 superato (24 superato, 0 saltati, 0 falliti, 0 bloccati, 0 errori valgrind)
Ripetere i passaggi precedenti per il modulo "rete" invece del modulo "core" e il
verrà costruito quanto segue, poiché la rete dipende dal core:
associazioni libns3-core.so libns3-network.so ns3 scratch utils
esempi libns3-core-test.so libns3-network-test.so campioni src
L'esecuzione di test.py causerà l'esecuzione di quei test che dipendono solo dai moduli core e network.
essere eseguito:
31 test su 31 superato (31 superato, 0 saltati, 0 falliti, 0 bloccati, 0 errori valgrind)
Consentire a tutti moduli utilizzando , il NS-3 configurazione filetto
È stato aggiunto un file di configurazione, .ns3rc, a NS-3 che consente agli utenti di specificare quale
i moduli devono essere inclusi nella build.
Quando si abilita un sottoinsieme di NS-3 moduli, le regole di precedenza sono le seguenti:
1. la stringa di configurazione --enable-modules sovrascrive qualsiasi file .ns3rc
2. il file .ns3rc nel livello superiore NS-3 la directory viene consultata successivamente, se presente
3. il sistema cerca ~/.ns3rc se i due precedenti non sono specificati
Se nessuno dei limiti sopra indicati limita i moduli da costruire, tutti i moduli di cui waf è a conoscenza saranno
essere costruito.
La versione mantenuta del file .ns3rc nel NS-3 il repository del codice sorgente risiede in
, il utils directory. Il motivo è che se fosse nella directory di primo livello del
repository, sarebbe soggetto a check-in accidentali da parte dei manutentori che abilitano il
moduli che desiderano utilizzare. Pertanto, gli utenti devono copiare manualmente il file .ns3rc dal
utils directory nella loro posizione preferita (directory di livello superiore o la loro directory home) per
abilita la configurazione di build modulare persistente.
Supponendo che tu sia al livello più alto NS-3 directory, puoi ottenere una copia del file .ns3rc
file che si trova nel utils directory come segue:
$ cp utils/.ns3rc .
Il file .ns3rc dovrebbe ora essere nel tuo livello superiore NS-3 directory, e contiene il
seguenti:
#! /usr/bin/env python
# Un elenco dei moduli che verranno abilitati quando viene eseguito ns-3.
# Saranno abilitati anche i moduli che dipendono dai moduli elencati.
#
# Tutti i moduli possono essere abilitati scegliendo 'all_modules'.
moduli_abilitati = ['tutti_i_moduli']
# Imposta questo valore su true se vuoi che vengano eseguiti degli esempi.
examples_enabled = False
# Imposta questo valore su true se vuoi che vengano eseguiti i test.
test_abilitati = Falso
Utilizza il tuo editor preferito per modificare il file .ns3rc per abilitare solo il modulo core con
esempi e test come questo:
#! /usr/bin/env python
# Un elenco dei moduli che verranno abilitati quando viene eseguito ns-3.
# Saranno abilitati anche i moduli che dipendono dai moduli elencati.
#
# Tutti i moduli possono essere abilitati scegliendo 'all_modules'.
moduli_abilitati = ['core']
# Imposta questo valore su true se vuoi che vengano eseguiti degli esempi.
examples_enabled = Vero
# Imposta questo valore su true se vuoi che vengano eseguiti i test.
test_abilitati = Vero
Ora sarà abilitato solo il modulo principale se provi questi comandi:
$ ./waf pulito
$ ./waf configura
$ ./waf build
$ cd build/debug/
$ l
e devono essere presenti le seguenti librerie:
associazioni libns3-core.so ns3 scratch utils
esempi libns3-core-test.so campioni src
Notare la ./waf cavedano il passaggio viene fatto qui solo per rendere più ovvio quali librerie di moduli
sono stati costruiti. Non devi farlo ./waf cavedano per abilitare sottoinsiemi di moduli.
L'esecuzione di test.py causerà l'esecuzione solo dei test che dipendono dal core del modulo:
24 test su 24 superato (24 superato, 0 saltati, 0 falliti, 0 bloccati, 0 errori valgrind)
Ripetere i passaggi precedenti per il modulo "rete" invece del modulo "core" e il
verrà costruito quanto segue, poiché la rete dipende dal core:
associazioni libns3-core.so libns3-network.so ns3 scratch utils
esempi libns3-core-test.so libns3-network-test.so campioni src
L'esecuzione di test.py causerà l'esecuzione di quei test che dipendono solo dai moduli core e network.
essere eseguito:
31 test su 31 superato (31 superato, 0 saltati, 0 falliti, 0 bloccati, 0 errori valgrind)
Abilitazione/disabilitazione NS-3 Test e Esempi
. NS-3 la distribuzione include molti esempi e test che vengono utilizzati per convalidare la NS-3
sistema. Gli utenti, tuttavia, potrebbero non voler sempre che questi esempi e test vengano eseguiti per i loro
installazione di NS-3.
Questo capitolo spiega come costruire NS-3 con o senza i relativi esempi e test.
Come a abilitare / disabilitare Esempi e test in NS-3
Ci sono 3 modi per abilitare/disabilitare esempi e test in NS-3:
1. Utilizzo di build.py quando NS-3 è costruito per la prima volta
2. Usare waf una volta NS-3 è stato costruito
3. Usando il NS-3 file di configurazione una volta NS-3 è stato costruito
Abilitare / disabilitare Esempi e test utilizzando build.py
Puoi usare build.py per abilitare/disabilitare esempi e test quando NS-3 è costruito per la prima
tempo.
Per impostazione predefinita, esempi e test non sono integrati NS-3.
Dalla directory ns-3-allinone, puoi compilare NS-3 senza alcun esempio o test semplicemente
facendo:
$ ./build.py
Esecuzione di test.py nel livello superiore NS-3 directory ora non causerà alcun esempio o test
correre:
0 test su 0 superato (0 superato, 0 saltati, 0 falliti, 0 bloccati, 0 errori valgrind)
Se vuoi costruire NS-3 con esempi e test, quindi esegui quanto segue da
directory ns-3-allinone:
$ ./build.py --enable-examples --enable-test
Esecuzione di test.py nel livello superiore NS-3 la directory causerà tutti gli esempi e i test
da eseguire:
170 test su 170 superato (170 superato, 0 saltati, 0 falliti, 0 bloccati, 0 errori valgrind)
Abilitare / disabilitare Esempi e test utilizzando waf
Puoi usare waf per abilitare/disabilitare esempi e test una volta NS-3 è stato costruito.
Per impostazione predefinita, esempi e test non sono integrati NS-3.
Dal livello superiore NS-3 directory, puoi costruire NS-3 senza alcun esempio o test semplicemente
facendo:
$ ./waf configura
$ ./waf build
L'esecuzione di test.py ora non causerà l'esecuzione di alcun esempio o test:
0 test su 0 superato (0 superato, 0 saltati, 0 falliti, 0 bloccati, 0 errori valgrind)
Se vuoi costruire NS-3 con esempi e test, quindi esegui quanto segue dall'alto
livello NS-3 directory:
$ ./waf configure --enable-examples --enable-tests
$ ./waf build
L'esecuzione di test.py causerà l'esecuzione di tutti gli esempi e dei test:
170 test su 170 superato (170 superato, 0 saltati, 0 falliti, 0 bloccati, 0 errori valgrind)
Abilitare / disabilitare Esempi e test utilizzando , il NS-3 configurazione filetto
È stato aggiunto un file di configurazione, .ns3rc, a NS-3 che consente agli utenti di specificare se
esempi e test dovrebbero essere compilati o meno. Puoi usare questo file per abilitare/disabilitare
esempi e test una volta NS-3 è stato costruito.
Quando si abilitano o disabilitano esempi e test, le regole di precedenza sono le seguenti:
1. le stringhe di configurazione --enable-examples/--disable-examples sovrascrivono qualsiasi file .ns3rc
2. le stringhe di configurazione --enable-tests/--disable-tests sovrascrivono qualsiasi file .ns3rc
3. il file .ns3rc nel livello superiore NS-3 la directory viene consultata successivamente, se presente
4. il sistema cerca ~/.ns3rc se il file .ns3rc non è stato trovato nel passaggio precedente
Se nessuno degli elementi sopra menzionati è presente, gli esempi e i test non verranno creati.
La versione mantenuta del file .ns3rc nel NS-3 il repository del codice sorgente risiede in
, il utils directory. Il motivo è che se fosse nella directory di primo livello del
repository, sarebbe soggetto a check-in accidentali da parte dei manutentori che abilitano il
moduli che desiderano utilizzare. Pertanto, gli utenti devono copiare manualmente il file .ns3rc dal
utils directory nella loro posizione preferita (directory di livello superiore o la loro directory home) per
abilita l'abilitazione persistente di esempi e test.
Supponendo che tu sia al livello più alto NS-3 directory, puoi ottenere una copia del file .ns3rc
file che si trova nel utils directory come segue:
$ cp utils/.ns3rc .
Il file .ns3rc dovrebbe ora essere nel tuo livello superiore NS-3 directory, e contiene il
seguenti:
#! /usr/bin/env python
# Un elenco dei moduli che verranno abilitati quando viene eseguito ns-3.
# Saranno abilitati anche i moduli che dipendono dai moduli elencati.
#
# Tutti i moduli possono essere abilitati scegliendo 'all_modules'.
moduli_abilitati = ['tutti_i_moduli']
# Imposta questo valore su true se vuoi che vengano eseguiti degli esempi.
examples_enabled = False
# Imposta questo valore su true se vuoi che vengano eseguiti i test.
test_abilitati = Falso
Dal livello superiore NS-3 directory, puoi costruire NS-3 senza alcun esempio o test semplicemente
facendo:
$ ./waf configura
$ ./waf build
L'esecuzione di test.py ora non causerà l'esecuzione di alcun esempio o test:
0 test su 0 superato (0 superato, 0 saltati, 0 falliti, 0 bloccati, 0 errori valgrind)
Se vuoi costruire NS-3 con esempi e test, usa il tuo editor preferito per modificare
i valori nel file .ns3rc per examples_enabled e tests_enabled devono essere True:
#! /usr/bin/env python
# Un elenco dei moduli che verranno abilitati quando viene eseguito ns-3.
# Saranno abilitati anche i moduli che dipendono dai moduli elencati.
#
# Tutti i moduli possono essere abilitati scegliendo 'all_modules'.
moduli_abilitati = ['tutti_i_moduli']
# Imposta questo valore su true se vuoi che vengano eseguiti degli esempi.
examples_enabled = Vero
# Imposta questo valore su true se vuoi che vengano eseguiti i test.
test_abilitati = Vero
Dal livello superiore NS-3 directory, puoi costruire NS-3 con esempi e test semplicemente
facendo:
$ ./waf configura
$ ./waf build
L'esecuzione di test.py causerà l'esecuzione di tutti gli esempi e dei test:
170 test su 170 superato (170 superato, 0 saltati, 0 falliti, 0 bloccati, 0 errori valgrind)
Troubleshooting
Questo capitolo pubblica alcune informazioni su possibili errori comuni nella compilazione o nell'esecuzione
NS-3 programmi.
Si prega di notare che il wiki (http://www.nsnam.org/wiki/Troubleshooting) potrebbe aver contribuito
articoli.
Silhouette errori
Run-time errori
A volte, possono verificarsi errori in un programma dopo una compilazione riuscita. Questi sono errori di runtime
errori e possono verificarsi comunemente quando la memoria è danneggiata o i valori del puntatore sono inaspettatamente
nullo.
Ecco un esempio di cosa potrebbe accadere:
$ ./waf --esegui tcp-point-to-point
Accesso alla directory '/home/tomh/ns-3-nsc/build'
Compilazione completata con successo
Il comando ['/home/tomh/ns-3-nsc/build/debug/examples/tcp-point-to-point'] è terminato con il codice -11
Il messaggio di errore dice che il programma è stato terminato senza successo, ma non è chiaro
da queste informazioni cosa potrebbe esserci di sbagliato. Per esaminare più attentamente, prova a eseguirlo sotto
, il gdb debugger:
$ ./waf --run tcp-point-to-point --command-template="gdb %s"
Accesso alla directory '/home/tomh/ns-3-nsc/build'
Compilazione completata con successo
GNU gdb Red Hat Linux (6.3.0.0-1.134.fc5rh)
Copyright 2004 Free Software Foundation, Inc.
GDB è un software libero, coperto dalla GNU General Public License, e tu sei
benvenuto a modificarlo e/o distribuirne copie a determinate condizioni.
Digita "mostra copia" per visualizzare le condizioni.
Non esiste alcuna garanzia per GDB. Digita "mostra garanzia" per i dettagli.
Questo GDB è stato configurato come "i386-redhat-linux-gnu"...Utilizzando l'host libthread_db
libreria "/lib/libthread_db.so.1".
(gdb) eseguire
Avvio del programma: /home/tomh/ns-3-nsc/build/debug/examples/tcp-point-to-point
Lettura dei simboli dall'oggetto condiviso letto dalla memoria di destinazione...fatto.
Il sistema caricato ha fornito DSO a 0xf5c000
Il programma ha ricevuto il segnale SIGSEGV, errore di segmentazione.
0x0804aa12 nel file principale (argc=1, argv=0xbfdfefa4)
in ../examples/tcp-point-to-point.cc:136
136 Ptr localSocket = socketFactory->CreateSocket ();
(gdb) p localSocket
$1 = {m_ptr = 0x3c5d65}
(gdb) p socketFactory
$2 = {m_ptr = 0x0}
(gdb) uscire
Il programma è in esecuzione. Uscire comunque? (y o n) y
Nota prima il modo in cui è stato richiamato il programma: passa il comando da eseguire come argomento al
modello di comando "gdb %s".
Ciò ci dice che si è verificato un tentativo di dereferenziare un puntatore nullo socketFactory.
Diamo un'occhiata alla riga 136 di tcp-point-to-point, come suggerisce gdb:
Ptr socketFactory = n2->GetObject (Tcp::iid);
Ptr localSocket = socketFactory->CreateSocket ();
localSocket->Bind ();
Il colpevole qui è che il valore di ritorno di GetObject non viene controllato e potrebbe essere
nullo.
A volte potrebbe essere necessario utilizzare il valgrind memoria checker per errori più sottili. Di nuovo,
si invoca l'uso di valgrind in modo simile:
$ ./waf --run tcp-point-to-point --command-template="valgrind %s"
FONTE
Questo documento è scritto in testoristrutturato da Sfinge ed è mantenuto nel
doc/manuale directory del codice sorgente di ns-3.
Utilizzare ns-3-manual online utilizzando i servizi onworks.net
