Manuale di Network Simulator v.2

Capitolo 10 - Gli AGENTI

a cura di Sandro Petrizzelli


 

Introduzione

 

Gli agenti rappresentano i punti terminali (end-points) in cui i pacchetti di livello network vengono costruiti e/o letti. Gli agenti sono le entità che NS usa per l’implementazione dei protocolli ai vari livelli.

La classe denominata Agent ha una implementazione parte in OTcl e parte in C++. L’implementazione in C++ è contenuta nei seguenti file:

 

·      ~ns/agent.cc

·      ~ns/agent.h

 

Il supporto OTcl è invece contenuto nell’unico file ~ns/tcl/lib/ns-agent.tcl.

 

 

10.1 “Stato” di un agente

 

La gerarchia da cui “discende” la classe Agent è la seguente:

 

 

La classe Agent (C++) include uno stato interno, ossia un insieme di variabili il cui contenuto viene usato per assegnare valori a vari campi di un pacchetto simulato, prima che questo venga inviato. Tale stato include le seguenti variabili:

 

 

Queste variabili possono essere modificate da qualsiasi classe derivata dalla classe Agent. Possiamo anche spendere qualche parola in più a proposito di questi parametri di stato:

 

 

Facciamo notare che, quando parliamo di “indirizzo” (sorgente o destinatario), intendiamo un “indirizzo di ns”, ossia un numero che identifica sia l’agente (mittente o ricevente del pacchetto) sia il nodo su cui esso si trova. Non c’è dunque alcun legame tra tali indirizzi e, ad esempio, gli indirizzi IP.

Segue ora un elenco di alcune delle classi derivate dalla classe Agent:

 

 

 

10.2 Metodi della classe Agent

 

La classe Agent supporta meccanismi di generazione e di ricezione di pacchetti. Le seguenti funzioni membro sono implementate nella classe Agent (C++) e non vengono generalmente sovrapposte dalle classi derivate ( [2] ):

 

 

Nella stessa classe sono anche definite le seguenti funzioni membro, che invece si prevede vengano sovrapposte dalle classi derivate:

 

 

Metodo allocpkt

 

Il metodo Agent::allocpkt() viene usato, dalle classi derivate dalla classe base Agent, per creare i pacchetti da inviare:

 

·      questa funzione in primo luogo riempie i seguenti campi nel common header del pacchetto: uid_, ptype_, size_;

·      inoltre, in base ai parametri interni di stato dell’agente, riempie i seguenti campi nell’header IP: src_, dst_, fid_, prio_, ttl_;

·      infine, la funzione inizializza al valore 0 i seguenti campi nel Flags header: ecn, pri, usr1, usr2.

 

Ogni informazione nell’header di un pacchetto, che non sia compresa nelle liste appena elencate, deve essere gestita dalle classi specifiche derivate dalla classe Agent.

Segue il listato della funzione allocpkt, sia nella versione senza argomenti sia in quella con singolo argomento:

 

Packet* Agent::allocpkt() const

{

     Packet* p = Packet::alloc();

     initpkt(p);

     return (p);

}

 

Packet* Agent::allocpkt(int n) const

{

        Packet* p = allocpkt();

 

     if (n > 0)

             p->allocdata(n);

 

     return(p);

}

 

Come si vede, la differenza tra le due funzioni è semplicemente nel fatto che la seconda, nel caso in cui l’argomento passatole è ¹0, usa la procedura allocdata(int) dopo aver usato la procedura allocpkt().

La procedura allocpkt fa uso, a sua volta, delle procedure alloc (definita nel file packet.h) e initpkt (definite nel file agent.cc), riportate qui di seguito:

 

void Packet* Packet::alloc();

{

Packet* p = free_;

if (p != 0) {

        assert(p->fflag_ == FALSE);

        free_ = p->next_;

assert(p->data_ == 0);

p->uid_ = 0;

p->time_ = 0;

}

else

{

 p = new Packet;

 p->bits_ = new unsigned char[hdrlen_];

 if (p == 0 || p->bits_ == 0)

          abort();

}

 

init(p); // Initialize bits_[]

 

(HDR_CMN(p))->next_hop_ = -2 // -1 reserved for IP_BROADCAST

(HDR_CMN(p))->last_hop_ = -2 // -1 reserved for IP_BROADCAST

p->fflag_ = TRUE;

(HDR_CMN(p))->direction() = hdr_cmn::DOWN;

/* setting all direction of pkts to be downward as default;

   until channel changes it to +1 (upward */

p->next_ = 0;

return (p);

}

 

 

void Agent::initpkt(Packet* p) const

{

     hdr_cmn* ch = hdr_cmn::access(p);

     ch->uid() = uidcnt_;

     ch->ptype() = type_;

     ch->size() = size_;

     ch->timestamp() = Scheduler::instance().clock();

     ch->iface() = UNKN_IFACE.value(); // from packet.h (agent is local)

     ch->direction() = 0;

     ch->ref_count() = 0;       /* reference count */

     ch->error() = 0;           /* pkt not corrupt to start with */

 

     hdr_ip* iph = hdr_ip::access(p);

     iph->src() = here_;

     iph->dst() = dst_;

     iph->flowid() = fid_;

     iph->prio() = prio_;

     iph->ttl() = defttl_;

 

     hdr_flags* hf = hdr_flags::access(p);

     hf->ecn_capable_ = 0;

     hf->ecn_ = 0;

     hf->eln_ = 0;

     hf->ecn_to_echo_ = 0;

     hf->fs_ = 0;

     hf->no_ts_ = 0;

     hf->pri_ = 0;

     hf->cong_action_ = 0;

}

 

E’ interessante osservare proprio il listato di initpkt() ( [3] ): infatti, si vede subito che è proprio questa funzione ad occuparsi concretamente di inizializzare i vari campi prima citati per la procedura allocpkt. Si notano, in particolare, tre “blocchi” di istruzioni:

 

·      il primo blocco inizializza i campi del common header;

·      il secondo blocco inizializza i campi dell’header IP; i valori di inizializzazione sono prelevati direttamente dalle variabili di stato dell’agente; si noti, in particolare, che il campo dst_ viene posto uguale al contenuto della variabile dst_ (variabile membro della classe Agent), rappresentativa del destinatario dei pacchetti;

·      l’ultimo blocco inizializza a 0 tutti i campi del flag header.

 

 

Metodo “recv”

 

Il metodo Agent:recv() è sostanzialmente il principale “punto di entrata” per un agente che riceve pacchetti. Questa funzione viene invocata dai nodi mittenti quando devono inviare un pacchetto ad un dato agente.

