A Discrete-Event Network Simulator
Rastreamento

Visão Conceitual

Antes de escrever códigos no ns-3 é extremamente importante entender um pouco dos conceitos e abstrações do sistema. Muitos conceitos poderão parecer óbvios, mas a recomendação geral é que esta seção seja lida por completo para assegurar que o leitor inicie com uma base sólida.

Principais Abstrações

Nesta seção, são revistos alguns termos que são comumente usados por profissionais de redes de computadores, mas que tem um significado específico no ns-3.

Nó (Node)

No jargão da Internet, um dispositivo computacional que conecta-se a uma rede é chamado de host ou em alguns casos de terminal. Devido ao fato do ns-3 ser um simulador de rede, e não um simulador da Internet, o termo host é intencionalmente não utilizado, pois está intimamente associado com a Internet e seus protocolos. Ao invés disso, é utilizado o termo node — em português, — que é um termo mais genérico e também usado por outros simuladores que tem suas origens na Teoria dos Grafos.

A abstração de um dispositivo computacional básico é chamado então de nó. Essa abstração é representada em C++ pela classe Node. Esta classe fornece métodos para gerenciar as representações de dispositivos computacionais nas simulações.

O nó deve ser pensado como um computador no qual se adicionam funcionalidades, tal como aplicativos, pilhas de protocolos e periféricos com seus drivers associados que permitem ao computador executar tarefas úteis. No ns-3 é utilizado este mesmo conceito básico.

Aplicações (Application)

Normalmente, programas de computador são divididos em duas classes. Programas de sistema organizam recursos do computador, tais como: memória, processador, disco, rede, etc., de acordo com algum modelo computacional. Tais programas normalmente não são utilizados diretamente pelos usuários. Na maioria das vezes, os usuários fazem uso de aplicações, que usam os recursos controlados pelos programas de sistema para atingir seus objetivos.

Geralmente, a separação entre programas de sistema e aplicações de usuários é feita pela mudança no nível de privilégios que acontece na troca de contexto feita pelo sistema operacional. No ns-3, não existe um conceito de sistema operacional real, não há o conceito de níveis de privilégios nem chamadas de sistema. Há apenas aplicações que são executadas nos nós para uma determinada simulação.

No ns-3, a abstração básica para um programa de usuário que gera alguma atividade a ser simulada é a aplicação. Esta abstração é representada em C++ pela classe Application, que fornece métodos para gerenciar a representação de suas versões de aplicações a serem simuladas. Os desenvolvedores devem especializar a classe Application para criar novas aplicações. Neste tutorial serão utilizadas duas especializações da classe Application, chamadas UdpEchoClientApplication e UdpEchoServerApplication. Estas aplicações compõem um modelo cliente/servidor usado para gerar pacotes simulados de eco na rede.

Canal de Comunicação (Channel)

No mundo real, computadores estão conectados em uma rede. Normalmente, o meio sobre o qual os dados trafegam é chamada de canal (channel). Quando um cabo Ethernet é ligado ao conector na parede, na verdade está se conectando a um canal de comunicação Ethernet. No mundo simulado do ns-3, um nó é conectado a um objeto que representa um canal de comunicação. A abstração de canal de comunicação é representada em C++ pela classe Channel.

A classe Channel fornece métodos para gerenciar objetos de comunicação de sub-redes e nós conectados a eles. Os Channels também podem ser especializados por desenvolvedores (no sentido de programação orientada a objetos). Uma especialização de Channel pode ser algo como um simples fio. Pode também ser algo mais complexo, como um comutador Ethernet ou ainda ser uma rede sem fio (wireless) em um espaço tridimensional com obstáculos.

Neste tutorial, são utilizadas versões especializadas de Channel chamadas CsmaChannel, PointToPointChannel e WifiChannel. O CsmaChannel, por exemplo, é uma versão do modelo de rede que implementa controle de acesso ao meio CSMA (Carrier Sense Multiple Access). Ele fornece uma funcionalidade similar a uma rede Ethernet.

