Podręcznik do symulatora sieci ns-3. Rozdział 4

Podręcznik do symulatora sieci ns-3. Rozdział 4
rozdziały 1,2
rozdział 3

4 Przegląd koncepcji
4.1 Kluczowe abstrakcje
4.1.1 Węzeł (Node)
4.1.2 Aplikacja (Application)
4.1.3 Kanał (Channel)
4.1.4 Urządzenie sieciowe (Net Device)
4.1.5 Asystenci topologii
4.2 Pierwszy skrypt ns-3
4.2.1 Kod bazowy (Boilerplate)
4.2.2 Moduły dodawane
4.2.3 Przestrzeń nazw ns3
4.2.4 Rejestrowanie (Logging)
4.2.5 Funkcja główna
4.2.6 Użycie asystentów topologii
4.2.7 Użycie aplikacji
4.2.8 Symulator
4.2.9 Kompilacja twojego scenariusza
4.3 Kod źródłowy ns-3

Rozdział 4

Przegląd koncepcji

Pierwszą rzeczą, którą musimy zrobić przed rozpoczęciem nauki lub pisania kodu ns-3, jest wyjaśnienie kilku podstawowych pojęć i abstrakcji w systemie. Wiele z tego może wydawać się oczywiste, ale zalecamy poświęcenie czasu na przeczytanie tej sekcji, aby upewnić się, że zaczynasz na solidnych podstawach.

4.1 Kluczowe abstrakcje

W tej sekcji omówimy niektóre terminy, które są powszechnie używane w sieci, ale mają określone znaczenie w ns-3.

4.1.1 Węzeł (Node)

W żargonie internetowym urządzenie komputerowe, które łączy się z siecią, nazywane jest hostem lub czasami systemem końcowym. Z tego powodu, że ns-3 jest symulatorem sieci, a nie symulatorem Internetu, celowo nie używamy terminu host, ponieważ jest on ściśle związany z Internetem i jego protokołami. Zamiast tego używamy bardziej ogólnego terminu również stosowanego przez inne symulatory, który wywodzi się z teorii grafów — węzeł (node).

W ns-3 podstawowa abstrakcja urządzenia komputerowego nazywa się węzłem. Ta abstrakcja jest reprezentowana w klasie C++ Node. NodeNode (węzeł) oferuje metody do zarządzania przedstawieniami urządzeń komputerowych w symulacjach.

Powinieneś myśleć Węzeł o komputerze, do którego dodasz funkcjonalność. Dodasz takie rzeczy jak aplikacje, stosy protokołów oraz karty peryferyjne z sterownikami, co pozwala komputerowi na wykonywanie użytecznej pracy. Używamy takiego samego podstawowego modelu w ns-3.

4.1.2 Aplikacja (Application)

Zazwyczaj oprogramowanie komputerowe dzieli się na dwie szerokie kategorie. Oprogramowanie systemowe organizuje różne zasoby komputerowe, takie jak pamięć, cykle procesora, dysk, sieć itp., w zgodzie z określoną modelem obliczeniowym. Oprogramowanie systemowe zazwyczaj nie wykorzystuje tych zasobów do wykonywania zadań, które przynoszą bezpośrednie korzyści użytkownikowi. Aby osiągnąć określony cel, użytkownik zazwyczaj uruchamia aplikację, która uzyskuje i wykorzystuje zasoby kontrolowane przez oprogramowanie systemowe.

Często linia podziału między oprogramowaniem systemowym a aplikacyjnym przebiega w momencie zmiany poziomu uprawnień, co ma miejsce w pułapkach systemu operacyjnego. W ns-3 nie ma rzeczywistej koncepcji systemu operacyjnego, a zatem nie ma pojęć poziomów uprawnień ani wywołań systemowych. Mamy jednak pojęcie aplikacji. Tak samo jak w „życiu realnym” do wykonywania zadań aplikacje programowe działają na komputerach, aplikacje ns-3 działają na węzłach ns-3, aby zarządzać symulacjami w symulowanym świecie.

W ns-3 podstawową abstrakcją dla programu użytkownika, który generuje pewną aktywność do modelowania, jest aplikacja. Ta abstrakcja jest reprezentowana w klasie C++ Application. Klasa Application dostarcza metod do zarządzania w symulacjach reprezentacjami naszej wersji aplikacji na poziomie użytkownika. Oczekuje się, że deweloperzy będą specjalizować klasę Application w sensie programowania obiektowego w celu tworzenia nowych aplikacji. W tym przewodniku będziemy korzystać ze specjalizacji klasy Application, które nazywamy. UdpEchoClientApplication i UdpEchoServerApplication. Jak można się było spodziewać, te aplikacje tworzą zestaw aplikacji klient/serwer wykorzystywanych do generowania i symulacji echa pakietów sieciowych.

4.1.3 Kanał (Channel)

W prawdziwym świecie można podłączyć komputer do sieci. Często środowiska, przez które przesyłane są dane w tych sieciach, nazywane są kanałami. Kiedy podłączasz kabel Ethernet do gniazdka na ścianie, podłączasz komputer do kanału komunikacyjnego Ethernet. W symulowanym świecie ns-3 węzeł łączy się z obiektem reprezentującym kanał komunikacyjny. Tutaj główna abstrakcja komunikacyjnej podsieci nazywana jest kanałem i jest reprezentowana w C++ klasą Channel (kanał).

Klasa KanałKanał udostępnia metody do zarządzania interakcją obiektów podsieci i łączenia ich z węzłami. Kanały mogą być również specjalizowane przez programistów w sensie programowania obiektowego. Specjalizacja kanału może modelować coś prostego jak przewód. Specjalizowany kanał może także modelować skomplikowane rzeczy, takie jak duży przełącznik Ethernet lub trójwymiarową przestrzeń pełną przeszkód w przypadku sieci bezprzewodowych.