La funzione recv() accetta due argomenti in ingresso, ma in molti casi gli agenti non fanno uso del secondo argomento, che viene posto al valore 0. Il primo argomento è invece il puntatore al pacchetto che deve essere ricevuto ed è quindi di importanza fondamentale.

Riportiamo anche il listato della funzione recv così come scritto nel file agent.cc:

 

void Agent::recv(Packet* p, Handler*)

{

     if (app_)

           app_->recv(hdr_cmn::access(p)->size());

     /*

      * didn't expect packet (or we're a null agent?)

      */

     Packet::free(p);

}

 

In seguito, avremo modo di parlare più dettagliatamente di questa funzione. Per il momento, limitiamoci a dire che app_ è un puntatore all’applicazione che si appoggia all’agente considerato: se tale puntatore risulta non nullo (il che significa che c’è una applicazione “operativa”), viene invocato il metodo recv() dell’applicazione, in modo che quest’ultima, prendendo in consegna il pacchetto dall’agente di trasporto ricevente, lo tratti nel modo previsto, dopodiché il pacchetto viene “liberato” tramite la procedura Packet::free().

Osserviamo inoltre due cose:

 

 

 

10.3 Vari tipi di agenti (e relativi protocolli)

 

Ci sono diversi agenti supportati dal simulatore. Segue la lista dei principali loro nomi in OTcl:

 

 

Gli agenti sono usati nell’implementazione dei protocolli ai vari livelli. Per alcuni protocolli di trasporto (ad esempio UDP), la distribuzione delle dimensioni dei pacchetti e/o i tempi di partenza dei pacchetti stessi possono essere imposti da un oggetto separato rappresentativo delle richieste di una data applicazione. A questo scopo, gli agenti forniscono una interfaccia API (application programming interface) per l’applicazione, di cui parleremo nel capitolo 32.

 

 

10.4 OTcl Linkage

 

Gli agenti possono essere creati in OTcl e lo stato interno di un agente può essere modificato usando la funzione OTcl denominata set oppure una qualsiasi altra funzione che la classe Agent (o le sue classi derivate) metta a disposizione. Da notare che, in alcuni casi, lo stato interno di un agente è accessibile solo tramite OTcl e non direttamente tramite il C++.

 

 

10.4.1 Creazione e manipolazione di un agente

 

Il seguente esempio illustra come creare e manipolare un agente di tipo TCP (predefinito) in OTcl:

 

set newtcp [new Agent/TCP]   ;# create new object (and C++ shadow object)

$newtcp set window_ 20       ;# sets the tcp agent’s window to 20

$newtcp target $dest         ;# target is implemented in Connector class

$newtcp set portID_ 1        ;# exists only in OTcl, not in C++

 

La prima riga usa i metodi OTcl denominati rispettivamente set e new per creare un nuovo agente di tipo TCP ed attribuirgli un identificativo, che in questo caso è newtcp. Così facendo, ogni qualvolta bisognerà riferirsi a tale agente, si potrà usare la simbologia $newtcp.

La seconda riga imposta uno dei possibili parametri di configurazione dell’agente in questione: in questo caso, si tratta del parametro window_, corrispondente all’ampiezza della finestra di invio dell’agente, secondo i noti meccanismi del protocollo TCP.

La terza riga fa invece uso della funzione target() per impostare l’oggetto a cui verranno consegnati i pacchetti generati dall’agente TCP precedentemente creato. In particolare, la funzione target() è quella implementata nella classe Connector ed è possibile richiamarla dall’OTcl grazie al fatto che, nella funzione Connector::command(), è stata prevista una chiamata OTcl di questo tipo. In questo caso, il valore della variabile target_ dell’agente TCP appena creato viene impostato pari all’indirizzo contenuto in $dest, dove si suppone implicitamente che $dest sia l’identificativo di un agente destinazione precedentemente creato.

 

10.4.2 Valori di default

 

I valori di default per le variabili membro, quelle visibili solo in OTcl e quelle linkate tra OTcl e C++ tramite la funzione bind, sono inizializzati nel file ~ns/tcl/lib/ns-default.tcl. Ad esempio, per la classe Agent abbiamo la seguente inizializzazione:

 

Agent set fid_          0

Agent set prio_         0

Agent set addr_         0

Agent set dst_          0

Agent set flags_        0

 

Generalmente, queste inizializzazioni sono localizzate nello spazio dei nomi OTcl prima che qualsiasi oggetto di questo tipo venga creato. Quindi, quando un oggetto di classe Agent viene creato, la chiamata della funzione bind nel costruttore dell’oggetto fa si che le corrispondenti variabili membro vengano automaticamente settate ai valori specificati come default.

 

 

10.4.3 Metodi OTcl

 

Le procedure definite per la classe OTcl Agent sono attualmente localizzate nel file ~ns/tcl/lib/ns-agent.tcl. Si tratta delle seguenti procedure:

 

·      port                      the agent’s port identifier

·      dst-port                  the destination’s port identifier

·      attach-source stype       create and attach a Source object to an agent

 

 

10.5 Esempi: Agenti TCP e TCP Sink

 

La classe TCPAgent rappresenta un mittente TCP semplificato. Esso invia dati ad un agente di classe TCPSinkAgent e processa i suoi ACK. Generalmente (ma non necessariamente) è necessario associare, all’oggetto TCPAgent mittente, un oggetto separato rappresentativo di una applicazione: è proprio quest’ultimo oggetto a generare i dati che l’agente di trasporto dovrà trasferire fino a destinazione.

Osservando le due classi TCPAgent e TCPSinkAgent, possiamo vedere come vengono costruiti agenti relativamente complessi. Daremo anche un esempio di agente Tahoe TCP per illustrare l’uso dei timer.

 

 

10.5.1 Creazione di un agente

 

Il seguente frammento di codice OTcl crea un agente TCP e ne setta i relativi parametri in modo opportuno:

 

set tcp [new Agent/TCP]          ;# create sender agent

$tcp set fid_ 2                  ;# set IP-layer flow ID

set sink [new Agent/TCPSink]     ;# create receiver agent

$ns attach-agent $n0 $tcp        ;# put sender on node $n0

$ns attach-agent $n3 $sink       ;# put receiver on node $n3

$ns connect $tcp $sink           ;# establish TCP connection

set ftp [new Application/FTP]    ;# create an FTP source "application"

$ftp attach-agent $tcp           ;# associate FTP with the TCP sender

$ns at 1.2 "$ftp start"          ;#arrange for FTP to start at time 1.2 sec

 

