
4 Revisión del concepto
4.1 Abstracciones clave
4.1.1 Nodo
4.1.2 Aplicación
4.1.3 Canal
4.1.4 Dispositivo de red
4.1.5 Ayudantes topológicos
4.2 Primer script de ns-3
4.2.1 Código base
4.2.2 Módulos conectables
4.2.3 Espacio de nombres ns3
4.2.4 Registro
4.2.5 Función principal
4.2.6 Uso de ayudantes topológicos
4.2.7 Uso de la aplicación
4.2.8 Simulador
4.2.9 Compilación de su script
4.3 Código fuente de ns-3
Capítulo 4
Revisión del concepto
Lo primero que debemos hacer antes de comenzar a estudiar o escribir código en ns-3 es explicar algunos conceptos y abstracciones básicos del sistema. Mucho de esto puede parecer obvio para algunos, pero recomendamos dedicar tiempo a leer esta sección para asegurarse de que comienza sobre una base sólida.
4.1 Abstracciones clave
En esta sección, revisaremos algunos términos que suelen usarse en la red, pero que tienen un significado específico en ns-3.
4.1.1 Nodo
En la jerga de Internet, un dispositivo informático conectado a la red se llama host o, a veces, sistema final. Debido a que ns-3 es un simulador de redes y no un simulador de Internet, no utilizamos intencionadamente el término host, ya que está estrechamente relacionado con Internet y sus protocolos. En su lugar, usamos un término más general, también utilizado por otros simuladores, que proviene de la teoría de grafos: nodo (node).
En ns-3, la abstracción básica de un dispositivo informático se llama nodo. Esta abstracción está representada en C++ por la clase Node. La clase NodeNode (nodo) proporciona métodos para gestionar las representaciones de los dispositivos informáticos en las simulaciones.
Debes entender Nodo como un ordenador al que le añadirás funcionalidad. Agregarás cosas como aplicaciones, pilas de protocolos y tarjetas periféricas con controladores que permiten al ordenador realizar trabajo útil. Utilizamos el mismo modelo básico en ns-3.
4.1.2 Aplicación
Por lo general, el software de computadora se divide en dos grandes clases. El software de sistema organiza diversos recursos informáticos como la memoria, los ciclos del procesador, el disco, la red, etc., de acuerdo con algún modelo de computación. El software de sistema generalmente no utiliza estos recursos para realizar tareas que benefician directamente al usuario. Para lograr un objetivo específico, el usuario normalmente inicia una aplicación que obtiene y utiliza recursos controlados por el software de sistema.
A menudo, la línea divisoria entre el software de sistema y el software de aplicación se establece al cambiar el nivel de privilegios, lo que ocurre en las trampas del sistema operativo. En ns-3 no existe un concepto real de sistema operativo y, por lo tanto, no hay concepciones de niveles de privilegios o llamadas de sistema. Sin embargo, tenemos la idea de la aplicación. Al igual que en el "mundo real", para realizar tareas las aplicaciones de software funcionan en computadoras, las aplicaciones de ns-3 funcionan en nodos de ns-3 para gestionar simulaciones en un mundo simulado.
En ns-3, la abstracción básica para un programa de usuario que genera alguna actividad para la modelación es la aplicación. Esta abstracción está representada en C++ por la clase Application. La clase Application proporciona métodos para gestionar en las simulaciones las representaciones de nuestra versión de aplicaciones a nivel de usuario. Se espera que los desarrolladores especialicen la clase Application en términos de programación orientada a objetos para crear nuevas aplicaciones. En esta guía, utilizaremos especializaciones de la clase Application, llamadas UdpEchoClientApplication y UdpEchoServerApplication. Como era de esperar, estas aplicaciones constituyen un conjunto de aplicaciones cliente/servidor utilizadas para generar y simular el eco de paquetes de red.
4.1.3 Canal
En el mundo real, se puede conectar una computadora a una red. A menudo, los medios a través de los cuales se transmiten datos en estas redes se llaman canales. Cuando conectas un cable Ethernet a un enchufe en la pared, estás conectando la computadora a un canal de comunicación Ethernet. En el mundo simulado de ns-3, un nodo se conecta a un objeto que representa un canal de comunicación. Aquí, la principal abstracción de la subred de comunicación se llama canal y está representada en C++ por la clase Channel (canal).
Clase ChannelChannel proporciona métodos para gestionar la interacción entre objetos de la subred y conectar nodos a ellos. Los canales también pueden ser especializados por los desarrolladores en términos de programación orientada a objetos. La especialización de un canal puede modelar algo tan simple como un cable. Un canal especializado también puede modelar cosas complejas como un gran conmutador Ethernet o un espacio tridimensional lleno de obstáculos en el caso de redes inalámbricas.
Usaremos en esta guía versiones especializadas del canal llamadas CsmaChannelCsmaChannel, PointToPointChannelPointToPointChannel y WifiChannelWifiChannel. CsmaChannel, por ejemplo, modela una versión de la subred de comunicación que implementa un medio de comunicación de acceso múltiple con detección de portadora. Esto nos da funcionalidades similares a Ethernet.
4.1.4 Dispositivo de red
Antes, si querías conectar una computadora a una red, necesitabas comprar un cable de red específico y un dispositivo de hardware, llamado (en la terminología de PC) tarjeta periférica, que debía instalarse en la computadora. Si en la tarjeta periférica se implementaban algunas funciones de red, se les llamaba tarjetas de interfaz de red o tarjetas de red. Hoy en día, la mayoría de las computadoras vienen con hardware de interfaz de red integrado, y los usuarios no los ven como dispositivos separados.
Una tarjeta de red no funcionará sin un controlador de software que gestione su hardware. En Unix (o Linux), parte del hardware periférico se clasifica como dispositivo. Los dispositivos se gestionan mediante controladores de dispositivos (device drivers), y los dispositivos de red (NIC) se gestionan utilizando controladores de dispositivos de red (network device drivers) y tienen el nombre genérico de dispositivos de red (net devices). En Unix y Linux, accede a los dispositivos de red con nombres como por ejemplo eth0.
En ns-3, la abstracción del dispositivo de red abarca tanto el controlador de software como el hardware simulado. Al simular, el dispositivo de red se 'instala' en el nodo para permitirle comunicarse con otros nodos a través de canales. Al igual que en un ordenador real, un nodo puede estar conectado a varios canales a través de múltiples dispositivos. NetDevices.
La abstracción de la red del dispositivo se presenta en C++ mediante la clase NetDevice. La clase NetDevice proporciona métodos para gestionar las conexiones con los objetos Node y Channel; y puede ser especializada por desarrolladores en términos de programación orientada a objetos. En esta guía, utilizaremos varias versiones especializadas de NetDevice denominadas CsmaNetDevice, PointToPointNetDevice y WifiNetDevice. Al igual que el adaptador de red Ethernet está diseñado para operar con la red Ethernet, CsmaNetDevice está diseñado para funcionar con CsmaChannel, PointToPointNetDevice está diseñado para funcionar con PointToPointChannel, y WifiNetDevice — está diseñado para operar con WifiChannel.
4.1.5 Ayudantes topológicos
En una red real, encontrarás computadoras host con tarjetas de red añadidas (o integradas). En ns-3, diríamos que verás nodos con NetDevices conectados. En una red simulada grande, necesitarás organizar conexiones entre muchos objetos. Nodo, NetDevice y Channel.
Dado que conectar NetDevices a nodos, NetDevices a canales, asignar direcciones IP, etc. en ns-3 es una tarea común, para facilitar esto tanto como sea posible, proporcionamos lo que llamamos ayudantes topológicos. Por ejemplo, para crear un NetDevice es necesario realizar múltiples operaciones nucleares de ns-3, agregar una dirección MAC, instalar este dispositivo de red en un Node, configurar la pila de protocolos del nodo y luego conectar el NetDevice a un Channel. Se requerirán aún más operaciones para conectar múltiples dispositivos a canales multipunto y luego conectar redes individuales en una red unificada (Internetworks). Proporcionamos objetos auxiliares de topología que, para su comodidad, combinan estas numerosas operaciones en un modelo fácil de usar.
4.2 Primer script de ns-3
Si has instalado el sistema como se sugirió anteriormente, tendrás una versión de ns-3 en una carpeta llamada repos en tu directorio personal. Navega a la carpeta release
Si no tiene este directorio, significa que al compilar la versión de lanzamiento de ns‑3 no especificó el directorio de salida, realice la compilación así:
$ ./waf configure --build-profile=release --out=build/release,
$ ./waf build
ahí debería ver una estructura de directorio similar a la siguiente:
AUTHORS examples scratch utils waf.bat*
bindings LICENSE src utils.py waf-tools
build ns3 test.py* utils.pyc wscript
CHANGES.html README testpy-output VERSION wutils.py
doc RELEASE_NOTES testpy.supp waf* wutils.pycNavegue al directorio examples/tutorial. Debería ver un archivo allí llamado first.cc. Este es un script que creará una conexión punto a punto entre dos nodos y transferirá un paquete entre los nodos. Veamos este script línea por línea, para ello abriremos first.cc en su editor favorito.
4.2.1 Código base
La primera línea en el archivo es la línea de modo del editor emacs. Le indica a emacs las convenciones de formato (estilo de codificación) que usaremos en nuestro código fuente.
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */Esta es siempre una cuestión bastante polémica, así que debemos aclararlo para despejarlo de inmediato. El proyecto ns‑3, como muchos proyectos grandes, ha adoptado un estilo de codificación que todo el código proporcionado debe seguir. Si desea contribuir con su código al proyecto, eventualmente tendrá que cumplir con el estándar de codificación de ns‑3, como se describe en el archivo doc/codingstd.txt o mostrado en la página web del proyecto: .
Te recomendamos que te acostumbres a la apariencia del código de ns‑3 y apliques este estándar cada vez que trabajes con nuestro código. Todo el equipo de desarrolladores y colaboradores estuvo de acuerdo con esto después de algunas quejas. La línea de modo de emacs mencionada anteriormente facilita el formato correcto si usas el editor emacs.
El simulador ns‑3 se licencia bajo la Licencia Pública General de GNU. Verás el encabezado legal correspondiente de GNU en cada archivo de distribución de ns‑3. A menudo puedes ver un aviso de derechos de autor para una de las instituciones participantes en el proyecto ns‑3 sobre el texto de la GPL y el autor que se muestra a continuación.
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation;
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/4.2.2 Módulos conectables
El propio código comienza con una serie de directivas de inclusión (include).
#include "ns3/core-module.h"
#include "ns3/network-module.h"
#include "ns3/internet-module.h"
#include "ns3/point-to-point-module.h"
#include "ns3/applications-module.h"Para ayudar a nuestros usuarios de scripts de alto nivel a manejar la gran cantidad de archivos de encabezado presentes en el sistema, los agrupamos según su uso en grandes módulos. Proporcionamos un único archivo de encabezado que cargaría recursivamente todos los archivos de encabezado utilizados en dicho módulo. En lugar de buscar cuál encabezado necesitas y posiblemente obtener la lista correcta de dependencias, te damos la oportunidad de cargar un grupo de archivos con un alto nivel de detalle. Este no es el enfoque más eficiente, pero definitivamente facilita la escritura de scripts.
Cada uno de los archivos incluidos de ns-3 se coloca en un directorio llamado ns3 (subdirectorio de la construcción), para evitar conflictos de nombres de archivos durante el proceso de construcción. El archivo ns3/core-module.h corresponde al módulo ns-3 que encontrarás en el directorio src/core en la versión que has instalado. En el listado de este directorio encontrarás una gran cantidad de archivos de encabezado. Cuando realizas la construcción, Waf coloca los archivos de encabezado públicos en el directorio ns3 en el subdirectorio build/debug
Si no tiene este directorio, significa que al compilar la versión de lanzamiento de ns‑3 no especificó el directorio de salida, realice la compilación así:
$ ./waf configure --build-profile=debug --out=build/debug
$ ./waf build
o
$ ./waf configure --build-profile=optimized --out=build/optimized
$ ./waf build
o build/optimized, dependiendo de tu configuración. Waf también generará automáticamente un archivo de inclusión del módulo para cargar todos los archivos de encabezado públicos. Dado que, por supuesto, sigues este manual al pie de la letra, ya has hecho
$ ./waf -d debug --enable-examples --enable-tests configurepara configurar el proyecto para realizar construcciones de depuración que incluyan ejemplos y pruebas. También has hecho
$ .\/wafpara construir el proyecto. Así que ahora, cuando mires en el directorio ../..//build/debug/ns3, allí, entre otros, encontrarás los archivos de encabezado de los cuatro módulos que se muestran arriba. Puedes echar un vistazo al contenido de estos archivos y descubrir que incluyen todos los archivos públicos utilizados por los módulos correspondientes.
4.2.3 Espacio de nombres ns3
La siguiente línea en el script first.cc es la declaración del espacio de nombres.
using namespace ns3;El proyecto ns‑3 está implementado en el espacio de nombres C++, que se llama ns3. Esto agrupa todas las declaraciones relacionadas con ns‑3 en un ámbito fuera del espacio de nombres global, lo que, esperamos, ayudará en la integración con otro código. El uso del operador C++ introduce el espacio de nombres ns‑3 en la región declarativa actual (global). Esta es una forma peculiar de decir que después de esta declaración, no será necesario utilizar el operador de resolución ns3::scope antes de todo el código de ns‑3 para usarlo. Si no estás familiarizado con los espacios de nombres, consulta casi cualquier libro de texto sobre C++ y compara el espacio de nombres ns3 con el uso del espacio de nombres std y las declaraciones. using namespace std; en ejemplos de uso con el operador de salida cout y flujos.
4.2.4 Registro
La siguiente línea del script es la siguiente:
NS_LOG_COMPONENT_DEFINE ("FirstScriptExample");Usaremos esta declaración como un lugar conveniente para discutir nuestro sistema de documentación. DoxygenSi miras el sitio web del proyecto ns‑3, encontrarás un enlace a 'Documentación' en la barra de navegación. Si eliges este enlace, te llevarán a nuestra página de documentación. Hay un enlace al 'Último lanzamiento', que te llevará a la documentación de la última versión estable de ns‑3. Si eliges el enlace 'Documentación API', llegarás a la página de documentación API de ns‑3.
En el lado izquierdo de la página, encontrarás una representación gráfica de la estructura de la documentación. Un buen lugar para comenzar es el 'libro' Modules ns‑3 en el árbol de navegación de ns‑3. Si expandes Modules, verás una lista de documentación de módulos ns‑3. Como se mencionó anteriormente, el concepto de módulo aquí está directamente relacionado con los archivos incluidos en el módulo anterior. La subsistema de registro (logging) de ns‑3 se discute en la sección Uso del módulo de registro, así que volveremos a ello más adelante en esta guía, pero puedes aprender sobre la declaración anterior mirando el módulo Core, y luego abriendo el libro Debugging tools, y luego seleccionando la página Logging. Haz clic en Logging.
Ahora deberías revisar la documentación Doxygen para el módulo Logging. En la lista de macros en la parte superior de la página, verás una entrada para NS_LOG_COMPONENT_DEFINE. Antes de hacer clic en el enlace, asegúrate de revisar la 'Descripción detallada' del módulo de registro para comprender su funcionamiento en general. Para hacerlo, puedes desplazarte hacia abajo o seleccionar 'Más…' debajo del diagrama.
Una vez que tengas una comprensión general de lo que está sucediendo, continúa y consulta la documentación sobre el NS_LOG_COMPONENT_DEFINE específico. No voy a duplicar la documentación aquí, pero para resumir, diré que esta línea declara un componente de registro denominado FirstScriptExample, que permite habilitar y deshabilitar el registro de mensajes de consola mediante un enlace al nombre.
4.2.5 Función principal
En las siguientes líneas del script, verás,
int
main (int argc, char *argv[])
{ Esta es simplemente la declaración de la función principal de tu programa (script). Al igual que en cualquier programa en C++, necesitas definir la función principal, que se ejecuta primero. No hay nada especial aquí. Tu script ns-3 es simplemente un programa en C++. La siguiente línea establece la resolución de tiempo a 1 nanosegundo, que es el valor por defecto:
Time::SetResolution (Time::NS);La resolución de tiempo o simplemente resolución es el valor de tiempo más pequeño que puede ser utilizado (la menor diferencia representable entre dos valores de tiempo). Puedes cambiar la resolución exactamente una vez. El mecanismo que proporciona esta flexibilidad consume memoria, así que una vez que la resolución esté establecida explícitamente, liberamos memoria para evitar actualizaciones posteriores. (Si no estableces la resolución explícitamente, por defecto será de un nanosegundo y la memoria será liberada al inicio de la simulación.)
Las siguientes dos líneas del script se utilizan para habilitar dos componentes de registro que están integrados en las aplicaciones EchoClient y EchoServer:
LogComponentEnable("UdpEchoClientApplication", LOG_LEVEL_INFO); LogComponentEnable("UdpEchoServerApplication", LOG_LEVEL_INFO);Si ha leído la documentación del componente Logging, habrá notado que existen varios niveles de detalle de registro que puede habilitar en cada componente. Estas dos líneas de código habilitan el registro de depuración en el nivel INFO para clientes y servidores de eco. En este nivel, la aplicación imprimirá mensajes durante la simulación al enviar y recibir paquetes.
Ahora pasaremos directamente a la creación de la topología y al inicio de la simulación. Utilizaremos objetos de asistentes de topología para facilitar este trabajo.
4.2.6 Uso de ayudantes topológicos
Las siguientes dos líneas de código en nuestro script crearán objetos Node ns-3 que representarán computadoras en la simulación.
NodeContainer nodes;
nodes.Create(2);Antes de continuar, busquemos la documentación para la clase NodeContainer. Otra forma de acceder a la documentación de esta clase es a través de la pestaña Classes en las páginas Doxygen. Si ya tiene Doxygen abierto, simplemente desplácese hacia arriba hasta la parte superior de la página y seleccione la pestaña Classes. Debería ver un nuevo conjunto de pestañas, una de las cuales es la lista de clases. Debajo de esta pestaña verá la lista de todas las clases ns-3. Desplácese hacia abajo hasta ns3::NodeContainer. Cuando encuentre la clase, selecciónela para ir a la documentación de la clase.
Como recordamos, una de nuestras abstracciones clave es el nodo. Representa una computadora a la que vamos a agregar cosas como pilas de protocolos, aplicaciones y tarjetas periféricas. El asistente de topología NodeContainer proporciona una forma conveniente de crear, gestionar y acceder a cualquier objeto Nodo, que estamos creando para ejecutar la simulación. La primera línea arriba simplemente declara NodeContainer, que llamamos nodes. La segunda línea invoca el método Create para el objeto nodes y solicita al contenedor que cree dos nodos. Como se describe en Doxygen, el contenedor solicita a la sistema ns-3 la creación de dos objetos Nodo y mantiene punteros a esos objetos dentro de sí mismo.
Los nodos creados en el script no hacen nada por ahora. El siguiente paso para construir la topología es conectar nuestros nodos a la red. La forma más simple de red que soportamos es una conexión punto a punto entre dos nodos. Ahora crearemos tal conexión.
PointToPointHelper
Creamos una conexión de dos puntos siguiendo un patrón que nos es familiar, utilizando un objeto auxiliar topológico para realizar el trabajo de bajo nivel necesario para la conexión. Recordemos que nuestras dos abstracciones clave NetDevice y Channel. En el mundo real, estos términos se corresponden aproximadamente con las tarjetas periféricas y los cables de red. Por lo general, estas dos cosas están estrechamente relacionadas entre sí, y nadie puede contar con intercambiar, por ejemplo, dispositivos Ethernet a través de un canal inalámbrico. Nuestros auxiliares topológicos siguen esta estrecha relación y, por lo tanto, en este escenario utilizará un solo objeto PointToPointHelper para configurar y conectar objetos ns-3 PointToPointNetDevice y PointToPointChannel. Las siguientes tres líneas en el escenario:
PointToPointHelper pointToPoint;
pointToPoint.SetDeviceAttribute ("DataRate", StringValue ("5Mbps"));
pointToPoint.SetChannelAttribute ("Delay", StringValue ("2ms"));La primera línea,
PointToPointHelper pointToPoint;crea en la pila una instancia del objeto PointToPointHelper. Desde el punto de vista de alto nivel, la siguiente línea,
pointToPoint.SetDeviceAttribute ("DataRate", StringValue ("5Mbps"));indica al objeto PointToPointHelper usar el valor "5 Mbps" (cinco megabits por segundo) como "DataRate».
Desde un punto de vista más específico, la línea "DataRate" se corresponde con lo que llamamos un atributo PointToPointNetDevice. Si miras Doxygen para la clase ns3::PointToPointNetDevice y en la documentación del método GetTypeId encontrarás una lista de atributos definidos para el dispositivo. Entre ellos estará el atributo "DataRate". La mayoría de los objetos visibles para el usuario en ns-3 tienen listas de atributos similares. Usamos este mecanismo para una fácil configuración de la simulación sin recompilación, como verás en la siguiente sección.
Similar a "DataRate" en PointToPointNetDevice, encontrarás el atributo "Delay" relacionado con PointToPointChannel. La línea final,
pointToPoint.SetChannelAttribute ("Delay", StringValue ("2ms"));indica PointToPointHelper usar el valor "2 ms" (dos milisegundos) como el valor de retardo de propagación en el canal punto a punto que posteriormente crea.
NetDeviceContainer
Hasta el momento, en nuestro escenario tenemos NodeContainer, el cual contiene dos nodos. Tenemos PointToPointHelper, que está preparado para crear objetos PointToPointNetDevices y conectarlos mediante un objeto PointToPointChannel. Así como utilizamos un objeto auxiliar de topología NodeContainer para crear nodos, le pediremos a PointToPointHelper que realice por nosotros el trabajo de creación, configuración e instalación de nuestros dispositivos. Necesitaremos una lista de todos los objetos creados NetDevice, por lo tanto, utilizamos NetDeviceContainer para almacenarlos de la misma manera que utilizamos NodeContainer para almacenar los nodos que hemos creado. Las siguientes dos líneas de código,
NetDeviceContainer devices;
devices = pointToPoint.Install (nodes);completan la configuración de los dispositivos y el canal. La primera línea declara el contenedor de dispositivos mencionado anteriormente, y la segunda realiza el trabajo principal. El método Instalar de la instalación PointToPointHelper toma NodeContainer como parámetro. Dentro de NetDeviceContainer para cada nodo que se encuentra en NodeContainer se crea (para la comunicación punto a punto debe haber exactamente dos) PointToPointNetDevice se crea y se guarda en el contenedor de dispositivos. PointToPointChannel se crea, y se le unen dos PointToPointNetDevices. Después de crear los objetos, los atributos almacenados en PointToPointHelper, se utilizan para inicializar los atributos correspondientes en los objetos creados.
Después de ejecutar la llamada pointToPoint.Install (nodes) tendremos dos nodos, cada uno con un dispositivo de red 'punto a punto' instalado y un canal 'punto a punto' entre ellos. Ambos dispositivos estarán configurados para transmitir datos a una velocidad de cinco megabits por segundo con un retardo de transmisión de dos milisegundos en el canal.
InternetStackHelper
Ahora tenemos nodos y dispositivos configurados, pero no hemos instalado pilas de protocolos en nuestros nodos. Las siguientes dos líneas de código se encargarán de esto.
InternetStackHelper stack;
stack.Install (nodes);InternetStackHelper es un asistente topológico para pilas de Internet, similar a PointToPointHelper para dispositivos de red bidireccionales. El método Instalar toma NodeContainer como parámetro. Al ejecutarse, instalará la pila de Internet (TCP, UDP, IP, etc.) en cada nodo del contenedor.
Ipv4AddressHelper
Luego necesitamos vincular nuestros dispositivos con direcciones IP. Proporcionamos un asistente topológico para gestionar la distribución de direcciones IP. La única API visible para el usuario es establecer la dirección IP base y la máscara de red para utilizar al realizar la distribución real de direcciones (esto se realiza a un nivel más bajo dentro del asistente). Las siguientes dos líneas de código en nuestro ejemplo de script first.cc,
Ipv4AddressHelper address;
address.SetBase ("10.1.1.0", "255.255.255.0");declaran un objeto de dirección auxiliar y le dicen que debe comenzar a asignar direcciones IP de la red 10.1.1.0, utilizando la máscara de subred 255.255.255.0 para determinarlo. Por defecto, las direcciones asignadas comenzarán con uno y aumentarán monotonamente, por lo que la primera dirección asignada de esta base será 10.1.1.1, luego 10.1.1.2, etc. En realidad, a un nivel bajo, el sistema ns-3 recuerda todas las direcciones IP asignadas y genera un error fatal si accidentalmente creas una situación en la que se genere la misma dirección dos veces (por cierto, este error es difícil de depurar).
La siguiente línea de código,
Ipv4InterfaceContainer interfaces = address.Assign(devices);realiza la asignación real de la dirección. En ns-3, establecemos la conexión entre la dirección IP y el dispositivo utilizando el objeto Ipv4Interface. Así como a veces necesitamos una lista de dispositivos de red creados por el asistente para uso posterior, a veces necesitamos una lista de objetos Ipv4Interface. Ipv4InterfaceContainer proporciona esta funcionalidad.
Hemos construido una red punto a punto, con pilas instaladas y direcciones IP asignadas. Ahora necesitamos en cada nodo aplicaciones para generar tráfico.
4.2.7 Uso de la aplicación
Otra de las principales abstracciones del sistema ns-3 es Application (aplicación). En este escenario utilizamos dos especializaciones de la clase base Application ns-3 llamada UdpEchoServerApplication y UdpEchoClientApplication. Al igual que en los casos anteriores, utilizamos objetos auxiliares para configurar y gestionar objetos básicos. Aquí utilizamos UdpEchoServerHelper y UdpEchoClientHelperobjetos para facilitarnos la vida.
UdpEchoServerHelper
Las siguientes líneas de código en nuestro ejemplo de script first.cc se utilizan para configurar la aplicación del servidor UDP echo en uno de los nodos que creamos anteriormente.
UdpEchoServerHelper echoServer (9);
ApplicationContainer serverApps = echoServer.Install(nodes.Get(1));
serverApps.Start(Seconds(1.0));
serverApps.Stop(Seconds(10.0));La primera línea de código en el fragmento anterior crea UdpEchoServerHelper. Como siempre, no se trata de una aplicación por sí misma, sino de un objeto que nos ayuda a crear aplicaciones reales. Uno de nuestros acuerdos es pasar los atributos necesarios al constructor del objeto auxiliar (ayudante). En este caso, el ayudante no podrá hacer nada útil si no se le proporciona el número de puerto en el que el servidor estará esperando paquetes, este número también debe ser conocido por el cliente. En este caso, pasamos el número de puerto al constructor del ayudante. El constructor, a su vez, simplemente ejecuta SetAttribute con el valor proporcionado. Más adelante, si lo deseas, podrás establecer otro valor para el atributo 'Puerto' mediante SetAttribute.
Al igual que muchos otros objetos auxiliares, el objeto UdpEchoServerHelper tiene un método Instalar. La ejecución de este método, de hecho, resulta en la creación de una aplicación básica de eco-servidor y su enlace a un nodo. Curiosamente, el método Instalar toma NodeContainter toma un parámetro de la misma forma que otros Instalar métodos que hemos visto.
La conversión implícita de C++, que funciona aquí, toma el resultado del método node.Get (1) (que devuelve un puntero inteligente al objeto nodo — Ptr ) y lo utiliza en el constructor para un objeto anónimo NodeContainer, que luego se pasa al método Instalar. Si no puedes determinar en el código C++ qué método se compila y se ejecuta con qué firma, busca entre las conversiones implícitas.
Ahora vemos que echoServer.Install está listo para instalar la aplicación UdpEchoServerApplication en el encontrado en NodeContainer, que utilizamos para gestionar nuestros nodos, el nodo con índice 1. El método Instalar devolverá un contenedor que contiene punteros a todas las aplicaciones (en este caso una, ya que pasamos un anónimo NodeContainer, que contiene un nodo) creado por el ayudante.
Las aplicaciones necesitan especificar el momento para iniciar la generación de tráfico 'start' y puede que también necesiten indicar cuándo detenerse 'stop'.Proporcionamos ambos parámetros. Estos tiempos se establecen utilizando los métodos ApplicationContainer Iniciar y Stop. Estos métodos aceptan parámetros del tipo Tiempo. En este caso, usamos una secuencia explícita de conversiones en C++ para tomar C++ double 1.0 y convertirlo en un objeto tns‑3 Time, utilizando el objeto Seconds para convertir a segundos. Recuerde que las reglas de conversión pueden ser controladas por el autor del modelo, y C++ tiene sus propias reglas, por lo que no siempre puede contar con que los parámetros se convertirán como usted esperaba. Dos cadenas,
serverApps.Start (Seconds (1.0));
serverApps.Stop (Seconds (10.0));hará que la aplicación de eco-servidor se inicie (se active automáticamente) un segundo después de que comience la simulación y se detenga (se apague) diez segundos después de la simulación. Debido a que hemos declarado un evento de simulación (evento de detención de la aplicación) que se ejecutará después de diez segundos, se simulará al menos diez segundos de funcionamiento de la red.
UdpEchoClientHelper
La aplicación cliente echo se configura de una manera prácticamente idéntica a la del servidor. Hay un objeto base UdpEchoClientApplication, que es controlado por
UdpEchoClientHelper.
UdpEchoClientHelper echoClient (interfaces.GetAddress (1), 9);
echoClient.SetAttribute ("MaxPackets", UintegerValue (1));
echoClient.SetAttribute ("Interval", TimeValue (Seconds (1.0)));
echoClient.SetAttribute ("PacketSize", UintegerValue (1024));
ApplicationContainer clientApps = echoClient.Install (nodes.Get (0));
clientApps.Start (Seconds (2.0));
clientApps.Stop (Seconds (10.0));Sin embargo, para el eco-cliente necesitamos establecer cinco atributos diferentes. Los primeros dos atributos se establecen durante la creación UdpEchoClientHelper. Pasamos los parámetros que se utilizan (dentro del asistente) para establecer los atributos "RemoteAddress" y "RemotePort" de acuerdo con nuestro convenio sobre la transmisión de los parámetros necesarios al constructor del asistente.
Recordemos que utilizamos Ipv4InterfaceContainer para rastrear las direcciones IP que asignamos a nuestros dispositivos. La interfaz cero en el contenedor de interfaces se corresponderá con la dirección IP del nodo cero en el contenedor de nodos. La primera interfaz en el contenedor de interfaces corresponde a la dirección IP del primer nodo en el contenedor de nodos. Así que, en la primera línea de código (de arriba hacia abajo), creamos un asistente y le decimos que la dirección remota del cliente será la dirección IP asignada al nodo en el que se encuentra el servidor. También decimos que se deben organizar el envío de paquetes al noveno puerto.
El atributo «MaxPackets» informa al cliente la cantidad máxima de paquetes que podemos enviar durante la simulación. El atributo «Interval» indica al cliente cuánto tiempo esperar entre paquetes, y el atributo «PacketSize» le dice al cliente cuán grandes deben ser las cargas útiles de los paquetes. Con esta combinación de atributos le decimos al cliente que envíe un paquete de 1024 bytes.
Al igual que en el caso del servidor de eco, configuramos los atributos del cliente de eco Iniciar y Stop, pero aquí iniciamos el cliente un segundo después de encender el servidor (dos segundos después de que comienza la simulación).
4.2.8 Simulador
En esta etapa necesitamos iniciar la simulación. Esto se hace mediante la función global Simulator::Run.
Simulator::Run ();Cuando anteriormente llamamos a los métodos,
serverApps.Start (Seconds (1.0));
serverApps.Stop (Seconds (10.0));
...
clientApps.Start (Seconds (2.0));
clientApps.Stop (Seconds (10.0));en realidad programamos eventos en el simulador para 1.0 segundos, 2.0 segundos y dos eventos a 10.0 segundos. Después de la llamada Simulator::Run, el sistema comenzará a revisar la lista de eventos programados y a ejecutarlos. Primero, ejecutará el evento a través de 1.0 segundos, lo que activará la aplicación del servidor de eco (este evento, a su vez, puede programar muchos otros eventos). Luego, ejecutará el evento programado para t = 2.0 segundos, que iniciará la aplicación del cliente de eco. Nuevamente, este evento puede programar muchos otros eventos. La implementación del evento de inicio en el cliente de eco comenzará la etapa de transferencia de datos de la simulación, enviando un paquete al servidor.
El acto de enviar el paquete al servidor invocará una cadena de eventos que se programarán automáticamente en segundo plano y que implementarán la mecánica de envío de paquetes de eco de acuerdo con los parámetros de sincronización que establecimos en el guion.
Como resultado, dado que enviamos solo un paquete (recordemos, el atributo MaxPackets se estableció en uno), la cadena de eventos iniciada por esta única solicitud de eco del cliente concluirá y la simulación pasará a un estado de espera. Una vez que esto ocurra, los eventos restantes programados serán eventos Stop para el servidor y el cliente. Cuando estos eventos se ejecuten, no habrá más eventos para procesar y Simulator::Run se devolverá el control. La simulación ha terminado.
Solo queda limpiar. Esto se hace llamando a la función global Simulator::Destroy. Dado que se invocaron funciones auxiliares (o código de bajo nivel de ns-3), que están organizadas de tal manera que se insertan ganchos en el simulador para destruir todos los objetos que fueron creados. No necesitas rastrear ninguno de estos objetos por tu cuenta: todo lo que tenías que hacer era llamar Simulator::Destroy y salir. El sistema ns-3 hará este trabajo difícil por ti. Las líneas restantes de nuestro primer script de ns-3, first.cc, hacen precisamente esto:
Simulator::Destroy ();
return 0;
}¿Cuándo se detendrá el simulador?
ns-3 es un simulador de eventos discretos (DE). En tal simulador, cada evento está relacionado con el tiempo de su ejecución, y la simulación continúa procesando eventos en el orden en que ocurren durante la simulación. Los eventos pueden causar la programación de eventos futuros (por ejemplo, un temporizador puede reprogramarse para finalizar el conteo en el siguiente intervalo).
Los eventos iniciales son generalmente iniciados por un objeto, por ejemplo, IPv6 programará la detección de servicios en la red, solicitudes de vecinos, etc. La aplicación programa el primer evento de envío de paquetes, etc. Cuando se procesa un evento, puede generar cero, uno o varios eventos. A medida que se lleva a cabo la simulación, los eventos simplemente terminan o generan nuevos. La simulación se detendrá automáticamente si la cola de eventos está vacía o se detecta un evento especial Stop. El evento Stop es generado por la función Simulator::Stop (detener el tiempo).
Hay un caso típico en el que Simulator::Stop es absolutamente necesario para detener la simulación: cuando hay eventos autosostenibles. Los eventos autosostenibles (o recurrentes) son aquellos que siempre se reprograman. Como resultado, siempre mantienen la cola de eventos no vacía. Hay muchos protocolos y módulos que contienen eventos recurrentes, como:
• FlowMonitor: verificación periódica de paquetes perdidos;
• RIPng: transmisión periódica de actualizaciones de tablas de enrutamiento;
• etc.
En tales casos Simulator::Stop es necesario para detener la simulación correctamente. Además, cuando ns-3 está en modo de emulación, se utiliza RealtimeSimulator para sincronizar el reloj de la simulación con el reloj de la máquina, y Simulator::Stop es necesario para detener el proceso.
Muchos de los programas de simulación en el tutorial no invocan Simulator::Stop claramente, ya que terminan automáticamente al agotarse los eventos en la cola. Sin embargo, estos programas también aceptarán la llamada a Simulator::Stop. Por ejemplo, la siguiente instrucción adicional en el primer ejemplo del programa programará una detención explícita en el segundo 11:
+ Simulator::Stop (Seconds (11.0));
Simulator::Run ();
Simulator::Destroy ();
return 0;
}Lo anterior no cambiará el comportamiento de este programa, ya que esta simulación específica termina naturalmente después de 10 segundos. Pero si hubieras cambiado el tiempo de detención en la instrucción anterior de 11 segundos a 1 segundo, notarías que la simulación se detiene antes de que cualquier salida aparezca en la pantalla (ya que la salida ocurre aproximadamente después de 2 segundos de tiempo de simulación).
Es importante llamar a Simulator::Stop antes de llamar a Simulator::Run; de lo contrario, Simulator::Run puede nunca devolver el control al programa principal para realizar la detención.
4.2.9 Compilación de su script
Hicimos que la creación de sus simples scripts fuera trivial. Todo lo que necesita hacer es colocar su script en el directorio scratch, y se compilará automáticamente si ejecuta Waf. Vamos a intentarlo. Regrese al directorio raíz y copie examples/tutorial/first.cc en el directorio scratch
$ cd ..
$ cp examples/tutorial/first.cc scratch/myfirst.ccAhora compile su primer ejemplo de script usando , por lo que en vez de lo anterior, el siguiente comando funcionará::
$ .\/wafDebería ver mensajes indicando que su primer ejemplo se ha creado correctamente.
Waf: Entrando en el directorio `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
[614/708] cxx: scratch/myfirst.cc -> build/debug/scratch/myfirst_3.o
[706/708] cxx_link: build/debug/scratch/myfirst_3.o -> build/debug/scratch/myfirst
Waf: Saliendo del directorio `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
'build' finalizado con éxito (2.357s)Ahora puede ejecutar el ejemplo (tenga en cuenta que si compila su programa en el directorio scratch, también debe ejecutarlo desde scratch):
$ ./waf --run scratch/myfirstDebería ver una salida similar:
Waf: Entrando en el directorio `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
Waf: Saliendo del directorio `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
'build' finalizado con éxito (0.418s) Enviados 1024 bytes a 10.1.1.2
Recibidos 1024 bytes de 10.1.1.1
Recibidos 1024 bytes de 10.1.1.2Aquí puedes ver que el sistema de compilación verifica que el archivo se ha compilado y luego lo ejecuta. Verás que la entrada del componente en el cliente de eco indica que ha enviado un paquete de 1024 bytes al servidor de eco 10.1.1.2. También puedes ver la entrada de registro en el servidor de eco que dice que recibió 1024 bytes de 10.1.1.1. El servidor de eco repite el paquete silenciosamente, y verás en el registro del cliente de eco que recibió su paquete de vuelta del servidor.
4.3 Código fuente de ns-3
Ahora que has utilizado algunos de los ayudantes de ns-3, puedes mirar algunos códigos fuente que implementan esta funcionalidad. El código más reciente se puede ver en nuestro servidor web en el siguiente enlace: . Allí verás una página de resumen de Mercurial para nuestro árbol de desarrollo de ns-3. En la parte superior de la página verás algunos enlaces,
summary | shortlog | changelog | graph | tags | filesSigue adelante y selecciona el enlace de archivos. Así es como se verá el nivel superior de la mayoría de nuestros repositorios:
drwxr-xr-x [up]
drwxr-xr-x bindings python files
drwxr-xr-x doc files
drwxr-xr-x examples files
drwxr-xr-x ns3 files
drwxr-xr-x scratch files
drwxr-xr-x src files
drwxr-xr-x utils files
-rw-r--r-- 2009-07-01 12:47 +0200 560 .hgignore file | revisions | annotate
-rw-r--r-- 2009-07-01 12:47 +0200 1886 .hgtags file | revisions | annotate
-rw-r--r-- 2009-07-01 12:47 +0200 1276 AUTHORS file | revisions | annotate
-rw-r--r-- 2009-07-01 12:47 +0200 30961 CHANGES.html file | revisions | annotate
-rw-r--r-- 2009-07-01 12:47 +0200 17987 LICENSE file | revisions | annotate
-rw-r--r-- 2009-07-01 12:47 +0200 3742 README file | revisions | annotate
-rw-r--r-- 2009-07-01 12:47 +0200 16171 RELEASE_NOTES file | revisions | annotate
-rw-r--r-- 2009-07-01 12:47 +0200 6 VERSION file | revisions | annotate
-rwxr-xr-x 2009-07-01 12:47 +0200 88110 waf file | revisions | annotate
-rwxr-xr-x 2009-07-01 12:47 +0200 28 waf.bat file | revisions | annotate
-rw-r--r-- 2009-07-01 12:47 +0200 35395 wscript file | revisions | annotate
-rw-r--r-- 2009-07-01 12:47 +0200 7673 wutils.py file | revisions | annotateNuestros ejemplos de scripts se encuentran en el directorio examples. Si haces clic en ejemplos, verás una lista de subdirectorios. Uno de los archivos en el subdirectorio tutorial — first.cc. Si haces clic en first.cc verás el código que acabas de estudiar.
El código fuente se encuentra principalmente en el directorio src. Puedes ver el código fuente haciendo clic en el nombre del directorio o en el enlace de los archivos a la derecha del nombre del directorio. Si haces clic en el directorio src, obtendrás una lista de subdirectorios src. Si luego haces clic en el subdirectorio core, encontrarás una lista de archivos. El primer archivo que verás (en el momento de escribir esta guía) es abort.h. Si haces clic en el enlace abort.h, serás dirigido al archivo fuente para abort.h, que contiene macros útiles para salir de scripts si se detectan condiciones anormales. El código fuente de los asistentes que utilizamos en este capítulo se puede encontrar en el directorio src/Applications/helper. No dudes en explorar el árbol de directorios para entender qué hay en cada lugar y familiarizarte con el estilo del programa ns-3.
Fuente: habr.com