Dispositivos de Rede (Net Device)

No passado, para conectar computadores em uma rede, era necessário comprar o cabo específico e um dispositivo chamado (na terminologia dos PCs) de periférico, que precisava ser instalado no computador. Se a placa implementava funções de rede, era chamada de interface de rede (Network Interface Card - NIC). Atualmente, a maioria dos computadores vêm com a placa de rede integrada à placa mãe (on-board) e os usuários não vêem o computador como uma junção de partes.

Uma placa de rede não funciona sem o driver que a controle. No Unix (ou Linux), um periférico (como a placa de rede) é classificado como um dispositivo (device). Dispositivos são controlados usando drivers de dispositivo (device drivers) e as placas de rede são controladas através de drivers de dispositivo de rede (network device drivers), também chamadas de dispositivos de rede (net devices). No Unix e Linux estes dispositivos de rede levam nomes como eth0.

No ns-3 a abstração do dispositivo de rede cobre tanto o hardware quanto o software (drive). Um dispositivo de rede é “instalado” em um nó para permitir que este se comunique com outros na simulação, usando os canais de comunicação (channels). Assim como em um computador real, um nó pode ser conectado a mais que um canal via múltiplos dispositivos de rede.

A abstração do dispositivo de rede é representado em C++ pela classe NetDevice, que fornece métodos para gerenciar conexões para objetos Node e Channel. Os dispositivos, assim como os canais e as aplicações, também podem ser especializados. Várias versões do NetDevice são utilizadas neste tutorial, tais como: CsmaNetDevice, PointToPointNetDevice e WifiNetDevice. Assim como uma placa de rede Ethernet é projetada para trabalhar em uma rede Ethernet, um CsmaNetDevice é projetado para trabalhar com um CsmaChannel. O PointToPointNetDevice deve trabalhar com um PointToPointChannel e o WifiNetNevice com um WifiChannel.

Assistentes de Topologia (Topology Helpers)

Em redes reais, os computadores possuem placas de rede, sejam elas integradas ou não. No ns-3, teremos nós com dispositivos de rede. Em grandes simulações, será necessário arranjar muitas conexões entre nós, dispositivos e canais de comunicação.

Visto que conectar dispositivos a nós e a canais, atribuir endereços IP, etc., são todas tarefas rotineiras no ns-3, são fornecidos os Assistentes de Topologia (topology helpers). Por exemplo, podem ser necessárias muitas operações distintas para criar um dispositivo, atribuir um endereço MAC a ele, instalar o dispositivo em um nó, configurar a pilha de protocolos no nó em questão e por fim, conectar o dispositivo ao canal. Ainda mais operações podem ser necessárias para conectar múltiplos dispositivos em canais multiponto e então fazer a interconexão das várias redes. Para facilitar o trabalho, são disponibilizados objetos que são Assistentes de Topologia, que combinam estas operações distintas em um modelo fácil e conveniente.

O primeiro código no ns-3

Se o sistema foi baixado como sugerido, uma versão do ns-3 estará em um diretório chamado repos dentro do diretório home. Entrando no diretório dessa versão, deverá haver uma estrutura parecida com a seguinte:

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.pyc

Entrando no diretório examples/tutorial, vai haver um arquivo chamado first.cc. Este é um código que criará uma conexão ponto-a-ponto entre dois nós e enviará um pacote de eco entre eles. O arquivo será analisado linha a linha, para isto, o leitor pode abri-lo em seu editor de textos favorito.

Padronização

A primeira linha do arquivo é uma linha de modo emacs, que informa sobre a convenção de formatação (estilo de codificação) que será usada no código fonte.

/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */

Este é sempre um assunto um tanto quanto controverso. O projeto ns-3, tal como a maioria dos projetos de grande porte, adotou um estilo de codificação, para o qual todo o código deve conformar. Se o leitor quiser contribuir com o projeto, deverá estar em conformidade com a codificação que está descrita no arquivo doc/codingstd.txt ou no sítio.

A equipe do ns-3 recomenda aos novos usuários que adotem o padrão quando estiverem tralhando com o código. Tanto eles, quanto todos os que contribuem, tiveram que fazer isto em algum momento. Para aqueles que utilizam o editor Emacs, a linha de modo torna mais facil seguir o padrão corretamente.

O ns-3 é licenciado usando a GNU General Public License - GPL. No cabeçalho de todo aquivo da distribuição há as questões legais associadas à licença. Frequentemente, haverá também informações sobre os direitos de cópia de uma das instituições envolvidas no projeto e o autor do arquivo.

/*
 * 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
 */

Inclusão de Módulos

O código realmente começa pelo carregamento de módulos através da inclusão dos arquivos:

#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"

Os arquivos a serem incluídos são agrupados em módulos relativamente grandes, de forma a ajudar os usuários. Também é possível fazer referência a um único arquivo que irá recursivamente carregar todas as bibliotecas de cada módulo. Ao invés de procurar pelo arquivo exato, e provavelmente, ter que resolver dependências, é possível carregar um grupo de arquivos de uma vez. Esta com certeza não é a abordagem mais eficiente, mas permite escrever códigos de forma bem mais fácil.

Durante a construção, cada um dos arquivos incluídos é copiado para o diretório chamado ns3 (dentro do diretório build), o que ajuda a evitar conflito entre os nomes dos arquivos de bibliotecas. O arquivo ns3/core-module.h, por exemplo, corresponde ao módulo que está no diretório src/core. Há um grande número de arquivos neste diretório. No momento em que o Waf está construindo o projeto, copia os arquivos para o diretório ns3, no subdiretório apropriado — build/debug ou build/optimized — dependendo da configuração utilizada.

Considerando que o leitor esteja seguindo este tutorial, já terá feito:

./waf -d debug --enable-examples --enable-tests configure

Também já terá feito

./waf

para construir o projeto. Então, no diretório ../../build/debug/ns3 deverá haver os quatro módulos incluídos anteriormente. Olhando para o conteúdo destes arquivos, é possível observar que eles incluem todos os arquivos públicos dos seus respectivos módulos.

Ns3 Namespace

A próxima linha no código first.cc é a declaração do namespace.

using namespace ns3;

O projeto ns-3 é implementado em um namespace chamado ns3. Isto agrupa todas as declarações relacionadas ao projeto em um escopo fora do global, que ajuda na integração com outros códigos. A declaração using do C++ insere o namespace ns-3 no escopo global, evitando que se tenha que ficar digitando ns3:: antes dos códigos ns-3. Se o leitor não esta familiarizado com namesapaces, consulte algum tutorial de C++ e compare o namespace ns3 e o namespace std, usado com frequência em C++, principalmente com cout e streams.

Registro (Logging)

A próxima linha do código é o seguinte,

NS_LOG_COMPONENT_DEFINE ("FirstScriptExample");

Nós iremos utilizar esta declaração em um lugar conveniente para conversar com o sistema de documentação Doxygen. Se você procurar no web site do projeto ns-3, você encontrará um link para a documentação (Documentation) na barra de navegação. Se selecionarmos este link, veremos a página de documentação. Lá tem um link para as últimas releases (Latest Release) que irão apresentar a documentação da última release do ns-3. Se você também pode selecionar o link para a documentação das APIs (API Documentation).

Do lado esquerdo, você achará uma representação gráfica da estrutura da documentação. Um bom lugar para começar é com o livro de módulos do NS-3 (NS-3 Modules) na árvore de navegação, você pode expandir para ver a lista de documentação de módulos do ns-3. O conceito de módulo aqui está diretamente ligado com a inclusão de bibliotecas apresentadas anteriormente. O sistema de registro (logging) é discutido na seção C++ Constructs Used by All Modules, vá em frente e expanda a documentação. Agora expanda o livro da depuração (Debugging) e selecione a página de Logging.