La prima riga contiene dunque l’istruzione OTcl new Agent/TCP, la quale determina la creazione di un oggetto di classe C++ TCPAgent. Il costruttore di tale classe per prima cosa “invoca” il costruttore della classe base Agent e poi effettua i propri collegamenti (bindings). Questi due costruttori sono fatti nel modo seguente:

 

The TcpSimpleAgent constructor (~ns/tcp.cc):

 

TcpAgent::TcpAgent() : Agent(PT_TCP), rtt_active_(0), rtt_seq_(-1),

rtx_timer_(this), delsnd_timer_(this), ……

{

bind("window_", &wnd_);

bind("windowInit_", &wnd_init_);

bind("windowOption_", &wnd_option_);

bind("windowConstant_", &wnd_const_);

...

bind("off_ip_", &off_ip_);

bind("off_tcp_", &off_tcp_);

...

}

 

The Agent constructor (~ns/agent.cc):

 

Agent::Agent(int pkttype) :

addr_(-1), dst_(-1), size_(0), type_(pkttype), fid_(-1),

prio_(-1), flags_(0)

{

memset(pending_, 0, sizeof(pending_)); /* timers */

// this is really an IP agent, so set up

// for generating the appropriate IP fields. . .

bind("addr_", (int*)&addr_);

bind("dst_", (int*)&dst_);

bind("fid_", (int*)&fid_);

bind("prio_", (int*)&prio_);

bind("flags_", (int*)&flags_);

...

}

 

Questi due frammenti di codice illustrano il caso comune in cui il costruttore di un agente fornisce l’identificatore del tipo di pacchetto (PT_TCP) al costruttore della classe base Agent (che infatti prevede un argomento pkttype). I valori per i vari tipi di pacchetti sono usati per facilitare il “tracing” dei pacchetti ( [5] ) e sono definiti nel file ~ns/trace.h.

Si nota che il costruttore della classe TcpAgent prevede un certo numero di chiamate della funzione bind, che serve ad effettuare i collegamenti (bindings) tra le variabili C++ e le corrispondenti variabili OTcl. In questo caso, le variabili che vengono collegate sono tradizionali variabili membro della classe, ad eccezione dei valori interi denominati “off_tcp_” e “off_ip_”: questi valori sono richiesti al fine di accedere, rispettivamente, all’header TCP e all’header IP di ciascun pacchetto. Dettagli ulteriori su questo argomento saranno comunque forniti in seguito ( [6] ).

Facciamo notare che il costruttore TcpAgent contiene anche l’inizializzazione di due timer, denominati “rtx_timer_” e “delsnd_timer_”. Gli oggetti della classe TimerHandler (letteralmente: manipolatori di timer) sono inizializzati fornendo un puntatore (this) al corrispondente agente.

 

 

10.5.2 Avviamento di un agente

 

Una volta creato, l’agente TCPAgent viene attivato, nel nostro esempio, quando la sua sorgente FTP riceve dall’OTcl la direttiva start nell’istante t=1.2 (espresso in secondi rispetto ad un istante iniziale t=0.0). L’operazione start è definita nella classe Application/FTP ( [7] ) e, in particolare, nel metodo command() di tale classe. La procedura OTcl start è invece definita nel file ~ns/tcl/lib/ns-source.tcl come segue:

 

Application/FTP instproc start {}

{

[$self agent] send –1

}

 

In questo frammento di codice, il termine “agent” si riferisce al nostro semplice agente TCP e “send –1” equivale ad inviare un file arbitrariamente grande. In questo caso, quindi, l’applicazione “consegna” all’agente di trasporto un numero imprecisato di byte, fino all’istante in cui non riceve una esplicita direttiva di stop, con la quale il trasferimento viene concluso. In alternativa, al posto di “–1” si può indicare un numero qualsiasi di byte che l’applicazione dovrà consegnare all’agente di trasporto per la trasmissione a destinazione ( [8] ).

La chiamata della funzione send provoca la generazione di pacchetti da parte dell’agente di trasporto mittente sul quale l’agente FTP si sta “poggiando” (nel nostro caso, è un mittente TCP). Questo è ottenuto tramite una chiamata della funzione output, contenuta in questo caso nel file tcp.cc, della quale riportiamo un frammento:

 

void TcpAgent::output(int seqno, int reason)

{

Packet* p = allocpkt();

 

hdr_tcp *tcph = (hdr_tcp*)p->access(off_tcp_);

 

double now = Scheduler::instance().clock();

 

tcph->seqno() = seqno;

tcph->ts() = now;

tcph->reason() = reason;

 

Connector::send(p, 0);

...

 

if (!(rtx_timer_.status() == TIMER_PENDING))

/* No timer pending. Schedule one. */

set_rtx_timer();

}

 

In questa funzione, osserviamo l’uso del metodo Agent::allocpkt(), al fine di allocare il pacchetto da inviare, e l’uso del metodo Connector::send(), al fine di inviare tale pacchetto.

La routine output() si comporta dunque nel modo seguente:

 

 

Notiamo infine che la funzione output() richiede in ingresso due argomenti: il primo è il numero di sequenza da attribuire al pacchetto da creare ed inviare; il secondo, denominato reason, è un parametro che, nell’attuale implementazione di ns, vale sempre 0 ed è impostato a tale valore dalla funzione chiamante di output.

 

 

10.5.3 Elaborazione dell’ingresso al ricevente

 

La maggior parte degli agenti TCP può essere usata insieme alla classe TCPSink come entità ricevente “alla pari” (peer entity). Questa classe prevede i metodi recv() e ack(), rispettivamente per la ricezione di un pacchetto e l’invio della conferma su tale pacchetto, nel modo seguente:

 

void TcpSink::recv(Packet* pkt, Handler*)

{

hdr_tcp *th = (hdr_tcp*)pkt->access(off_tcp_);

acker_->update(th->seqno());

ack(pkt);

Packet::free(pkt);

}

 

void TcpSink::ack(Packet* opkt)

{

Packet* npkt = allocpkt();

hdr_tcp *otcp = (hdr_tcp*)opkt->access(off_tcp_);

hdr_tcp *ntcp = (hdr_tcp*)npkt->access(off_tcp_);

ntcp->seqno() = acker_->Seqno();

ntcp->ts() = otcp->ts();

  

hdr_ip* oip = (hdr_ip*)opkt->access(off_ip_);

hdr_ip* nip = (hdr_ip*)npkt->access(off_ip_);

nip->flowid() = oip->flowid();

 

hdr_flags* of = (hdr_flags*)opkt->access(off_flags_);

hdr_flags* nf = (hdr_flags*)npkt->access(off_flags_);

nf->ecn_ = of->ecn_;

  

acker_->append_ack((hdr_cmn*)npkt->access(off_cmn_),

ntcp, otcp->seqno());

   add_to_ack(npkt);  /* this function doesn’t anything  */

send(npkt, 0);

}

 