W tym przewodniku będziemy używać specjalizowanych wersji kanału o nazwie CsmaChannelCsmaChannel, PointToPointChannelPointToPointChannel i WifiChannelWifiChannel. CsmaChannel, na przykład, modeluje wersję komunikacyjnej podsieci, która implementuje środowisko komunikacji z wieloma dostępami z kontrolą nośnika. Daje nam to funkcjonalność podobną do Ethernetu.

4.1.4 Urządzenie sieciowe (Net Device)

Kiedyś było tak, że jeśli chciałeś podłączyć komputer do sieci, musiałeś kupić określony kabel sieciowy i urządzenie sprzętowe, zwane (w terminologii PC) kartą interfejsu, które musisz zainstalować w komputerze. Jeśli na karcie interfejsu realizowane były pewne funkcje sieciowe, nazywano je kartami sieciowymi lub kartami NIC. Dziś większość komputerów jest dostarczana z wbudowanym sprzętem interfejsu sieciowego, a użytkownicy nie traktują ich jako oddzielnych urządzeń.

Karta sieciowa nie będzie działać bez programowego sterownika, który zarządza jej sprzętem. W Unixie (lub Linuksie) część sprzętu klasyfikowana jest jako urządzenie. Urządzenia są zarządzane za pomocą sterowników urządzeń, a urządzenia sieciowe (NIC) są zarządzane za pomocą sterowników urządzeń sieciowych (dyski urządzeń sieciowych) i mają zbiorową nazwę urządzenia sieciowe (urządzenia sieciowe). W systemach Unix i Linux urządzenia sieciowe są identyfikowane po nazwach, takich jak na przykład eth0.

W ns‑3 abstrakcja urządzenia sieciowego obejmuje zarówno sterownik programowy, jak i modelowany sprzęt. Podczas symulacji urządzenie sieciowe jest 'zainstalowane' w węźle, aby umożliwić mu komunikację z innymi węzłami przez kanały. Tak jak w rzeczywistym komputerze, węzeł może być podłączony do kilku kanałów za pomocą różnych urządzeń. Urządzenia sieciowe.

Abstrakcja urządzenia sieciowego jest reprezentowana w klasie C++ NetDevice. Klasa NetDevice zapewnia metody zarządzania połączeniami z obiektami Node i Channel; mogą być również specjalizowane przez programistów w kontekście programowania obiektowego. W tym przewodniku użyjemy kilku specjalizowanych wersji NetDevice o nazwach CsmaNetDevice, PointToPointNetDevice i WifiNetDevice. Podobnie jak adapter sieciowy Ethernet jest przeznaczony do pracy w sieci Ethernet, CsmaNetDevice jest przeznaczony do pracy z CsmaChannel, PointToPointNetDevice jest przeznaczony do pracy z PointToPointChannel, a WifiNetDevice — przeznaczony do pracy z WifiChannel.

4.1.5 Asystenci topologii

W rzeczywistej sieci znajdziesz komputery-hosty z dodatkowymi (lub wbudowanymi) kartami sieciowymi. W ns‑3 powiedzielibyśmy, że zobaczysz węzły z podłączonymi NetDevices. W dużej symulowanej sieci będziesz musiał zorganizować połączenia między wieloma obiektami. Węzeł, NetDevice i Channel.

Ponieważ podłączenie NetDevices do węzłów, NetDevices do kanałów, przydzielanie adresów IP itd. w ns‑3 jest wspólnym zadaniem, aby uprościć ten proces, oferujemy tzw. pomocników topologicznych. Na przykład, aby stworzyć NetDevice, trzeba wykonać wiele operacji rdzenia ns‑3, dodać adres MAC, zainstalować to urządzenie sieciowe w Node, skonfigurować stos protokołów w węźle, a następnie podłączyć NetDevice do Channel. Jeszcze więcej operacji będzie wymagane, aby podłączyć kilka urządzeń do kanałów wielopunktowych, a następnie połączyć różne sieci w jedną sieć (Internetworks). Oferujemy obiekty pomocnicze topologii, które łączą te liczne operacje w prosty w użyciu model.

4.2 Pierwszy skrypt ns-3

Jeśli zainstalowałeś system zgodnie z powyższymi wskazówkami, będziesz miał wydanie ns‑3 w katalogu o nazwie repos w swoim katalogu domowym. Przejdź do katalogu release

Jeśli nie masz takiego katalogu, oznacza to, że podczas budowy wersji wydania ns‑3 nie określiłeś katalogu wyjściowego, wykonaj budowę w następujący sposób:
$ . /waf configure —build-profile=release —out=build/release,
$ . /waf build

tam musisz zobaczyć strukturę katalogu podobną do poniższej:

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

Przejdź do katalogu examples/tutorial. Powinieneś zobaczyć znajdujący się tam plik o nazwie first.cc. To jest skrypt, który stworzy proste połączenie punkt-punkt między dwoma węzłami i przekaże jeden pakiet między nimi. Przyjrzyjmy się temu skryptowi linia po linii, otwierając first.cc w swoim ulubionym edytorze.

4.2.1 Kod bazowy (Boilerplate)
Pierwsza linia w pliku to linia trybu edytora emacs. Informuje ona emacs o konwencjach formatowania (styl kodowania), które będziemy używać w naszym kodzie źródłowym.

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