Agora você deve procurar na documentação Doxygen pelo módulo de Logging. Na lista de #define bem no topo da página você verá uma entrada para NS_LOG_COMPONENT_DEFINE. Antes de ir para lá, dê uma boa olhada na descrição detalhada (Detailed Description) do módulo de logging.

Uma vez que você tem uma ideia geral, prossiga e olhe a documentação de NS_LOG_COMPONENT_DEFINE. Não esperamos duplicar a documentação, mas para resumir esta linha declara o componente de logging chamado FirstScriptExample que permite habilitar e desabilitar mensagens de logging referenciando pelo nome.

Função Principal

As próximas linhas do código são,

int
main (int argc, char *argv[])
{

Esta é a declaração da função principal (main) do programa. Assim como em qualquer programa em C++, você precisa definir uma função principal que é a primeira função que será executada no programa. Não há nada de especial e seu código ns-3 é apenas um programa C++.

As próximas duas linhas do código são usadas para habilitar dois componentes de registro que são construídos com as aplicações de Echo Client e Echo Server:

LogComponentEnable("UdpEchoClientApplication", LOG_LEVEL_INFO);
LogComponentEnable("UdpEchoServerApplication", LOG_LEVEL_INFO);

Se você leu a documentação do componente de logging você viu que existem vários níveis de detalhamento de logging e que você pode habilitá-los para cada componente. Essas duas linhas de código habilitam a depuração de logging com o nível INFO para o cliente e servidor. Isto ira fazer com que as aplicações mostrem as mensagens dos pacotes sendo enviados e recebidos durante a simulação.

Agora nós iremos direto ao ponto, criando uma topologia e executando uma simulação. Nós usaremos o Assistente de Topologia para fazer isto da forma mais simples possível.

Assistentes de Topologia

Contêineres de nós (NodeContainer)

As próximas duas linhas de código cria os objetos do tipo Node que representarão os computadores na simulação.

NodeContainer nodes;
nodes.Create (2);

Antes de continuar vamos pesquisar a documentação da classe NodeContainer. Outra forma de obter a documentação é através aba Classes na página do Doxygen. No Doxygen, vá até o topo da página e selecione Classes. Então, você verá um novo conjunto de opções aparecendo, uma delas será a sub-aba Class List. Dentro desta opção você verá uma lista com todas as classes do ns-3. Agora procure por ns3::NodeContainer. Quando você achar a classe, selecione e veja a documentação da classe.

Lembre-se que uma de nossas abstrações é o nó. Este representa um computador, ao qual iremos adicionar coisas, como protocolos, aplicações e periféricos. O assistente NodeContainer fornece uma forma conveniente de criar, gerenciar e acessar qualquer objeto Node que criamos para executar a simulação. A primeira linha declara um NodeContainer que chamamos de nodes. A segunda linha chama o método Create sobre o objeto nodes e pede para criar dois nós.

Os nós, como estão no código, não fazem nada. O próximo passo é montar uma topologia para conectá-los em uma rede. Uma forma simples de conectar dois computadores em uma rede é com um enlace ponto-a-ponto.

PointToPointHelper

Construiremos um enlace ponto-a-ponto e para isto usaremos um assistente para configurar o nível mais baixo da rede. Lembre-se que duas abstrações básicas são NetDevice e Channel. No mundo real, estes termos correspondem, a grosso modo, à placa de rede e ao cabo. Normalmente, estes dois estão intimamente ligados e não é normal ficar trocando. Por exemplo, não é comum placas Ethernet conectadas em canais sem fio. O assistente de topologia acopla estes dois conceitos em um simples PointToPointHelper para configurar e conectar objetos PointToPointNetDevice e PointToPointChannel em nosso código.

As próximas três linhas no código são,

PointToPointHelper pointToPoint;
pointToPoint.SetDeviceAttribute ("DataRate", StringValue ("5Mbps"));
pointToPoint.SetChannelAttribute ("Delay", StringValue ("2ms"));

A primeira linha,

PointToPointHelper pointToPoint;

instancia o objeto PointToPointHelper na pilha. De forma mais superficial a próxima linha,

pointToPoint.SetDeviceAttribute ("DataRate", StringValue ("5Mbps"));

diz ao objeto PointToPointHelper para usar o valor de “5Mbps” (cinco megabits por segundo) como “DataRate” (taxa de transferência) quando criarmos um objeto PointToPointNetDevice.

De uma perspectiva mais detalhada, a palavra “DataRate” corresponde ao que nós chamamos de atributo (Attribute) do PointToPointNetDevice. Se você olhar no Doxygen na classe ns3::PointToPointNetDevice e procurar a documentação para o método GetTypeId, você achará uma lista de atributos definidos por dispositivos. Dentro desses está o atributo “DataRate”. A maioria dos objetos do ns-3 tem uma lista similar de atributos. Nós usamos este mecanismo para facilitar a configuração das simulações sem precisar recompilar, veremos isto na seção seguinte.

Parecido com o “DataRate” no PointToPointNetDevice você achará o atributo “Delay” (atraso) associado com o PointToPointChannel. O final da linha,

pointToPoint.SetChannelAttribute ("Delay", StringValue ("2ms"));

diz ao PointToPointHelper para usar o valor de “2ms” (dois milissegundos) como valor de atraso de transmissão para o canal ponto-a-ponto criado.

NetDeviceContainer

Até agora no código, temos um NodeContainer que contém dois nós. Também temos PointToPointHelper que carrega e prepara os objetos PointToPointNetDevices e PointToPointChannel. Depois, usamos o assistente NodeContainer para criar os nós para a simulação. Iremos pedir ao PointToPointHelper para criar, configurar e instalar nossos dispositivos. Iremos necessitar de uma lista de todos os objetos NetDevice que são criados, então nós usamos um NetDeviceContainer para agrupar os objetos criados, tal como usamos o NodeContainer para agrupar os nós que criamos. Nas duas linhas de código seguintes,

NetDeviceContainer devices;
devices = pointToPoint.Install (nodes);

vamos terminar configurando os dispositivos e o canal. A primeira linha declara o contêiner de dispositivos mencionado anteriormente e o segundo faz o trabalho pesado. O método Install do PointToPointHelper utiliza um NodeContainer como parâmetro. Internamente, um NetDeviceContainer é criado. Para cada nó no NodeContainer (devem existir dois para um enlace ponto-a-ponto) um PointToPointNetDevice é criado e salvo no contêiner do dispositivo. Um PointToPointChannel é criado e dois PointToPointNetDevices são conectados. Quando os objetos são criados pelo PointToPointHelper, os atributos, passados anteriormente, são configurados pelo assistente (Helper).

Depois de executar a chamada pointToPoint.Install (nodes) iremos ter dois nós, cada qual instalado na rede ponto-a-ponto e um único canal ponto-a-ponto ligando os dois. Ambos os dispositivos serão configurados para ter uma taxa de transferência de dados de cinco megabits por segundo, que por sua vez tem um atraso de transmissão de dois milissegundos.

InternetStackHelper

Agora temos os nós e dispositivos configurados, mas não temos qualquer pilha de protocolos instalada em nossos nós. As próximas duas linhas de código irão cuidar disso.

InternetStackHelper stack;
stack.Install (nodes);

O InternetStackHelper é um assistente de topologia inter-rede. O método Install utiliza um NodeContainer como parâmetro. Quando isto é executado, ele irá instalar a pilha de protocolos da Internet (TCP, UDP, IP, etc) em cada nó do contêiner.

Ipv4AddressHelper

Agora precisamos associar os dispositivos dos nós a endereços IP. Nós fornecemos um assistente de topologia para gerenciar a alocação de endereços IP’s. A única API de usuário visível serve para configurar o endereço IP base e a máscara de rede, usado para alocação de endereços.

As próximas duas linhas de código,

Ipv4AddressHelper address;
address.SetBase ("10.1.1.0", "255.255.255.0");

declara um assistente de endereçamento e diz para ele iniciar a alocação de IP’s na rede 10.1.1.0 usando a máscara 255.255.255.0. Por padrão, os endereços alocados irão iniciar do primeiro endereço IP disponível e serão incrementados um a um. Então, o primeiro endereço IP alocado será o 10.1.1.1, seguido pelo 10.1.1.2, etc. Em um nível mais baixo, o ns-3 mantém todos os endereços IP’s alocados e gera um erro fatal se você acidentalmente usar o mesmo endereço duas vezes (esse é um erro muito difícil de depurar).

A próxima linha de código,

Ipv4InterfaceContainer interfaces = address.Assign (devices);

realiza efetivamente o endereçamento. No ns-3 nós fazemos a associação entre endereços IP e dispositivos usando um objeto Ipv4Interface. As vezes precisamos de uma lista dos dispositivos de rede criados pelo assistente de topologia para consultas futuras. O Ipv4InterfaceContainer fornece esta funcionalidade.

Agora que nós temos uma rede ponto-a-ponto funcionando, com pilhas de protocolos instaladas e endereços IP’s configurados. O que nós precisamos são aplicações para gerar o tráfego de rede.

Aplicações

Outra abstração do núcleo do ns-3 são as aplicações (Application). Neste código são utilizadas duas especializações da classe Application chamadas UdpEchoServerApplication e UdpEchoClientApplication. Assim como nas explicações anteriores que nós utilizamos assistentes para configurar e gerenciar outros objetos, nós usaremos os objetos UdpEchoServerHelper e UdpEchoClientHelper para deixar a nossa vida mais fácil.

UdpEchoServerHelper

As linhas seguintes do código do exemplo first.cc, são usadas para configurar uma aplicação de eco (echo) UDP em um dos nós criados anteriormente.

UdpEchoServerHelper echoServer (9);

ApplicationContainer serverApps = echoServer.Install (nodes.Get (1));
serverApps.Start (Seconds (1.0));
serverApps.Stop (Seconds (10.0));

Um fragmento da primeira linha do código declara o UdpEchoServerHelper. Esta não é a própria aplicação, é o objeto usado para ajudar na criação da aplicação. Uma de nossas convenções é colocar os atributos obrigatórios no construtor do assistente de topologia. Neste caso, o assistente não pode fazer nada se não colocarmos um número de porta que o cliente conhece. O construtor, por sua vez, configura o atributo “Port” usando SetAttribute.

De forma semelhante aos outros assistentes, o UdpEchoServerHelper tem o método Install. É este método que instancia a aplicação de servidor de eco (echo server) e a associa ao nó. O método Install tem como parâmetro um NodeContainter, assim como o outro método Install visto. Isto é o que é passado para o método, mesmo que não seja visível. Há uma conversão implícita em C++, que pega o resultado de nodes.Get (1) (o qual retorna um ponteiro para o objeto nodePtr<Node>) e o usa em um construtor de um NodeContainer sem nome, que então é passado para o método Install.

Agora vemos que o echoServer.Install instala um UdpEchoServerApplication no primeiro nó do NodeContainer. O Install irá retornar um contêiner que armazena os ponteiros de todas as aplicações (neste caso passamos um NodeContainer contendo um nó) criadas pelo assistente de topologia.

As aplicações requerem um tempo para “iniciar” a geração de tráfego de rede e podem ser opcionalmente “desligadas”. Estes tempos podem ser configurados usando o ApplicationContainer com os métodos Start e Stop, respectivamente. Esses métodos possuem o parâmetro Time. Em nosso exemplo, nós usamos uma convenção explicita do C++ para passar 1.0 e converter em um objeto Time usando segundos. Esteja ciente que as regras de conversão podem ser controladas pelo autor do modelo e o C++ tem suas próprias regras, desta forma, você não pode assumir que o parâmetro sempre vai ser convertido para você. As duas linhas,

serverApps.Start (Seconds (1.0));
serverApps.Stop (Seconds (10.0));

irão iniciar (Start) a aplicação de servidor de eco um segundo após o início da simulação e depois desligar (Stop) em dez segundos. Em virtude de termos declarado que um evento de simulação (o evento de desligamento da aplicação) deve ser executado por dez segundos, a simulação vai durar pelo menos dez segundos.

UdpEchoClientHelper

A aplicação cliente de eco é configurada de forma muito similar ao servidor. Há o UdpEchoClientApplication que é gerenciado por um UdpEchoClientHelper.

UdpEchoClientHelper echoClient (interfaces.GetAddress (1), 9);
echoClient.SetAttribute ("MaxPackets", UintegerValue (1));
echoClient.SetAttribute ("Interval", TimeValue (Seconds (1.)));
echoClient.SetAttribute ("PacketSize", UintegerValue (1024));

ApplicationContainer clientApps = echoClient.Install (nodes.Get (0));
clientApps.Start (Seconds (2.0));
clientApps.Stop (Seconds (10.0));

Para o cliente de eco, precisamos configurar cinco diferentes atributos. Os dois primeiros são configurados durante a construção do UdpEchoClientHelper. Passamos os parâmetros que são usados (internamente pelo Assistente) para configurar os atributos “RemoteAddress” (endereço remoto) e “RemotePort” (porta remota).

Lembre-se que usamos um Ipv4InterfaceContainer para configurar o endereço IP em nossos dispositivos. A interface zero (primeira) no contêiner corresponde ao endereço IP do nó zero no contêiner de nós. A primeira interface corresponde ao endereço IP do primeiro nó. Então, na primeira linha do código anterior, nós criamos um assistente e dizemos ao nó para configurar o endereço remoto do cliente conforme o IP do servidor. Nós dizemos também para enviar pacotes para a porta nove.

O atributo “MaxPackets” diz ao cliente o número máximo de pacotes que são permitidos para envio durante a simulação. O atributo “Interval” diz ao cliente quanto tempo esperar entre os pacotes e o “PacketSize” informa ao cliente qual é o tamanho da área de dados do pacote. Com esta combinação de atributos que nós fizemos teremos clientes enviando pacotes de 1024 bytes.

Assim como no caso do servidor de eco, nós dizemos para o cliente de eco iniciar e parar, mas aqui nós iniciamos o cliente um segundo depois que o servidor estiver funcionando (com dois segundos de simulação).

Simulador (Simulator)

O que nós precisamos agora é executar o simulador. Isto é feito usando a função global Simulator::Run.

Simulator::Run ();

Quando nós chamamos os métodos

serverApps.Start (Seconds (1.0));
serverApps.Stop (Seconds (10.0));
...
clientApps.Start (Seconds (2.0));
clientApps.Stop (Seconds (10.0));

agendamos os eventos no simulador em 1 segundo, 2 segundos e dois eventos em 10 segundos. Quando chamamos Simulator::Run, o sistema verificará a lista de eventos agendados e os executará no momento apropriado. Primeiramente, ele vai executar o evento de 1 segundo que inicia a aplicação de servidor de eco. Depois executa o evento agendado com dois segundos (t=2,0) que iniciará a aplicação do cliente de eco. Estes eventos podem agendar muitos outros eventos. O evento start do cliente irá iniciar a fase de transferência de dados na simulação enviando pacotes ao servidor.

O ato de enviar pacotes para o servidor vai disparar uma cadeia de eventos que serão automaticamente escalonados e executarão a mecânica do envio de pacotes de eco de acordo com os vários parâmetros de tempo que configuramos no código.

Considerando que enviamos somente um pacote (lembre-se que o atributo MaxPackets foi definido com um), uma cadeia de eventos será disparada por este único pedido de eco do cliente até cessar e o simulador ficará ocioso. Uma vez que isto ocorra, os eventos restantes serão o Stop do servidor e do cliente. Quando estes eventos forem executados, não havendo mais eventos para processar, o Simulator::Run retorna. A simulação está completa.

Tudo que resta é limpar. Isto é feito chamando uma função global chamada Simulator::Destroy. Uma das funções dos assistentes (ou do código de baixo nível do ns-3) é agrupar todos os objetos que foram criados e destruí-los. Você não precisa tratar estes objetos — tudo que precisa fazer é chamar Simulator::Destroy e sair. O ns-3 cuidará desta difícil tarefa para você. As linhas restantes do código fazem isto:

  Simulator::Destroy ();
  return 0;
}

Construindo o código

É trivial construir (criar os binários de) seu código. Tudo que tem a fazer é copiar seu código para dentro do diretório scratch e ele será construído automaticamente quando executar o Waf. Copie examples/tutorial/first.cc para o diretório scratch e depois volte ao diretório principal.

cd ../..
cp examples/tutorial/first.cc scratch/myfirst.cc

Agora construa seu primeiro exemplo usando o Waf:

./waf

Você deve ver mensagens reportando que o seu exemplo myfirst foi construído com sucesso.

Waf: Entering directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
[614/708] cxx: scratch/myfirst.cc -> build/debug/scratch/myfirst_3.o
[706/708] cxx_link: build/debug/scratch/myfirst_3.o -> build/debug/scratch/myfirst
Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
'build' finished successfully (2.357s)

Você agora pode executar o exemplo (note que se você construiu seu programa no diretório scratch, então deve executar o comando fora deste diretório):

./waf --run scratch/myfirst

Você deverá ver algumas saídas:

Waf: Entering directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
'build' finished successfully (0.418s)
Sent 1024 bytes to 10.1.1.2
Received 1024 bytes from 10.1.1.1
Received 1024 bytes from 10.1.1.2

O sistema verifica se os arquivos foram construídos e então executa-os. Através do componente de registro vemos que o cliente enviou 1024 bytes para o servidor através do IP 10.1.1.2. Também podemos ver que o servidor diz ter recebido 1024 bytes do IP 10.1.1.1 e ecoa o pacote para o cliente, que registra o seu recebimento.