Il metodo recv() qui riportato si sovrappone al metodo Agent::recv() (il quale semplicemente scarta il pacchetto ricevuto).

 

 

Per completezza di esposizione, riportiamo anche il listato della funzione Agent::recv(), la quale, come detto, non compie alcuna particolare operazione se non scartare il pacchetto, motivo per cui deve essere ridefinita nelle classi derivate da Agent:

 

void Agent::recv(Packet* p, Handler*)

{

    if (app_)

         app_->recv(hdr_cmn::access(p)->size());

    /*

     * didn't expect packet (or we're a null agent?)

     */

    Packet::free(p);

}

 

 

Il metodo TcpSink::recv() compie sostanzialmente due operazioni:

 

 

Tale metodo ack(), come si deduce dal listato prima riportato, fa libero uso dell’accesso ai campi dell’header del pacchetto, adottando un accesso separato all’header TCP, all’header IP, al Flags header e al common header: infatti, dopo aver usato il “solito” metodo allocpkt() per allocare il pacchetto di ACK e riempire i suoi campi fondamentali, il metodo ack() riempie gli ulteriori campi in base alle informazioni contenute nel pacchetto di cui deve dare conferma (del quale ha ricevuto il puntatore come argomento in ingresso). Fatto questo, il metodo ack() invoca nuovamente, tramite la chiamata finale, il metodo Connector::send() per la consegna di tale ACK a destinazione.

 

 

10.5.4 Elaborazione delle risposte al mittente

 

Una volta che il ricevente TCP ha ricevuto un pacchetto e generato il corrispondente ACK (così come descritto nel paragrafo precedente), il mittente deve generalmente processare tale ACK. Nell’agente TCPAgent, questo viene effettuato tramite il metodo TCPAgent::recv(), di cui riportiamo il listato quasi completo:

 

/*

* main reception path - should only see acks, otherwise the

* network connections are misconfigured

*/

void TcpAgent::recv(Packet *pkt, Handler*)

{

hdr_tcp *tcph = (hdr_tcp*)pkt->access(off_tcp_);

hdr_ip* iph = (hdr_ip*)pkt->access(off_ip_);

...

if (((hdr_flags*)pkt->access(off_flags_))->ecn_)

quench(1);

if (tcph->seqno() > last_ack_) {

newack(pkt);

opencwnd();

} else if (tcph->seqno() == last_ack_) {

if (++dupacks_ == NUMDUPACKS) {

...

}

}

Packet::free(pkt);

send(0, 0, maxburst_);

}

 

Questa funzione membro viene dunque invocata quando un ACK giunge al mittente. In questo caso, una volta che l’informazione contenuta nell’ACK è stata processata (tramite la procedura newack), il pacchetto non serve più e viene restituito all’allocatore della memoria per i pacchetti (tramite il metodo Packet::free()).

In aggiunta, la ricezione dell’ACK indica la possibilità di inviare ulteriori dati, per cui il metodo TcpSimpleAgent::send() viene invocato per tentare di inviare più dati se la finestra TCP lo consente.

Possiamo concludere questo discorso con alcune osservazioni:

 

 

 

10.5.5 Implementazione dei timers

 

Come diremo nel prossimo capitolo, specifiche classi timer devono essere derivate da una classe astratta base denominata TimerHandler definita nel file ~ns/timer-handler.h. Le istanze di queste sottoclassi possono quindi essere usate come vari agenti di tipo timer.

Un agente può desiderare di sovrapporre, al metodo Agent::timeout() (che non fa niente), un proprio metodo analogo, più specializzato. Nel caso di un agente TCP Tahoe, sono ad esempio usati due timer: un timer di invio ritardato “delsnd_timer_” ed un timer di ritrasmissione “rtx_timer_”. Nel paragrafo 11.1.2 descriveremo il timer di ritrasmissione nel TCP come esempio di utilizzo dei timer.

 

 

10.6 Creazione di un nuovo agente

 

Per creare un nuovo agente, bisogna fondamentalmente compiere le seguenti operazioni:

 

  1. decidere la sua struttura ereditaria ( [12] ) e creare le opportune definizioni di classe;

  2. definire il metodo “recv()” ed eventualmente il metodo “timeout()”( [13] );

  3. definire ogni necessaria classe “timer”;

  4. definire le funzioni per l’ OTcl linkage ( [14] );

  5. scrivere il codice OTcl necessario per accedere all’agente ( [15] );

  6. ricompilare NS per rendere “operative” le modifiche effettuate.

 

Possiamo illustrare il procedimento completo tramite un esempio molto semplice: supponiamo di voler costruire un agente che effettui le operazioni  ICMP ECHO REQUEST/REPLY (o “ping”).

 

 

10.6.1 Struttura ereditaria

 

Consideriamo dunque la costruzione di un agente che consenta di implementare il ping, ossia il noto protocollo, basato su pacchetti ICMP, per l’analisi e la verifica di corretto comportamento di una rete.

Come detto nel precedente paragrafo, il primo passo, per la creazione di un qualunque agente, consiste nel decidere la struttura ereditaria dell’agente in questione. Tale decisione è molto personale, ma comunque deve generalmente essere legata al livello al quale l’agente deve operare e alle assunzioni da farsi a proposito delle funzionalità offerte dai livelli inferiori.

Il tipo più semplice di agente, con livello di trasporto di tipo datagram e non orientato alla connessione (connection-less), è la classe base denominata Agent/UDP: i generatori di traffico possono essere facilmente connessi ad agenti UDP ( [16] ). Per quei protocolli che invece desiderano usare un trasporto orientato alla connessione (connection-oriented), come TCP, si possono usare i vari tipi di agenti TCP ( [17] ). Infine, se si vuole introdurre un nuovo protocollo di trasporto o di “sub-trasporto”, la scelta probabilmente migliore consiste nell’usare direttamente la classe Agent come classe base, da cui derivare direttamente la classe specialistica corrispondente al nuovo agente da creare.

Nel nostro esempio, usiamo proprio la classe Agent come classe base, a partire dalla quale costruiremo un agente logicamente appartenente al livello IP (o leggermente al di sopra di esso).

Useremo le seguenti due definizioni di classi:

 

class ECHO_Timer;

 

class ECHO_Agent : public Agent