To zawsze dość kontrowersyjny temat, więc musimy wyjaśnić, aby od razu go odsunąć. Projekt ns‑3, podobnie jak większość dużych projektów, przyjął styl kodowania, któremu musi odpowiadać cały dostarczony kod. Jeśli chcesz wnieść swój kod do projektu, w końcu będziesz musiał dostosować się do standardu kodowania ns‑3, jak opisano w pliku doc/codingstd.txt lub przedstawiony na stronie internetowej projektu: https://www.nsnam.org/develop/contributing-code/coding-style/.

Zalecamy Ci przyzwyczaić się do wyglądu kodu ns‑3 i stosować ten standard zawsze, gdy pracujesz z naszym kodem. Cały zespół deweloperów i kontrybutorów zgodził się z tym po pewnym narzekaniu. Linia trybu emacs podana powyżej ułatwia prawidłowe formatowanie, jeśli używasz edytora emacs.

Symulator ns‑3 jest licencjonowany na podstawie GNU General Public License. Zobaczysz odpowiedni prawny nagłówek GNU w każdym pliku dystrybucji ns‑3. Często możesz zobaczyć powiadomienie o prawach autorskich dla jednej z uczestniczących instytucji w projekcie ns‑3 powyżej tekstu GPL i autora, przedstawionego poniżej.