Código fonte do Ns-3

Agora que você já utilizou alguns assistentes do ns-3, podemos dar uma olhada no código fonte que implementa estas funcionalidades. Pode-se navegar o código mais recente no seguinte endereço: http://code.nsnam.org/ns-3-dev. Lá você verá a página de sumário do Mercurial para a árvore de desenvolvimento do ns-3.

No início da página, você verá vários links,

summary | shortlog | changelog | graph | tags | files

selecione files. Aparecerá o primeiro nível do repositório:

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 | annotate

Os códigos exemplo estão no diretório examples. Se você clicar verá uma lista de subdiretórios. Um dos arquivos no subdiretório tutorial é o first.cc. Clicando nele você encontrará o código que acabamos de analisar.

O código fonte é mantido no diretório src. Você pode vê-lo clicando sobre o nome do diretório ou clicando no item files a direita do nome. Clicando no diretório src, obterá uma lista de subdiretórios. Clicando no subdiretório core, encontrará um lista de arquivos. O primeiro arquivo é o abort.h, que contém macros caso condições anormais sejam encontradas.

O código fonte para os assistentes utilizados neste capítulo podem ser encontrados no diretório src/applications/helper. Sinta-se à vontade para explorar a árvore de diretórios e ver o estilo de código do ns-3.