{

public:

ECHO_Agent();

int command(int argc, const char*const* argv);

protected:

void timeout(int);

void sendit();

double interval_;

ECHO_Timer echo_timer_;

};

 

class ECHO_Timer : public TimerHandler

{

public:

ECHO_Timer(ECHO_Agent *a) : TimerHandler() { a_ = a; }

protected:

virtual void expire(Event *e);

ECHO_Agent *a_;

};

 

La prima definizione è evidentemente quella del nostro nuovo agente, denominato ECHO_Agent. Per esso sono state previste, oltre al costruttore, tre funzioni membro (command(), dichiarata public, e poi timeout() e sendit(), dichiarate protected) e due variabili (interval, di tipo double, e echo_timer_, che fa riferimento alla classe ECHO_Timer). Vedremo più avanti ( [18] ) il codice corrispondente al costruttore della classe ECHO_Agent qui definito, nonché anche quello della funzione command().

La seconda classe (denominata ECHO_Timer) riguarda invece una nuova categoria di timer da usare appositamente per il nostro nuovo agente, così come vedremo meglio tra poco.

 

 

10.6.2 Metodi “recv()” e “timeout()”

 

Nel nostro esempio, scegliamo esplicitamente di non definire, per il nostro nuovo agente, alcun metodo recv(): vogliamo infatti ipotizzare che il nostro agente abbia bisogno solo di inviare pacchetti e non necessiti invece di gestire né pacchetti in ingresso né eventi di qualsiasi natura ( [19] ). Dato che scegliamo di non definire il metodo recv(), l’agente da noi definito userà la versione di tale metodo appartenente alla classe base da cui deriva: si tratterà, perciò, della funzione Agent::recv(), che è così definita (file agent.cc):

 

void Agent::recv(Packet* p, Handler*)

{

     if (app_)

           app_->recv(hdr_cmn::access(p)->size());

     /*

      * didn't expect packet (or we're a null agent?)

      */

     Packet::free(p);

}

 

Come si può vedere in questo codice, se esiste una applicazione che si sta appoggiando al nostro agente (cioè se il puntatore app_ a tale applicazione non risulta nullo), le viene consegnato l’eventuale pacchetto ricevuto (invocando il metodo recv() dell’applicazione stessa); se invece non c’è alcuna applicazione, allora il pacchetto viene semplicemente “liberato” (metodo Packet::free()), ossia scartato.

Il metodo timeout() è invece usato dal nostro agente per inviare periodicamente pacchetti di tipo REQUEST. In particolare, scegliamo di usare il metodo timeout() così come riportato qui di seguito, insieme ad un ulteriore metodo sendit() per l’invio concreto dei pacchetti generati:

 

void ECHO_Agent::timeout(int)

{

sendit();

echo_timer_.resched(interval_);

}

 

void ECHO_Agent::sendit()

{

Packet* p = allocpkt();

ECHOHeader *eh = ECHOHeader::access(p->bits());

eh->timestamp() = Scheduler::instance().clock();

send(p, 0); // Connector::send()

}

 

void ECHO_Timer::expire(Event *e)

{

a_->timeout(0);

}

 

Come si vede, il metodo timeout() semplicemente fa sì che la funzione sendit() venga eseguita ogni intervallo di intervall_ secondi (“resched” sta infatti per “rischedulazione”).

Il metodo sendit() crea un nuovo pacchetto con la maggior parte dei campi già settati tramite l’uso del metodo allocpkt(). Il pacchetto creato manca solo dell’etichetta temporale (timestamp): la chiamata della funzione access() fornisce allora una interfaccia strutturata ai campi dell’header del pacchetto e viene usata per individuare e settare il campo timestamp (o un qualsiasi altro campo se fosse necessario).

Osserviamo che questo agente usa il suo speciale header (ECHOHeader): l’argomento della creazione e dell’uso di header di pacchetti verrà descritto nel capitolo 12.

Al fine di inviare il pacchetto al nodo destinatario, viene utilizzato il metodo Connector::send(p,0) senza alcun manipolatore (handler), come testimoniato dal fatto che, mentre il primo argomento corrisponde al puntatore al pacchetto da inviare, il secondo argomento (appunto l’handler) viene posto direttamente al valore 0.

 

 

 

10.6.3 Collegamento dell’agente con OTcl

 

I metodi ed i meccanismi per stabilire i collegamenti (linkage) con l’OTcl sono stati già descritti dettagliatamente nel capitolo 3. Questo paragrafo si limita allora a fornire un veloce riassunto degli argomenti principali trattati nel suddetto capitolo, descrivendo le minime funzionalità necessarie per creare l’agente ping cui ci stiamo interessando e poterlo utilizzare negli script di simulazione.

Ci sono tre questioni che dobbiamo affrontare per collegare, in modo appropriato, il nostro nuovo agente con OTcl. Per prima cosa, abbiamo bisogno di stabilire una corrispondenza (mapping) tra il nome OTcl della nostra nuova classe e l’oggetto che viene creato quando, in OTcl, viene richiesta la creazione di una istanza di tale classe. Questa corrispondenza viene effettuata nel modo seguente:

 

static class ECHOClass : public TclClass {

public:

ECHOClass() : TclClass("Agent/ECHO") {}

TclObject* create(int argc, const char*const* argv)

{

return (new ECHO_Agent());

}

} class_echo;

 

Tramite questo codice, abbiamo creato un oggetto statico denominato class_echo. Il suo costruttore (eseguito immediatamente quando si esegue il simulatore) si occupa di inserire il nome “Agent/ECHO” nello spazio dei nomi OTcl.

Si nota che nella funzione appena scritta compare il metodo create(): questo metodo specifica come bisogna creare un oggetto C++ che riproduca esattamente l’oggetto di classe “Agent/ECHO” nel momento in cui quest’ultimo viene creato dall’interprete OTcl. Il metodo in questione restituisce un oggetto allocato dinamicamente, ossia un puntatore (che in questo caso punta ad un oggetto di classe TclObject). In pratica, si tratta del modo standard di creazione degli oggetti shadow (che cioè hanno una corrispondenza uno-ad-uno con oggetti creati dall’interprete OTcl) in C++.

Una volta effettuata la creazione di un oggetto, dobbiamo collegare le variabili membro C++ con le corrispondenti variabili nello spazio dei nomi OTcl, in modo che gli eventuali accessi alle variabili OTcl vengano tramutati in accessi alle variabili membro in C++. Supponiamo, per esempio, di volere che OTcl sia capace di modificare l’intervallo di invio dei pacchetti ping e la dimensione dei pacchetti stessi. Questa funzionalità viene garantita dal costruttore di classe, che avevamo lasciato sospeso in precedenza:

 

ECHO_Agent::ECHO_Agent() : Agent(PT_ECHO)

{

bind_time("interval_", &interval_);

bind("packetSize_", &size_);

}

 

Come si vede, le variabili C++ denominate interval_ e size_ sono linkate con le variabili OTcl denominate rispettivamente interval_ e packetSize_ ( [20] ). Ogni operazione di lettura o modifica delle variabili OTcl corrisponderà quindi ad una analoga operazione sulle variabili membro C++. Per fare questo, si usa il metodo bind(), di cui si parla dettagliatamente nel paragrafo 3.4.2.

Sempre nel codice appena riportato, notiamo anche la presenza di una costante PT_ECHO che viene passata al costruttore della classe Agent (cioè quindi alla funzione Agent::Agent()): in tal modo, il metodo Agent::allocpkt() può settare il campo riportante il tipo di pacchetto usato dal trace support ( [21] ). In questo caso, PT_ECHO rappresenta un nuovo tipo di pacchetto e deve essere definito nel file ~ns/trace.h ( [22] ).

A questo punto, una volta creato l’oggetto di interesse ed effettuato il binding delle variabili, dobbiamo implementare metodi in C++ che possano essere invocati anche dall’OTcl ( [23] ). Si tratta generalmente di funzioni di controllo, che avviano, terminano o modificano il comportamento dell’oggetto. Nel nostro esempio, vogliamo poter avviare, tramite una direttiva start in OTcl, l’agente che invia i pacchetti ECHO REQUEST. Questo risultato può essere ottenuto con il seguente codice:

 

int ECHO_Agent::command(int argc, const char*const* argv)

{

if (argc == 2)

{

if (strcmp(argv[1], "start") == 0)

{

timeout(0);

return (TCL_OK);

}

}

return (Agent::command(argc, argv));

}

 

Tramite questo codice, il metodo start disponibile in OTcl semplicemente richiama la funzione membro C++ denominata timeout(), la quale avvia la generazione del primo pacchetto (argomento passato pari a 0) e schedula quella del pacchetto successivo, così come abbiamo visto in precedenza. In questo modo, quindi, l’uso congiunto delle funzioni per l’invio dei pacchetti e di quelle per la gestione dei timeout consente di simulare l’invio di un qualsivoglia numero di pacchetti.

Facciamo notare che la classe che abbiamo creato è talmente semplice da non includere un meccanismo esplicito per bloccare la generazione dei pacchetti. Si tratta di una semplificazione ovviamente da non adottare nelle proprie simulazioni, in quanto bisognerebbe arrestare il sistema per poter interrompere le simulazioni stesse.

E’ importante anche notare il modo con cui si comporta la funzione membro ECHO_Agent::command() prima riportata: qualora il numero di argomenti (contenuti nella variabile argc) forniti in ingresso non sia pari a 2, tale funzione fa in modo che venga chiamata la funzione command() della classe imparentata di livello superiore (parent class), che in questo caso è la classe Agent; viene cioè invocata la funzione Agent::command(), alla quale vengono passati gli stessi argomenti. Il procedimento è ricorsivo, nel senso che, in assenza di riscontri per la sequenza di argomenti passata in ingresso, viene ricorsivamente richiamata la funzione command della classe di livello superiore, fino alla classe base, che eventualmente interromperà il processo in modo opportuno. Se, invece, il numero di argomenti passati alla funzione ECHO_Agent::command() è pari a due, tale funzione svolge normalmente il suo compito e restituisce la stringa TCL_OK per segnalare che tutto è andato bene.

Si tratta di un concetto ovviamente generale, valido cioè per la funzione command() di qualsiasi tipo di oggetto in NS: qualora un dato comando OTcl (ad esempio start o stop o connect o altri ) non sia stato definito nella funzione command() cui il comando stesso è stato impartito, si passa a “consultare” la funzione command() della classe superiore e così via fino alla classe di livello più alto: se il comando in questione non viene mai trovato, la simulazione viene arrestata; se invece il comando viene individuato, vengono eseguite le operazioni espressamente previste per esso.

 

 

10.6.4 Usare l’agente tramite OTcl

 

L’agente che abbiamo creato dovrà guidare la nostra simulazione, per cui dovremo istanziarlo e poi agganciarlo ad un nodo. Assumiamo allora che un oggetto simulatore ($simulator) e, successivamente, un oggetto nodo ($node) siano stati già creati. Allora, il seguente codice crea il nostro agente “ping” e lo attacca al nodo desiderato:

 

set echoagent [new Agent/ECHO]

$simulator attach-agent $node $echoagent

 

Se ora vogliamo impostare l’intervallo di invio dei pacchetti o la dimensione dei pacchetti o altri campi, dobbiamo semplicemente far eseguire un codice del tipo seguente:

 

$echoagent set dst_ $dest

$echoagent set fid_ 0

$echoagent set prio_ 0

$echoagent set flags_ 0

$echoagent set interval_ 1.5

$echoagent set packetSize_ 1024

 

Per far partire la simulazione, dobbiamo infine scrivere

 

$echoagent start

 

Questa istruzione fa si che il nostro agente generi, ogni 1.5 secondi, un pacchetto, di dimensione 1024 byte, destinato al nodo denominato “$dest”.

Possiamo ora fare alcune importanti considerazioni:

 

 

 

10.7 Le Agent API

 

Le applicazioni che vengono simulate in NS possono essere implementate “appoggiandole” agli agenti di trasporto. Nel capitolo 32 viene descritto come le applicazioni usino le cosiddette API (Application Programming Interface) per usufruire dei servizi forniti dai suddetti agenti.

 

 

10.8 Vari tipi di agenti

 

La classe Agent costituisce la classe base da cui vengono derivati diversi tipi di altri agenti, come ad esempio quelli TCP o UDP o RTP o tanti altri. I metodi della classe Agent sono descritti nel prossimo paragrafo. Elenchiamo invece adesso i parametri di configurazione:

 

·      fid_               Flowid

·      prio_             Priority

·      agent_addr_   Address of this agent

·      agent_port_   Port adress of this agent

·      dst_addr_      Destination address for the agent

·      dst_port_       Destination port address for the agent

·      flags_

·      ttl_               TTL (defaults to 32)

 

Non ci sono variabili di stato specifiche per la classe generica Agent.

Altri oggetti derivati dalla classe Agent sono i seguenti:

 

·      Null Objects Null objects are a subclass of agent objects that implement a traffic sink. They inherit all of the generic agent object functionality. There are no methods specific to this object. The state variables are “sport_” e “dport_”;

 

·      LossMonitor Objects LossMonitor objects are a subclass of agent objects that implement a traffic sink which also maintains some statistics about the received data e.g., number of bytes received, number of packets lost etc. They inherit all of the generic agent object functionality.

 

$lossmonitor clear

Resets the expected sequence number to -1.

 

State Variables are:

nlost_ Number of packets lost.

npkts_ Number of packets received.

bytes_ Number of bytes received.

lastPktTime_ Time at which the last packet was received.

expected_ The expected sequence number of the next packet.

 

·      TCP objects TCP objects are a subclass of agent objects that implement the BSD Tahoe TCP transport protocol as described in paper: "Fall, K., and Floyd, S. Comparisons of Tahoe, Reno, and Sack TCP. December 1995." URL ftp://ftp.ee.lbl.gov/papers/sacks.ps.Z. They inherit all of the generic agent functionality. Configuration Parameters are:

 

window_ The upper bound on the advertised window for the TCP connection.

maxcwnd_ The upper bound on the congestion window for the TCP connection. Set to zero to ignore. (This is the default.)

windowInit_ The initial size of the congestion window on slow-start.

windowOption_ The algorithm to use for managing the congestion window.

windowThresh_ Gain constant to exponential averaging filter used to compute awnd (see below). For investigations of different window-increase algorithms.

overhead_ The range of a uniform random variable used to delay each output packet. The idea is to insert random delays at the source in order to avoid phase effects, when desired [see Floyd, S., and Jacobson, V. On Traffic Phase Effects in Packet-Switched Gateways. Internetworking: Research and Experience, V.3 N.3, September 1992. pp. 115-156 ]. This has only been implemented for the Tahoe ("tcp") version of tcp, not for tcp-reno. This is not intended to be a realistic model of CPU processing overhead.

ecn_ Set to true to use explicit congestion notification in addition to packet drops to signal congestion. This allows a Fast Retransmit after a quench() due to an ECN (explicit congestion notification) bit.

packetSize_ The size in bytes to use for all packets from this source.

tcpTick_ The TCP clock granularity for measuring roundtrip times. Note that it is set by default to the non-standard value of 100ms.

bugFix_ Set to true to remove a bug when multiple fast retransmits are allowed for packets dropped in a single window of data.

maxburst_ Set to zero to ignore. Otherwise, the maximum number of packets that the source can send in response to a single incoming ACK.

slow_start_restart_ Set to 1 to slow-start after the connection goes idle. On by default.

 

Defined Constants are:

 

MWS The Maximum Window Size in packets for a TCP connection. MWS determines the size of an array in tcp-sink.cc. The default for MWS is 1024 packets. For Tahoe TCP, the "window" parameter, representing the re-ceiver’s advertised window, should be less than MWS-1. For Reno TCP, the "window" parameter should be less than (MWS-1)/2.

 

State Variables are:

 

dupacks_ Number of duplicate acks seen since any new data was acknowledged.

seqno_ Highest sequence number for data from data source to TCP.

t_seqno_ Current transmit sequence number.

ack_ Highest acknowledgment seen from receiver. cwnd_ Current value of the congestion window.

awnd_ Current value of a low-pass filtered version of the congestion window. For investigations of different window-increase algorithms.

ssthresh_ Current value of the slow-start threshold.

rtt_ Round-trip time estimate.

srtt_ Smoothed round-trip time estimate.

rttvar_ Round-trip time mean deviation estimate.

backoff_ Round-trip time exponential backoff constant.

 

·      TCP/Reno Objects TCP/Reno objects are a subclass of TCP objects that implement the Reno TCP transport protocol de-scribed in paper: "Fall, K., and Floyd, S. Comparisons of Tahoe, Reno, and Sack TCP. December 1995." URL ftp://ftp.ee.lbl.gov/papers/sacks.ps.Z. There are no methods, configuration parameters or state variables specific to this ob-ject.

 

·      TCP/Newreno Objects TCP/Newreno objects are a subclass of TCP objects that implement a modified version of the BSD Reno TCP transport protocol. There are no methods or state variables specific to this object. Configuration Parameters are:

 