/* 
* 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 Moduły dodawane

Sam kod zaczyna się od szeregu operatorów include (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"

Aby pomóc naszym użytkownikom skryptów wysokiego poziomu poradzić sobie z dużą ilością plików nagłówkowych obecnych w systemie, grupujemy je według ich zastosowania w duże moduły. Udostępniamy jeden plik nagłówkowy, który rekurencyjnie załaduje wszystkie pliki nagłówkowe używane w danym module. Zamiast szukać, który dokładnie nagłówek jest potrzebny, i ewentualnie uzyskać właściwą listę zależności, dajemy Ci możliwość ładowania grupy plików z dużą szczegółowością. To nie jest najbardziej efektywne podejście, ale na pewno znacznie ułatwia pisanie skryptów.

Każdy z dołączonych plików ns-3 znajduje się w katalogu o nazwie ns3 (podkatalog budowy), aby uniknąć konfliktów nazw plików podczas procesu budowy. Plik ns3/core-module.h odpowiada modułowi ns-3, który znajdziesz w katalogu src/core w zainstalowanej wersji. W wykazie tego katalogu znajdziesz dużą ilość plików nagłówkowych. Podczas budowy Waf umieszcza publiczne pliki nagłówkowe w katalogu ns3 w podkatalogu build/debug

Jeśli nie masz takiego katalogu, oznacza to, że podczas budowy wersji wydania ns‑3 nie określiłeś katalogu wyjściowego, wykonaj budowę w następujący sposób:
$ ./waf configure --build-profile=debug --out=build/debug
$ . /waf build
lub
$ ./waf configure --build-profile=optimized --out=build/optimized
$ . /waf build

lub build/optimized, w zależności od Twojej konfiguracji. Waf będzie również automatycznie generować dołączany plik modułu do ładowania wszystkich publicznych plików nagłówkowych. Ponieważ oczywiście rygorystycznie przestrzegasz tego przewodnika, już zrobiłeś

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

aby skonfigurować projekt do wykonywania kompilacji debug, które obejmują przykłady i testy. Zrobiłeś również

$ ./waf

aby zbudować projekt. Więc teraz, gdy spojrzysz do katalogu ../..../build/debug/ns3, wśród innych znajdziesz pliki nagłówkowe czterech modułów, jak pokazano powyżej. Możesz rzucić okiem na zawartość tych plików i odkryć, że zawierają wszystkie publiczne pliki używane przez odpowiednie moduły.

4.2.3 Przestrzeń nazw ns3

Następna linia w skrypcie first.cc to deklaracja przestrzeni nazw.

using namespace ns3;

Projekt ns‑3 jest zrealizowany w przestrzeni nazw C++, nazywanej ns3. Grupa ta gromadzi wszystkie deklaracje związane z ns‑3 w zasięgu poza globalną przestrzenią nazw, co, jak mamy nadzieję, ułatwi integrację z innym kodem. Użycie operatora C++ wprowadza przestrzeń nazw ns‑3 do bieżącego (globalnego) regionu deklaracyjnego. To tylko sposób na powiedzenie, że po tej deklaracji nie będziesz musiał wprowadzać operatora rozdzielenia ns3:: przed całym kodem ns‑3, aby móc go używać. Jeżeli nie jesteś zaznajomiony z przestrzeniami nazw, zapoznaj się z właściwie dowolnym podręcznikiem do C++ i porównaj przestrzeń nazw ns3 z używaniem przestrzeni nazw std oraz deklaracji. using namespace std; w przykładach korzystania z operatora wyjścia cout i strumieni.

4.2.4 Rejestrowanie (Logging)

Następna linia skryptu wygląda następująco,

NS_LOG_COMPONENT_DEFINE ("FirstScriptExample");

Będziemy używać tego stwierdzenia jako wygodnego miejsca do omówienia naszego systemu dokumentacji Doxygen. Jeśli spojrzysz na stronę internetową projektu ns‑3, znajdziesz link „Dokumentacja” (Documentation) w panelu nawigacyjnym. Jeśli wybierzesz ten link, przejdziesz na naszą stronę dokumentacji. Istnieje link do „Ostatniego wydania”, który przeniesie Cię do dokumentacji dla najnowszej stabilnej wersji ns‑3. Jeśli wybierzesz link „Dokumentacja API”, przejdziesz na stronę dokumentacji API ns‑3.

Po lewej stronie strony znajdziesz graficzne przedstawienie struktury dokumentacji. Dobrym miejscem na rozpoczęcie jest „książka” Modules ns‑3 w drzewie nawigacji ns‑3. Jeśli rozwiniesz Modules, zobaczysz listę dokumentacji modułów ns‑3. Jak wcześniej omawiano, koncepcja modułu jest ściśle związana z plikami włączonymi w dany moduł. Podsystem logowania ns‑3 jest omówiony w sekcji Użycie modułu logowania, dlatego wrócimy do niej później w tym poradniku, ale możesz dowiedzieć się o wyżej przedstawionym stwierdzeniu, przeglądając moduł Rdzeń, a następnie otwierając książkę Narzędzia debugowania, a następnie wybierając stronę Logowanie. Kliknij na Logowanie.

Teraz powinieneś przejrzeć dokumentację Doxygen dla modułu Logowanie. Na liście makr u góry strony zobaczysz wpis dla NS_LOG_COMPONENT_DEFINE. Zanim klikniesz w link, koniecznie zapoznaj się z „Szczegółowym opisem” modułu rejestrowania, aby zrozumieć jego działanie w całości. Możesz to zrobić, przewijając w dół lub wybierając „Więcej…” pod diagramem.

Gdy już będziesz miał ogólne pojęcie o tym, co się dzieje, przejdź do dokumentacji dotyczącej konkretnego NS_LOG_COMPONENT_DEFINE. Nie będę powtarzać dokumentacji tutaj, ale podsumowując, powiem, że ten wiersz deklaruje komponent rejestrowania o nazwie FirstScriptExample, który umożliwia włączanie i wyłączanie rejestracji komunikatów w konsoli przy użyciu linku do nazwy.

4.2.5 Funkcja główna

W następnych wierszach skryptu zobaczysz,

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

To po prostu deklaracja głównej funkcji twojego programu (skryptu). Jak w każdej aplikacji w C++, musisz zdefiniować funkcję main, która jest wykonywana jako pierwsza. Nie ma nic specjalnego. Twój skrypt ns-3 to po prostu program w C++. Następny wiersz ustawia rozdzielczość czasu na 1 nanosekundę, co jest wartością domyślną:

Time::SetResolution (Time::NS);

Rozdzielczość czasu lub po prostu rozdzielczość — to najmniejsza wartość czasu, która może być użyta (najmniejsza przedstawialna różnica między dwoma wartościami czasu). Możesz zmienić rozdzielczość tylko raz. Mechanizm zapewniający tę elastyczność zużywa pamięć, dlatego gdy rozdzielczość zostanie jawnie ustawiona, zwalniamy pamięć, zapobiegając dalszym aktualizacjom. (Jeśli nie ustawisz rozdzielczości jawnie, domyślnie wyniesie ona jedną nanosekundę, a pamięć zostanie zwolniona przy rozpoczęciu symulacji.)

Dwa następne wiersze skryptu służą do włączenia dwóch komponentów rejestrowania, które są wbudowane w aplikacje EchoClient i EchoServer:

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

Jeśli przeczytałeś dokumentację komponentu Logging, zobaczysz, że istnieje kilka poziomów szczegółowości logowania, które możesz włączyć dla każdego komponentu. Te dwie linie kodu włączają logowanie debugowania na poziomie INFO dla klientów i serwerów echo. Na tym poziomie aplikacja podczas symulacji będzie drukować komunikaty przy wysyłaniu i odbieraniu pakietów.

Teraz przejdźmy bezpośrednio do tworzenia topologii i uruchamiania symulacji. Wykorzystamy obiekty pomocników topologii, aby ułatwić tę pracę.

4.2.6 Użycie asystentów topologii

Następujące dwie linie kodu w naszym skrypcie faktycznie utworzą obiekty Node ns-3, które będą reprezentować komputery w symulacji.

NodeContainer nodes;
nodes.Create (2);

Zanim przejdziemy dalej, znajdźmy dokumentację dla klasy NodeContainer. Innym sposobem na dotarcie do dokumentacji dla tej klasy jest zakładka Classes na stronach Doxygen. Jeśli masz już otwarty Doxygen, wystarczy przewinąć w górę do góry strony i wybrać zakładkę Classes. Powinieneś zobaczyć nowy zestaw zakładek, z jednym z nich zawierającym listę klas. Pod tą zakładką zobaczysz listę wszystkich klas ns-3. Przewiń w dół do ns3::NodeContainer. Gdy znajdziesz klasę, wybierz ją, aby przejść do dokumentacji tej klasy.

Jak pamiętamy, jednym z naszych kluczowych abstrakcji jest węzeł. Reprezentuje on komputer, do którego zamierzamy dodać takie rzeczy, jak stosy protokołów, aplikacje i karty peryferyjne. Pomocnik topologii NodeContainer zapewnia wygodny sposób tworzenia, zarządzania i dostępu do dowolnych obiektów Węzeł, które tworzymy w celu uruchomienia symulacji. Pierwsza linia powyżej po prostu deklaruje NodeContainer, który nazywamy nodes. Druga linia wywołuje metodę Create dla obiektu nodes i prosi kontener o utworzenie dwóch węzłów. Jak opisano w Doxygen, kontener prosi w systemie ns-3 o utworzenie dwóch obiektów Węzeł i przechowuje wskaźniki do tych obiektów wewnątrz.

Utworzone w skrypcie węzły na razie nic nie robią. Następnym krokiem w budowie topologii jest podłączenie naszych węzłów do sieci. Najprostsza forma sieci, którą obsługujemy, to połączenie punkt-punkt między dwoma węzłami. Teraz stworzymy takie połączenie.

PointToPointHelper

Tworzymy dwupunktowe połączenie, działając według znanego nam schematu, wykorzystując obiekt pomocniczy topologii do realizacji niskopoziomowych zadań niezbędnych do nawiązania połączenia. Przypomnijmy, że nasze dwie kluczowe abstrakcje NetDevice i Channel. W prawdziwym świecie te terminy odpowiadają mniej więcej kartom peryferyjnym i kablom sieciowym. Zasadniczo te dwie rzeczy są ze sobą ściśle powiązane i nikt nie może liczyć na wymianę, na przykład urządzeń Ethernet za pomocą łącza bezprzewodowego. Nasi pomocnicy topologiczni podążają za tym ścisłym związkiem, dlatego w tym scenariuszu będziesz używał jednego obiektu PointToPointHelper do konfiguracji i łączenia obiektów ns-3 PointToPointNetDevice i PointToPointChannel. Następne trzy linie w scenariuszu:

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

Pierwsza linia,

PointToPointHelper pointToPoint;

tworzy w stosie instancję obiektu PointToPointHelper. Z punktu widzenia wyższego poziomu następna linia,

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

informuje obiekt PointToPointHelper o użyciu wartości „5 Mbps” (pięć megabitów na sekundę) jako „DataRate».

Z bardziej szczegółowego punktu widzenia linia „DataRate” odpowiada temu, co nazywamy atrybutem PointToPointNetDevice. Jeśli spojrzysz na Doxygen dla klasy ns3::PointToPointNetDevice i w dokumentacji metody GetTypeId znajdziesz listę atrybutów zdefiniowanych dla urządzenia. Wśród nich będzie atrybut „DataRate”. Większość obiektów ns-3 widocznych dla użytkowników ma podobne listy atrybutów. Wykorzystujemy ten mechanizm do prostej konfiguracji symulacji bez rekompilacji, jak zobaczysz w następnej sekcji.

Podobnie jak „DataRate” w PointToPointNetDevice, znajdziesz atrybut „Delay” związany z PointToPointChannel. Ostatnia linia,

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

mówi PointToPointHelper informuje o użyciu wartości „2 ms” (dwie milisekundy) jako wartości opóźnienia propagacji przez kanał punkt-punkt, który następnie zostanie utworzony.

NetDeviceContainer

Na ten moment w naszym scenariuszu mamy NodeContainer, który zawiera dwa węzły. Mamy PointToPointHelper, który jest przygotowany do tworzenia obiektów PointToPointNetDevices i łączenia ich za pomocą obiektu PointToPointChannel. Tak jak używaliśmy pomocniczego obiektu NodeContainer do tworzenia węzłów, poprosimy PointToPointHelper o wykonanie dla nas pracy związanej z tworzeniem, konfigurowaniem i instalowaniem naszych urządzeń. Potrzebujemy listy wszystkich stworzonych obiektów NetDevice, dlatego używamy NetDeviceContainer do ich przechowywania tak jak używaliśmy NodeContainer do przechowywania stworzonych przez nas węzłów. Następujące dwie linie kodu,

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

kończą konfigurowanie urządzeń i kanału. Pierwsza linia deklaruje kontener urządzeń, wspomniany wcześniej, a druga wykonuje podstawową pracę. Metoda Zainstaluj obiektu PointToPointHelper przyjmuje NodeContainer jako parametr. Wewnątrz NetDeviceContainer dla każdego węzła znajdującego się w NodeContainer tworzy (w celu komunikacji punkt-punkt musi ich być dokładnie dwa) PointToPointNetDevice jest tworzony i przechowywany w kontenerze urządzeń. PointToPointChannel jest tworzony, a do niego dołączają dwa PointToPointNetDevices. Po utworzeniu obiektów, atrybuty przechowywane w PointToPointHelper, są używane do inicjowania odpowiednich atrybutów w utworzonych obiektach.

Po wywołaniu pointToPoint.Install (nodes) będziemy mieli dwa węzły, każdy z zainstalowanym urządzeniem sieciowym „punkt-punkt” i jednym kanałem „punkt-punkt” między nimi. Oba urządzenia będą skonfigurowane do przesyłania danych z prędkością pięciu megabitów na sekundę z opóźnieniem transmisji przez kanał wynoszącym dwie milisekundy.

InternetStackHelper

Teraz mamy skonfigurowane węzły i urządzenia, ale na naszych węzłach nie są zainstalowane stosy protokołów. Następujące dwie linie kodu zajmą się tym.

InternetStackHelper stack;
stack.Install (nodes);

InternetStackHelper — stanowi pomocnika topologii dla internetowych stosów, podobnie jak PointToPointHelper dla urządzeń sieciowych punkt-punkt. Metoda Zainstaluj przyjmuje NodeContainer jako parametr. Podczas wykonania zainstaluje stos internetu (TCP, UDP, IP itd.) na każdym węźle kontenera.

Ipv4AddressHelper

Następnie musimy przypisać nasze urządzenia do adresów IP. Oferujemy pomocnika topologii do zarządzania przydzielaniem adresów IP. Jedynym widocznym dla użytkownika API jest ustawienie bazowego adresu IP i maski sieci, które będą używane podczas wykonywania rzeczywistego przydzielania adresów (jest to realizowane na niższym poziomie wewnątrz pomocnika). Następujące dwie linie kodu w naszym przykładzie skryptu first.cc,

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

deklarują dodatkowy obiekt adresu i mówią mu, że powinien zacząć przydzielać adresy IP z sieci 10.1.1.0, używając do określenia maski bitowej 255.255.255.0. Domyślnie przydzielone adresy będą zaczynały się od jeden i będą się monotonicznie zwiększać, więc pierwszy adres przydzielony z tej puli to 10.1.1.1, następnie 10.1.1.2 itd. W rzeczywistości, na niskim poziomie system ns-3 zapamiętuje wszystkie przydzielone adresy IP i generuje poważny błąd, jeśli przypadkowo stworzysz sytuację, w której ten sam adres będzie wygenerowany dwa razy (swoją drogą, ten błąd jest trudny do debugowania).

Następny wiersz kodu,

Ipv4InterfaceContainer interfaces = address.Assign(devices);

wykonuje faktyczne przypisanie adresu. W ns-3 tworzymy powiązanie między adresem IP a urządzeniem, używając obiektu Ipv4Interface. Tak jak czasami potrzebujemy listy urządzeń sieciowych stworzonych przez pomocnika do późniejszego użycia, tak samo czasami potrzebujemy listy obiektów Ipv4Interface. Ipv4InterfaceContainer oferuje tę funkcjonalność.

Zbudowaliśmy sieć punkt-punkt, z zainstalowanymi stakami i przydzielonymi adresami IP. Teraz potrzebujemy aplikacji na każdym węźle do generowania ruchu.

4.2.7 Użycie aplikacji

Jedną z podstawowych abstrakcji systemu ns-3 jest Application (aplikacja). W tym scenariuszu używamy dwóch specjalizacji podstawowej klasy Application ns-3 o nazwie UdpEchoServerApplication i UdpEchoClientApplication. Tak jak w poprzednich przypadkach, używamy obiektów pomocniczych do konfigurowania i zarządzania podstawowymi obiektami. Tutaj używamy UdpEchoServerHelper i UdpEchoClientHelpero, aby ułatwić sobie życie.

UdpEchoServerHelper

Następujące wiersze kodu w naszym przykładzie skryptu first.cc służą do konfiguracji aplikacji serwera echa UDP na jednym z węzłów, które stworzyliśmy wcześniej.

UdpEchoServerHelper echoServer(9);

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

Pierwszy wiersz kodu w powyższym fragmencie tworzy UdpEchoServerHelper. Jak zwykle, to nie jest sama aplikacja, lecz obiekt, który pomaga nam tworzyć rzeczywiste aplikacje. Jednym z naszych założeń jest przekazywanie niezbędnych atrybutów do konstruktora obiektu pomocniczego. W tym przypadku pomocnik nie może zrobić nic użytecznego, jeśli nie podano numeru portu, na którym serwer będzie oczekiwał pakietów; ten numer musi również być znany klientowi. W tym przypadku przekazujemy konstruktorowi pomocnika numer portu. Konstruktor, z kolei, po prostu wykonuje SetAttribute z przekazaną wartością. Później, jeśli zajdzie taka potrzeba, przy użyciu SetAttribute będziesz mógł ustawić inną wartość atrybutu "Port".

Podobnie jak wiele innych obiektów pomocniczych, obiekt UdpEchoServerHelper ma metodę Zainstaluj. Wykonanie tej metody faktycznie prowadzi do stworzenia podstawowej aplikacji echo-serwera i przypisania jej do węzła. Interesujące jest to, że metoda Zainstaluj przyjmuje NodeContainter jako parametr, podobnie jak inne Zainstaluj metody, które widzieliśmy.

Niejawna konwersja C++, działająca tutaj, przyjmuje wynik metody node.Get(1) (która zwraca inteligentny wskaźnik na obiekt węzła — Ptr) i wykorzystuje go w konstruktorze dla anonimowego obiektu NodeContainer, który następnie jest przekazywany do metody Zainstaluj. Jeśli nie możesz określić w kodzie C++, która sygnatura metody kompiluje się i wykonuje, poszukaj wśród niejawnych konwersji.

Teraz widzimy, że echoServer.Install zostanie zainstalowana aplikacja UdpEchoServerApplication na znalezionym w NodeContainer, który używamy do zarządzania naszymi węzłami, węzeł z indeksem 1. Metoda Zainstaluj zwróci kontener, który zawiera wskaźniki na wszystkie aplikacje (w tym przypadku jedną, ponieważ przekazaliśmy anonimowego NodeContainer, zawierającego jeden węzeł) stworzonego przez pomocnika.

Aplikacjom trzeba wskazać moment rozpoczęcia generowania ruchu "start" i mogą być potrzebne dodatkowe dane, aby wskazać czas, kiedy należy je zatrzymać "stop".Oba parametry podajemy. Te czasy są ustawiane za pomocą metod ApplicationContainer Rozpocznij i Stop. Te metody przyjmują parametry typu Czas. W tym przypadku używamy jawnej sekwencji konwersji C++, aby wziąć C++ double 1.0 i przekształcić go w obiekt tns‑3 Time, używający obiektu Seconds do konwersji na sekundy. Pamiętaj, że zasady konwersji mogą być kontrolowane przez autora modelu, a C++ ma swoje własne zasady, dlatego nie zawsze możesz polegać na tym, że parametry zostaną przekształcone tak, jak oczekiwałeś. Dwie linie,

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

spowoduje, że aplikacja echo serwera uruchomi się (włączy się automatycznie) po jednej sekundzie od rozpoczęcia symulacji i zatrzyma się (wyłączy się) po dziesięciu sekundach symulacji. Mając na uwadze, że zadeklarowaliśmy zdarzenie symulacji (zdarzenie zatrzymania aplikacji), które zostanie wykonane po dziesięciu sekundach, to symulacja będzie trwała nie mniej niż dziesięć sekund działania sieci.

UdpEchoClientHelper

Aplikacja klienta echo jest konfigurowana w sposób niemal identyczny jak serwer. Istnieje podstawowy obiekt UdpEchoClientApplication, którym zarządza
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));;

Jednak dla echo klienta musimy ustawić pięć różnych atrybutów. Pierwsze dwa atrybuty są ustawiane w momencie tworzenia UdpEchoClientHelper. Przekazujemy parametry, które są używane (wewnętrznie w pomocniku) do ustawienia atrybutów "RemoteAddress" i "RemotePort" zgodnie z naszą umową o przekazywaniu wymaganych parametrów do konstruktora pomocnika.

Przypomnijmy, że używaliśmy Ipv4InterfaceContainer do śledzenia adresów IP, które przypisaliśmy naszym urządzeniom. Interfejs zerowy w kontenerze interfejsów odpowiada adresowi IP zerowego węzła w kontenerze węzłów. Pierwszy interfejs w kontenerze interfejsów odpowiada adresowi IP pierwszego węzła w kontenerze węzłów. Tak więc w pierwszej linii kodu (na górze) tworzymy pomocnika i informujemy go, że zdalnym adresem klienta będzie adres IP przypisany węzłowi, na którym znajduje się serwer. Mówimy również, że pakiety muszą być wysyłane na dziewiąty port.

Atrybut „MaxPackets” informuje klienta o maksymalnej liczbie pakietów, które możemy wysłać podczas symulacji. Atrybut „Interval” mówi klientowi, jak długo czekać między pakietami, a atrybut „PacketSize” określa, jak duża powinna być ładunek pakietu. Tą kombinacją atrybutów mówimy klientowi, aby wysłał jeden pakiet o wielkości 1024 bajtów.

Podobnie jak w przypadku serwera echo, ustawiamy atrybuty dla klienta echo, Rozpocznij i Stop, ale tutaj uruchamiamy klienta po sekundzie od włączenia serwera (po dwóch sekundach od rozpoczęcia symulacji).

4.2.8 Symulator

Na tym etapie musimy uruchomić symulację. Robimy to za pomocą globalnej funkcji Simulator::Run.

Simulator::Run ();

Kiedy wcześniej wywoływaliśmy metody,

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

faktycznie zaplanowaliśmy zdarzenia w symulatorze na 1,0 sekundy, 2,0 sekundy i dwa zdarzenia na 10,0 sekundy. Po wywołaniu Simulator::Run, system zacznie przeszukiwać listę zaplanowanych zdarzeń i je wykonywać. Na początku uruchomi zdarzenie po 1,0 sekundy, co aktywuje aplikację serwera echo (to zdarzenie może z kolei zaplanować wiele innych zdarzeń). Następnie uruchomi zdarzenie zaplanowane na t = 2,0 sekundy, które uruchomi aplikację klienta echo. Ponownie, to zdarzenie może zaplanować jeszcze wiele innych zdarzeń. Wykonanie zdarzenia uruchamiającego w kliencie echo rozpocznie etap przesyłania danych symulacji, wysyłając pakiet do serwera.

Akt wysyłania pakietu do serwera wywoła łańcuch zdarzeń, które zostaną automatycznie zaplanowane za kulisami i które będą implementować mechanikę wysyłania pakietu echo zgodnie z parametrami synchronizacji, które ustawiliśmy w skrypcie.

W rezultacie, ponieważ wysyłamy tylko jeden pakiet (przypomnijmy, że atrybut MaxPackets został ustawiony na jeden), łańcuch zdarzeń wywołany tym pojedynczym żądaniem echo klienta zakończy się, a symulacja przejdzie w tryb oczekiwania. Gdy to nastąpi, pozostałymi zaplanowanymi zdarzeniami będą zdarzenia Stop dla serwera i klienta. Gdy te zdarzenia zostaną wykonane, nie pozostanie żadne zdarzenie do dalszego przetworzenia, a Simulator::Run sterowanie zostanie zwrócone. Symulacja zakończona.

Pozostaje tylko posprzątać. Robimy to, wywołując globalną funkcję Simulator::Destroy. Ponieważ wywoływane były funkcje pomocnicze (lub kod niskiego poziomu ns-3), które zostały zorganizowane tak, aby w symulatorze były wstawione haki do zniszczenia wszystkich obiektów, które zostały utworzone. Nie musisz monitorować żadnych z tych obiektów samodzielnie – wszystko, co musiałeś zrobić, to wywołać Simulator::Destroy i wyjść. System ns-3 wykonuje tę trudną pracę za Ciebie. Pozostałe linie naszego pierwszego skryptu ns-3, first.cc, robią dokładnie to:

Simulator::Destroy ();
return 0;
}

Kiedy symulator się zatrzyma?

ns-3 to symulator zdarzeń dyskretnych (DE). W takim symulatorze każde zdarzenie jest związane z czasem jego wykonania, a symulacja trwa przez przetwarzanie zdarzeń w kolejności ich wystąpienia w trakcie symulacji. Zdarzenia mogą powodować planowanie przyszłych zdarzeń (na przykład, timer może zaplanować siebie ponownie, aby zakończyć odliczanie w kolejnym interwale).

Początkowe zdarzenia są zazwyczaj inicjowane przez obiekt, na przykład IPv6 zaplanuje określanie usług w sieci, zapytania o sąsiadów itp. Aplikacja planuje pierwsze zdarzenie wysyłania pakietu itp. Gdy zdarzenie jest przetwarzane, może generować zero, jedno lub więcej zdarzeń. W miarę postępu symulacji zdarzenia po prostu kończą się lub generują nowe. Symulacja zatrzyma się automatycznie, jeśli kolejka zdarzeń będzie pusta lub zostanie wykryte specjalne zdarzenie Stop. Zdarzenie Stop jest generowane przez funkcję Simulator::Stop (zatrzymanie czasu).

Jest typowy przypadek, w którym Simulator::Stop jest absolutnie niezbędny do zatrzymania symulacji: gdy istnieją samopodtrzymujące się zdarzenia. Samopodtrzymujące się (lub powtarzające się) zdarzenia to zdarzenia, które zawsze są ponownie planowane. W rezultacie zawsze utrzymują kolejkę zdarzeń jako niepustą. Istnieje wiele protokołów i modułów, które zawierają powtarzające się zdarzenia, na przykład:

• FlowMonitor – okresowe sprawdzanie utraconych pakietów;

• RIPng – okresowa transmisja aktualizacji tabel routingowych;

• itd.

W takich przypadkach Simulator::Stop jest niezbędny do prawidłowego zatrzymania symulacji. Ponadto, gdy ns-3 znajduje się w trybie emulacji, RealtimeSimulator jest używany do synchronizacji zegarów symulacji z zegarami maszyny, a Simulator::Stop jest niezbędny do zatrzymania procesu.

Wiele programów symulacyjnych w podręczniku nie wywołuje Simulator::Stop jest to oczywiste, ponieważ kończą się one automatycznie po wyczerpaniu zdarzeń w kolejce. Jednak te programy również zaakceptują wywołanie Simulator::Stop. Na przykład, następujący dodatkowy operator w pierwszym przykładzie programu zaplanuje wyraźne zatrzymanie na 11 sekundzie:

+ Simulator::Stop (Seconds (11.0));
  Simulator::Run ();
  Simulator::Destroy ();
  return 0;
}

Powyższe faktycznie nie zmieni zachowania tego programu, ponieważ ta konkretna symulacja naturalnie kończy się po 10 sekundach. Ale gdybyście zmienili czas zatrzymania w powyższym operatorze z 11 sekund na 1 sekundę, zauważylibyście, że symulacja zatrzymuje się nim jakiekolwiek wyjście trafi na ekran (ponieważ wyjście pojawia się około 2 sekundy czasu symulacji).

Ważne jest, aby wywołać Simulator::Stop przed wywołaniem Simulator::Run; w przeciwnym razie Simulator::Run może nigdy nie zwrócić kontroli do programu głównego, aby wykonać zatrzymanie!

4.2.9 Kompilacja twojego scenariusza

Ułatwiliśmy tworzenie twoich prostych skryptów. Wszystko, co musisz zrobić, to umieścić swój skrypt w katalogu scratch, a on zostanie automatycznie skompilowany, gdy uruchomisz Waf. Spróbujmy. Wróć do katalogu głównego i skopiuj examples/tutorial/first.cc do katalogu scratch

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

Teraz skompiluj swój pierwszy przykład skryptu, używając waf:

$ ./waf

Powinieneś zobaczyć komunikaty o tym, że twój pierwszy przykład został pomyślnie utworzony.

Waf: Wprowadzanie katalogu `/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: Opuszczenie katalogu `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
'build' zakończono pomyślnie (2.357s)

Teraz możesz uruchomić przykład (zauważ, że jeśli kompilujesz swój program w katalogu scratch, musisz go również uruchomić z scratch):

$ ./waf --run scratch/myfirst

Powinieneś zobaczyć podobne wyjście:

Waf: Wprowadzanie katalogu `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
Waf: Opuszczenie katalogu `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
'build' zakończono pomyślnie (0.418s) Wysłano 1024 bajty do 10.1.1.2
Odebrano 1024 bajty od 10.1.1.1
Odebrano 1024 bajty od 10.1.1.2

Tutaj widzisz, że system kompilacji sprawdza, czy plik został zbudowany, a następnie go uruchamia. Możesz zobaczyć wpis komponentu na kliencie echa, który wskazuje, że wysłał jeden 1024-bajtowy pakiet do serwera echa 10.1.1.2. Możesz także zobaczyć komponent logowania na serwerze echa, który informuje, że otrzymał 1024 bajty od 10.1.1.1. Serwer echa cicho zwraca pakiet, a w dzienniku klienta echa widzisz, że otrzymał swój pakiet z powrotem z serwera.

4.3 Kod źródłowy ns-3

Teraz, gdy użyłeś niektórych asystentów ns‑3, możesz spojrzeć na niektóre kody źródłowe, które implementują tę funkcjonalność. Najnowszy kod możesz zobaczyć na naszej stronie internetowej pod następującym linkiem: https://gitlab.com/nsnam/ns-3-dev.git. Tam znajdziesz stronicę podsumowującą Mercurial dla naszego repozytorium ns‑3. Na górze strony zobaczysz kilka linków,

summary | shortlog | changelog | graph | tags | files

Przewiń dalej i wybierz link do plików. Oto jak wyglądać będzie górny poziom większości naszych repozytoriów:

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

Nasze przykłady skryptów znajdują się w katalogu examples. Jeśli klikniesz na przykłady, zobaczysz listę podkatalogów. Jeden z plików w podkatalogu tutorial — first.cc. Jeśli klikniesz na first.cc zobaczysz kod, który właśnie przeanalizowałeś.

Kod źródłowy znajduje się głównie w katalogu src. Możesz zobaczyć kod źródłowy, klikając na nazwę katalogu lub klikając link plików po prawej stronie nazwy katalogu. Jeśli klikniesz na katalog src, otrzymasz listę podkatalogów src. Następnie, klikając na podkatalog core, znajdziesz listę plików. Pierwszy plik, który zobaczysz (w momencie pisania tego przewodnika) — abort.h. Jeśli klikniesz na link abort.h, zostaniesz przeniesiony do pliku źródłowego dla abort.h, który zawiera przydatne makra do wychodzenia ze skryptów, gdy wykryte zostaną nienormalne warunki. Kod źródłowy dla pomocników, których użyliśmy w tym rozdziale, można znaleźć w katalogu src/Applications/helper. Nie krępuj się przeszukiwać drzewo katalogów, aby zrozumieć, co gdzie się znajduje i zapoznać się ze stylem programowania ns‑3.

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster