4.1 Más de dos nodos
En este paso, vamos a dar un gran salto: crear varios módulos tic
y conectarlos en una red.
Por ahora, vamos a mantener su funcionamiento simple:
- Uno de los nodos generará un mensaje.
- Los otros nodos continuarán pasando el mensaje alrededor en direcciones aleatorias hasta que llegue a un nodo de destino predefinido (
tic[3]
).
4.1.1 Cambios en el modelo
Uso de múltiples módulos tic (seis en total):
- Los módulos están definidos como un vector de módulos (
tic[6]
). - Esto significa que se crean automáticamente seis módulos con nombres
tic[0]
,tic[1]
,tic[2]
, etc.
Uso de vectores de puertas (in[]
y out[]
):
- Las puertas (in y out) se definen como vectores para permitir múltiples conexiones por cada módulo.
- El tamaño del vector se determina automáticamente al conectar los módulos.
4.1.2 Definición de red (NED)
En el archivo NED (tictoc10.ned
) se declaran las puertas como vectores con el uso de []
.
simple Txc10
{
parameters:
@display("i=block/routing");
gates:
input in[]; // declare in[] and out[] to be vector gates
output out[];
}
Luego, se define la red con varios nodos conectados entre sí:
network Tictoc10
{
submodules:
tic[6]: Txc10;
connections:
tic[0].out++ --> { delay = 100ms; } --> tic[1].in++;
tic[0].in++ <-- { delay = 100ms; } <-- tic[1].out++;
tic[1].out++ --> { delay = 100ms; } --> tic[2].in++;
tic[1].in++ <-- { delay = 100ms; } <-- tic[2].out++;
tic[1].out++ --> { delay = 100ms; } --> tic[4].in++;
tic[1].in++ <-- { delay = 100ms; } <-- tic[4].out++;
tic[3].out++ --> { delay = 100ms; } --> tic[4].in++;
tic[3].in++ <-- { delay = 100ms; } <-- tic[4].out++;
tic[4].out++ --> { delay = 100ms; } --> tic[5].in++;
tic[4].in++ <-- { delay = 100ms; } <-- tic[5].out++;
}
La topología resultante queda de esta forma:
4.1.3 Explicación del Código
- Se crean 6 módulos tic en un arreglo llamado
tic[]
. - Cada módulo tiene múltiples entradas (
in[]
) y salidas (out[]
). - Los módulos están conectados con retardos (
delay = 100ms
). - El mensaje se crea en
tic[0]
y se envía al siguiente nodo al azar.
4.1.3.1 Generación de mensaje inicial en tic[0]
En este modelo, solo el módulo tic[0]
es el encargado de generar el mensaje inicial que será enviado y retransmitido por los demás nodos.
En la función initialize()
del módulo se TxC10
utiliza la función getIndex()
para identificar si el módulo actual es tic[0]
.
El proceso arranca mediante tic[0]
programando el envío del mensaje a si mismo, en el tiempo 0.0
, por lo cual se activa el método handleMessage()
void Txc10::initialize()
{
if (getIndex() == 0) {
// Boot the process scheduling the initial message as a self-message.
char msgname[20];
snprintf(msgname, sizeof(msgname), "tic-%d", getIndex());
cMessage *msg = new cMessage(msgname);
scheduleAt(0.0, msg);
}
}
4.1.3.2 Reenvio de mensajes al azar
El código principal que maneja este comportamiento está en la función forwardMessage()
.
Se selecciona aleatoriamente una puerta de salida para reenviar el mensaje.
void Txc10::forwardMessage(cMessage *msg)
{
// Seleccionar aleatoriamente una puerta de salida del vector `out[]`
int n = gateSize("out");
int k = intuniform(0, n-1); // Selección aleatoria de un índice de puerta
EV << "Reenviando mensaje " << msg << " por la puerta out[" << k << "]\n";
send(msg, "out", k); // Enviar el mensaje por la puerta seleccionada
}
4.1.3.3 Eliminación del mensaje en tic[3]
Cuando un mensaje llega al módulo tic[3]
, se elimina, lo cual simula que ha llegado a su destino.
if (getIndex() == 3) {
// Message arrived.
EV << "Message " << msg << " arrived.\n";
delete msg;
}
4.1.4 Resultados de la simulacíon
- En la consola, verás mensajes indicando el envío de mensajes entre nodos:
- En la gráfica de secuencia (Sequence Chart), podrás observar cómo el mensaje viaja entre los nodos.
- Cuando un mensaje llega a
tic[3]
, se elimina y se muestra un mensaje indicando su eliminación.
4.1.5 Código Fuente
4.2 Canales y definiciones de tipo interno
Nuestra definición de red se está volviendo bastante compleja y larga, especialmente la sección de conexiones. Vamos a simplificarla. Lo primero que notamos es que las conexiones siempre usan el mismo parámetro de retardo (delay
).
Es posible crear tipos personalizados para las conexiones (llamados canales) de manera similar a como se crean los módulos simples (simple modules
).
Vamos a crear un tipo de canal que especifica el parámetro de retardo y usaremos ese tipo para todas las conexiones en la red.
4.2.1 Definición de red (tictoc11.ned
)
network Tictoc11
{
types:
channel Channel extends ned.DelayChannel {
delay = 100ms;
}
submodules:
tic[6]: Txc11;
connections:
tic[0].out++ --> Channel --> tic[1].in++;
tic[0].in++ <-- Channel <-- tic[1].out++;
tic[1].out++ --> Channel --> tic[2].in++;
tic[1].in++ <-- Channel <-- tic[2].out++;
tic[1].out++ --> Channel --> tic[4].in++;
tic[1].in++ <-- Channel <-- tic[4].out++;
tic[3].out++ --> Channel --> tic[4].in++;
tic[3].in++ <-- Channel <-- tic[4].out++;
tic[4].out++ --> Channel --> tic[5].in++;
tic[4].in++ <-- Channel <-- tic[5].out++;
}
4.2.2 Explicación:
- Hemos definido un nuevo tipo de canal llamado
Channel
dentro de la seccióntypes
. - Este canal se basa en
ned.DelayChannel
, que es un tipo incorporado de OMNeT++. - Este tipo personalizado (
Channel
) solo es visible dentro de la redTictoc11
. - Simplifica las conexiones al permitirnos especificar solo
Channel
en lugar de definir el retardo (delay
) en cada conexión.
4.2.3 Código Fuente
4.3 Uso de conexiones bidireccionales
Si observamos la sección de conexiones, nos damos cuenta de que cada par de nodos está conectado con dos conexiones: una para cada dirección.
OMNeT++ permite usar conexiones bidireccionales (inout gates
), así que vamos a implementarlas.
4.3.2 Definición del módulo (Txc12.ned
)
simple Txc12
{
parameters:
@display("i=block/routing");
gates:
inout gate[]; // Declarar conexiones bidireccionales
}
4.3.3 Definición de red (tictoc12.ned
)
network Tictoc12
{
types:
channel Channel extends ned.DelayChannel {
delay = 100ms;
}
submodules:
tic[6]: Txc12;
connections:
tic[0].gate++ <--> Channel <--> tic[1].gate++;
tic[1].gate++ <--> Channel <--> tic[2].gate++;
tic[1].gate++ <--> Channel <--> tic[4].gate++;
tic[3].gate++ <--> Channel <--> tic[4].gate++;
tic[4].gate++ <--> Channel <--> tic[5].gate++;
}
4.3.4 Modificación del código en C++ (txc12.cc
)
void Txc12::forwardMessage(cMessage *msg)
{
int n = gateSize("gate");
int k = intuniform(0, n - 1);
EV << "Forwarding message " << msg->getName() << " on gate[" << k << "]\n";
send(msg, "gate$o", k);
}
4.3.5 Explicación:
- Ahora usamos puertas
inout
en lugar deinput
youtput
. - El sufijo
$o
se usa para identificar la parte de salida de una puerta bidireccional. - El sufijo
$i
se usa para identificar la parte de entrada de una puerta bidireccional (no utilizado en este ejemplo). - OMNeT++ permite definir conexiones bidireccionales con
<-->
, simplificando la topología.
4.3.6 Código Fuente
4.4 Definición de nuestra propia clase de mensaje
En este paso, vamos a eliminar la dependencia de que el mensaje siempre llegue a tic[3]
y permitiremos que el destino se seleccione al azar.
Agregaremos la dirección de destino como un campo dentro del mensaje.
La mejor manera de hacerlo es crear una subclase de cMessage y agregar la dirección de destino (destination
) como un miembro de datos (variable) dentro de esa clase.
Escribir manualmente la clase de mensaje sería un proceso tedioso porque normalmente requiere mucho código repetitivo y estructural (boilerplate code).
En lugar de hacerlo a mano, vamos a permitir que OMNeT++ genere la clase por nosotros.
La especificación de la clase de mensaje se escribe en un archivo llamado tictoc13.msg
.
4.4.1 Definición del mensaje (tictoc13.msg
)
message TicTocMsg13
{
int source;
int destination;
int hopCount = 0;
}
4.4.2 Generación del código para la clase mensaje
OMNeT++ genera automáticamente las clases necesarias (tictoc13_m.h
y tictoc13_m.cc
) al compilar el proyecto con el comando opp_msgc
.
4.4.3 Modificación del código en C++ (txc13.cc
)
Primero, incluye el archivo generado:
#include "tictoc13_m.h"
Luego, modificamos la función generateMessage()
para crear el mensaje y configurar sus campos.
TicTocMsg13 *generateMessage(int src)
{
int dest = intuniform(0, 5); // Generar un destino aleatorio
char msgname[20];
sprintf(msgname, "tic-%d-to-%d", src, dest);
TicTocMsg13 *msg = new TicTocMsg13(msgname);
msg->setSource(src);
msg->setDestination(dest);
return msg;
}
Y actualizamos handleMessage()
para verificar el destino del mensaje.
void Txc13::handleMessage(cMessage *msg)
{
TicTocMsg13 *ttmsg = check_and_cast<TicTocMsg13 *>(msg);
if (ttmsg->getDestination() == getIndex()) {
EV << "Message " << ttmsg->getName() << " arrived at its destination.\n";
delete ttmsg;
// Generar un nuevo mensaje con destino aleatorio
TicTocMsg13 *newMsg = generateMessage(getIndex());
forwardMessage(newMsg);
}
else {
forwardMessage(ttmsg); // Reenviar el mensaje si no ha llegado al destino
}
}
4.4.4 Explicación:
- Creamos un nuevo tipo de mensaje
TicTocMsg13
con campossource
,destination
yhopCount
. - Usamos la función
generateMessage()
para crear un nuevo mensaje con un destino aleatorio cada vez que llega uno a su destino. - La función
check_and_cast<>()
asegura que el mensaje recibido es del tipo correcto (TicTocMsg13
).
4.4.5 Resultado de la Simulación
Al ejecutar la simulación podemos ver que cuando un mensaje llega a su destino, el nodo destino genera otro mensaje con un destino aleatorio.
Al hacer click en el mensaje es posible ver el contenido en la ventana de inspección. Al hacer doble click se abre una nueva ventana, la cual muestra información valiosa; los campos del mensaje se pueden ver en la pagina de contenido:
4.4.6 Código Fuente
4.5 Ejercicio sugerido
- Actualmente, solo hay un mensaje en circulación en cada momento: los nodos solo generan un nuevo mensaje cuando reciben otro.
- Modifica la clase para que genere mensajes periódicamente.
- El intervalo entre mensajes debe ser un parámetro del módulo, retornando valores distribuidos exponencialmente.