newreno_changes_ Set to zero for the default New Reno described in "Fall, K., and Floyd, S. Comparisons of Tahoe, Reno, and Sack TCP. December 1995". Set to 1 for additional New Reno algorithms [see Hoe, J., Improving the Start-up Behavior of a Congestion Control Scheme for TCP. in SIGCOMM 96, August 1996, pp. 270-280. URL http://www.acm.org/sigcomm/sigcomm96/papers/hoe.html.]; this includes the estimation of the ssthresh parame-ter during slow-start.

 

·      TCP/Vegas Objects There are no methods or configuration parameters specific to this object. State variables are:

 

v_alpha_

v_beta_

v_gamma_

v_rtt_

 

·      TCP/Sack1 Objects TCP/Sack1 objects are a subclass of TCP objects that implement the BSD Reno TCP transport protocol with Selective Acknowledgement Extensions described in "Fall, K., and Floyd, S. Comparisons of Tahoe, Reno, and Sack TCP. December 1995". URL ftp://ftp.ee.lbl.gov/papers/sacks.ps.Z. They inherit all of the TCP object functional-ity. There are no methods, configuration parameters or state variables specific to this object.

 

·      TCP/FACK Objects TCP/Fack objects are a subclass of TCP objects that implement the BSD Reno TCP transport protocol with Forward Acknowledgement congestion control. They inherit all of the TCP object functionality. There are no methods or state variables specific to this object.

Configuration Parameters are:

ss-div4 Overdamping algorithm. Divides ssthresh by 4 (instead of 2) if congestion is detected within 1/2 RTT of slow-start. (1=Enable, 0=Disable)

rampdown Rampdown data smoothing algorithm. Slowly reduces congestion window rather than instantly halving it. (1=Enable, 0=Disable)

 

·      TCP/FULLTCP Objects This section has not yet been added here. The implementation and the configuration parameters are described in paper: "Fall, K., Floyd, S., and Henderson, T., Ns Simulator Tests for Reno FullTCP. July, 1997." URL ftp://ftp.ee.lbl.gov/papers/fulltcp.ps.

 

·      TCPSINK Objects TCPSink objects are a subclass of agent objects that implement a receiver for TCP packets. The simu-lator only implements "one-way" TCP connections, where the TCP source sends data packets and the TCP sink sends ACK packets. TCPSink objects inherit all of the generic agent functionality. There are no methods or state variables specific to the TCPSink object. Configuration Parameters are:

 

packetSize_ The size in bytes to use for all acknowledgment packets.

maxSackBlocks_ The maximum number of blocks of data that can be acknowledged in a SACK option. For a receiver that is also using the time stamp option [RFC 1323], the SACK option specified in RFC 2018 has room to include three SACK blocks. This is only used by the TCPSink/Sack1 subclass. This value may not be increased within any particular TCPSink object after that object has been allocated. (Once a TCPSink object has been allocated, the value of this parameter may be decreased but not increased).

 

·      TCPSINK/DELACK Objects DelAck objects are a subclass of TCPSink that implement a delayed-ACK receiver for TCP packets. They inherit all of the TCPSink object functionality. There are no methods or state variables specific to the DelAck object. Configuration Parameters are:

 

interval_ The amount of time to delay before generating an acknowledgment for a single packet. If another packet arrives before this time expires, generate an acknowledgment immediately.

 

·      TCPSINK/SACK1 Objects TCPSink/Sack1 objects are a subclass of TCPSink that implement a SACK receiver for TCP packets. They inherit all of the TCPSink object functionality. There are no methods, configuration parameters or state variables specific to this object.

 

·      TCPSINK/SACK1/DELACK Objects TCPSink/Sack1/DelAck objects are a subclass of TCPSink/Sack1 that implement a delayed-SACK receiver for TCP packets. They inherit all of the TCPSink/Sack1 object functionality. There are no methods or state variables specific to this object. Configuration Parameters are:

 

interval_ The amount of time to delay before generating an acknowledgment for a single packet. If another packet arrives before this time expires, generate an acknowledgment immediately.

 

 

10.9 Comandi principali

 

Riportiamo alcuni dei più importanti comandi, legati agli agenti, da usarsi negli script di simulazione:

 

Questo comando aggancia l’agente <agent> al nodo <node>. Si assume chiaramente che l’agente e il nodo in questione siano stati già creati. Nel caso dell’agente, ad esempio, si assume che sia stata utilizzata una istruzione del tipo set agent [new Agent/AgentType], dove Agent/AgentType individua la definizione della classe dell’agente.

 

Questo comando restituisce il numero di porta (port number) al quale è “localizzato” l’agente

 

Questo comando restituisce il port number dell’agente cui è destinato il flusso prodotto dall’agente $agent. Quando una qualsiasi connessione viene impostata tra due nodi, ciascun agente memorizza il destination port (porta dell’agente destinazione) nella propria variabile denominata dst_port_.

 

This commands attaches an application of type <s_type> to the agent. A handle to the application object is returned. Also note that the application type must be defined as a packet type in packet.h.

 

This used to be the procedure to attach source of type <s_type> to the agent. But this is obsolete now. Use attach-app (described above) instead.

 

Attaches a token bucket filter (tbf) to the agent.

 

Sets up a connection between the src and dst agents.

 

This sets up a complete connection between two agents. First creates a source of type <srctype> and binds it to <src>. Then creates a destination of type <dsttype> and binds it to <dst>. Finally connects the src and dst agents and returns a handle to the source agent.

 

This command is exactly similar to create-connection described above. But instead of returning only the source-agent, this returns a list of source and destination agents.

 

This is an internal method that actually sets up an unidirectional connection between the <src> agent and <dst> agent. It simply sets the destination address and destination port of the <src> as <dst>’s agent-address and agent-port. The “connect" described above calls this method twice to set up a bi-directional connection between the src and dst.

 

This is an internal procedure used to inform users of the backward compatibility issues resulting from the upgrade to 32-bit addressing space currently used in ns.

 

This attaches the <file> to the agent to allow nam-tracing of the agent events.

 

 

 



[1] Nel capitolo 12 (paragrafo 12.2.3) vedremo che ogni pacchetto nel simulatore possiede un “common header” comprendente una serie di campi, tra cui ts_, ptype_, uid_, size_ e iface_. L’uso dettagliato di questi campi sarà descritto in quella sede.

[2] Ricordiamo che il concetto di “sovrapposizione delle funzioni” è il seguente: ogni classe comprende delle funzioni membro; una classe derivata può eventualmente ridefinire una o più funzioni membro della classe base da cui discende, nel qual caso si parla appunto di “sovrapposizione di funzioni”.

[3] Facciamo osservare come alcuni nomi di funzioni riportati in questo listato possano essere soggetti a variazioni nelle nuove versioni di ns

[4] Questi discorsi potrebbero comunque risultare più chiari dalla lettura dei capitoli 28 e soprattutto 29, in cui si descrivono, rispettivamente, gli agenti UDP e TCP

[5] Paragrafo 22.5

[6] Paragrafo 12.1

[7] Paragrafo 32.4

[8] Ad ogni modo, per maggiori dettagli sulle Applicazioni si veda il capitolo 32.

[9] Si veda, in proposito, il paragrafo 12.2.4

[10] Per maggiori dettagli sul metodo Connector::send(), consigliamo di consultarne direttamente il listato nel file Connector.h.

[11] A quest’ultimo proposito, è importante notare che l’operatore “::” del C++ viene qui usato per evitare di richiamare erroneamente la funzione TcpSimpleAgent::send (che è anch’essa definita);

[12] Paragrafo 10.6.1

[13] Paragrafo 10.6.2

[14] Paragrafo 10.6.3

[15] Paragrafo 10.6.4

[16] Si veda, in proposito, il capitolo 28 specifico degli agenti UDP ed il capitolo 32 per la descrizione del funzionamento delle applicazioni e dei generatori di traffico.

[17] Capitolo 29

[18] Paragrafo 10.6.3

[19] Si tratta ovviamente di una semplificazione poco realistica, dato che un agente ICMP ECHO REQUEST avrà sempre la necessità di processare i messaggi ECHO REPLY.

[20] Notiamo una cosa a questo proposito: mentre la variabile interval_ è stata espressamente definita nella nuova classe ECHO_Agent, la variabile size_ non ha subito la stessa sorte, il che significa che si tratta della variabile ereditata dalla classe base Agent. Essa, comunque, rappresenta la dimensione dei pacchetti inviati, che tra l’altro è riportata nell’omonimo campo size_ del common header di tali pacchetti.

[21] Paragrafo 22.5

[22] Paragrafo 22.4

[23] Paragrafo 3.4.4

[24] Per ulteriori dettagli su questo importante concetto, si veda il paragrafo 32.2.2

 

 


Continua con:        Indice       Capitolo 9      Capitolo 11


Aggiornamento: 6 maggio 2001

home