poradnik dotyczący symulatora sieci ns-3. Rozdział 4

poradnik dotyczący symulatora sieci ns-3. Rozdział 4
Rozdział 1,2
rozdział 3

4 Przegląd koncepcji
4.1 Kluczowe abstrakcje
4.1.1 Węzeł
4.1.2 Zastosowanie
4.1.3 Kanał
4.1.4 Urządzenie sieciowe
4.1.5 Asystenci topologiczni
4.2 Pierwszy skrypt ns-3
4.2.1 Kod standardowy
4.2.2 Wtyczki
4.2.3 Przestrzeń nazw ns3
4.2.4 Rejestrowanie
4.2.5 Funkcja główna
4.2.6 Korzystanie z asystentów topologii
4.2.7 Korzystanie z Aplikacji
4.2.8 Symulator
4.2.9 Budowanie skryptu
4.3 ns-3 Kod źródłowy

Rozdział 4

Przegląd koncepcji

Pierwszą rzeczą, którą musimy zrobić zanim zaczniemy uczyć się lub pisać kod ns-3, jest wyjaśnienie kilku podstawowych pojęć i abstrakcji występujących w systemie. Wiele z tego może wydawać się niektórym oczywiste, ale zalecamy poświęcenie czasu na przeczytanie tej sekcji, aby mieć pewność, że zaczynasz na solidnych podstawach.

4.1 Kluczowe abstrakcje

W tej sekcji przyjrzymy się pewnym terminom, które są powszechnie używane w Internecie, ale mają określone znaczenie w ns-3.

4.1.1 Węzeł

W żargonie internetowym urządzenie komputerowe łączące się z siecią nazywane jest hostem, a czasami systemem końcowym. Ponieważ ns-3 jest symulatorem sieci, a nie Internetu, celowo nie używamy terminu host, ponieważ jest on ściśle powiązany z Internetem i jego protokołami. Zamiast tego używamy bardziej ogólnego terminu, używanego także w innych symulatorach, który wywodzi się z teorii grafów: węzeł (węzeł).

W ns-3 podstawowa abstrakcja urządzenia obliczeniowego nazywana jest węzłem. Ta abstrakcja jest reprezentowana w C++ przez klasę Node. Klasa WęzełWęzeł (węzeł) zapewnia metody manipulowania reprezentacjami urządzeń obliczeniowych w symulacjach.

Musisz zrozumieć Node jak komputer, do którego dodajesz funkcjonalność. Dodasz takie rzeczy, jak aplikacje, stosy protokołów i karty peryferyjne ze sterownikami, które pozwolą komputerowi wykonywać użyteczną pracę. W ns-3 używamy tego samego podstawowego modelu.

4.1.2 Zastosowanie

Ogólnie oprogramowanie komputerowe dzieli się na dwie szerokie klasy. Oprogramowanie systemowe organizuje różne zasoby komputera, takie jak pamięć, cykle procesora, dysk, sieć itp., zgodnie z pewnym modelem obliczeniowym. Oprogramowanie systemowe zazwyczaj nie wykorzystuje tych zasobów do wykonywania zadań, które przynoszą bezpośrednie korzyści użytkownikowi. Użytkownik zazwyczaj uruchamia aplikację, aby osiągnąć konkretny cel, jakim jest uzyskanie i wykorzystanie zasobów kontrolowanych przez oprogramowanie systemowe.

Często linia oddzielająca system od aplikacji jest wyznaczana na podstawie zmian poziomu uprawnień, które występują w pułapkach systemu operacyjnego. ns-3 nie ma prawdziwej koncepcji systemu operacyjnego, a zatem nie ma koncepcji poziomów uprawnień ani wywołań systemowych. Mamy jednak pomysł na aplikację. Podobnie jak w „prawdziwym świecie” aplikacje działają na komputerach w celu wykonywania zadań, aplikacje ns-3 działają na węzłach ns-3 w celu kontrolowania symulacji w symulowanym świecie.

W ns-3 podstawową abstrakcją programu użytkownika, która generuje pewną aktywność na potrzeby modelowania, jest aplikacja. Ta abstrakcja jest reprezentowana w C++ przez klasę Application. Klasa Application udostępnia metody manipulowania widokami wersji aplikacji na poziomie użytkownika w symulacjach. Od programistów oczekuje się specjalizacji klasy Application w sensie programowania obiektowego w celu tworzenia nowych aplikacji. W tym samouczku wykorzystamy specjalizacje klasy Application o nazwie Aplikacja UdpEchoClient и Aplikacja UdpEchoServer. Jak można się spodziewać, aplikacje te tworzą zestaw aplikacji klient/serwer używanych do generowania i wysyłania echa pakietów sieciowych.

4.1.3 Kanał

W prawdziwym świecie możesz podłączyć komputer do sieci. Często media, którymi przesyłane są dane w tych sieciach, nazywane są kanałami. Podłączając kabel Ethernet do gniazdka elektrycznego, podłączasz komputer do łącza Ethernet. W symulowanym świecie ns-3 węzeł jest połączony z obiektem reprezentującym kanał komunikacyjny. Tutaj podstawowa abstrakcja podsieci komunikacyjnej nazywa się kanałem i jest reprezentowana w C++ przez klasę Channel.

Klasa KanałKanał zapewnia metody zarządzania interakcją obiektów podsieci i łączenia z nimi hostów. Kanały mogą być również specjalizowane przez programistów w sensie programowania obiektowego. Specjalizacja kanałów może modelować coś tak prostego jak przewód. Dedykowany kanał może również modelować złożone rzeczy, takie jak duży przełącznik Ethernet lub trójwymiarową przestrzeń pełną przeszkód w przypadku sieci bezprzewodowych.

W tym samouczku będziemy używać wyspecjalizowanych wersji kanału o nazwie CsmaChannelCsmaKanał, PointToPointChannelPointToPointChannel и WifiKanałWifiKanał. Kanał Csmana przykład modeluje wersję podsieci komunikacyjnej, która implementuje środowisko komunikacyjne z wielodostępem z wykorzystaniem operatora. Daje nam to funkcjonalność podobną do Ethernetu.

4.1.4 Urządzenie sieciowe

Kiedyś, aby podłączyć komputer do sieci, trzeba było kupić odpowiedni kabel sieciowy i urządzenie sprzętowe zwane (w terminologii PC) kartą peryferyjną, które należało zainstalować w komputerze. Jeśli karta peryferyjna implementowała pewne funkcje sieciowe, nazywano je kartami interfejsu sieciowego lub kartami sieciowymi. Obecnie większość komputerów jest wyposażona w zintegrowany interfejs sieciowy i nie są postrzegane przez użytkowników jako oddzielne urządzenia.

Karta sieciowa nie będzie działać bez sterownika programowego sterującego jej sprzętem. W systemie Unix (lub Linux) urządzenie peryferyjne jest klasyfikowane jako urządzenie. Urządzeniami zarządza się za pomocą sterowników urządzeń, a urządzeniami sieciowymi (NIC) zarządza się za pomocą sterowników urządzeń sieciowych (sterowniki urządzeń sieciowych) i są zbiorczo nazywane urządzeniami sieciowymi (urządzenia sieciowe). W systemach Unix i Linux urządzenia sieciowe określa się nazwami takimi jak eth0.

W ns-3 abstrakcja urządzeń sieciowych obejmuje zarówno oprogramowanie sterownika, jak i modelowany sprzęt. W symulacji w węźle „instaluje się” urządzenie sieciowe, aby umożliwić mu komunikację z innymi węzłami za pośrednictwem kanałów. Podobnie jak prawdziwy komputer, węzeł może być podłączony do wielu kanałów za pośrednictwem wielu urządzeń Urządzenia sieciowe.

Abstrakcja sieciowa urządzenia jest reprezentowana w C++ przez klasę Urządzenie sieciowe. Klasa Urządzenie sieciowe udostępnia metody zarządzania połączeniami z obiektami Node i Channel; i może być specjalizowany przez programistów w sensie programowania obiektowego. W tym samouczku użyjemy kilku wyspecjalizowanych wersji NetDevice o nazwie Urządzenie CsmaNet, Urządzenie PointToPointNet и Urządzenie WifiNet. Podobnie jak karta sieciowa Ethernet jest zaprojektowana do pracy w sieci Ethernet, Urządzenie CsmaNet zaprojektowany do pracy Kanał Csma, Urządzenie PointToPointNet zaprojektowany do pracy kanał punkt-punktI Urządzenie WifiNet - zaprojektowany do pracy Kanał Wi-Fi.

4.1.5 Asystenci topologiczni

W prawdziwej sieci znajdziesz komputery-hosty z dodanymi (lub wbudowanymi) kartami sieciowymi. W ns-3 powiedzielibyśmy, że zobaczysz węzły z podłączonymi urządzeniami NetDevices. W dużej symulowanej sieci konieczne będzie zorganizowanie połączeń między wieloma obiektami Node, Urządzenie sieciowe и Kanał.

Od momentu podłączenia NetDevices do węzłów, NetDevices do łączy, przydzielenia adresów IP itp. w ns-3 są częstym zadaniem, aby było to tak proste, jak to tylko możliwe, zapewniamy tak zwane pomocniki topologii. Na przykład, aby utworzyć urządzenie NetDevice, należy wykonać wiele operacji na jądrze ns-3, dodać adres MAC, zainstalować urządzenie sieciowe w węźle, skonfigurować stos protokołów węzła, a następnie podłączyć urządzenie NetDevice do kanału. Jeszcze więcej pracy będzie wymagało podłączenie wielu urządzeń do łączy wielopunktowych, a następnie połączenie poszczególnych sieci w sieć Internetworks. Dla Twojej wygody udostępniamy obiekty pomocnicze topologii, które łączą te wiele operacji w łatwy w użyciu model.

4.2 Pierwszy skrypt ns-3

Jeśli zainstalowałeś system zgodnie z sugestią powyżej, będziesz mieć wersję ns-3 w katalogu o nazwie repos w swoim katalogu domowym. Przejdź do katalogu zwolnić

Jeśli nie masz takiego katalogu, oznacza to, że nie określiłeś katalogu wyjściowego podczas tworzenia wersji ns-3, zbuduj w ten sposób:
$ ./waf konfiguracji —build-profile=release —out=build/release,
Budowa $ ./waf

tam powinieneś zobaczyć strukturę katalogów 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 przykłady/samouczek. Powinieneś zobaczyć znajdujący się tam plik o nazwie pierwszy.cc. Jest to skrypt, który utworzy proste połączenie punkt-punkt pomiędzy dwoma węzłami i prześle jeden pakiet pomiędzy węzłami. Przyjrzyjmy się temu skryptowi linia po linii; w tym celu otwórz plik First.cc w swoim ulubionym edytorze.

4.2.1 Kod standardowy
Pierwsza linia pliku to linia trybu edytora emacs. Informuje emacsa o konwencjach formatowania (stylu kodowania), których używamy w naszym kodzie źródłowym.

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

Jest to zawsze dość kontrowersyjna kwestia, dlatego musimy wyjaśnić sprawę, aby od razu usunąć ją z drogi. Projekt ns-3, podobnie jak większość dużych projektów, przyjął styl kodowania, z którym musi być zgodny cały tworzony kod. Jeśli chcesz wnieść swój kod do projektu, ostatecznie będziesz musiał dostosować się do standardu kodowania ns-3, jak opisano w pliku doc/codingstd.txt lub pokazane na stronie internetowej projektu: https://www.nsnam.org/develop/contributing-code/coding-style/.

Zalecamy przyzwyczajenie się do wyglądu i sposobu działania kodu ns-3 i stosowanie tego standardu podczas pracy z naszym kodem. Cały zespół programistów i współpracownicy zgodzili się na to po pewnym narzekaniu. Powyższa linia trybu emacs ułatwia prawidłowe formatowanie, jeśli używasz edytora emacs.

Symulator ns-3 posiada licencję na użytkowanie Powszechna Licencja Publiczna GNU. W każdym pliku dystrybucyjnym ns-3 zobaczysz odpowiedni nagłówek GNU. Często nad tekstem GPL i autorem, jak pokazano poniżej, można zobaczyć informację o prawach autorskich jednej z instytucji uczestniczących w projekcie ns-3.

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

Sam kod zaczyna się od serii instrukcji włączających (zawierać).

#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 zaawansowanym użytkownikom skryptów poradzić sobie z dużą liczbą plików nagłówkowych obecnych w systemie, grupujemy je według ich użycia w duże moduły. Udostępniamy pojedynczy plik nagłówkowy, który będzie rekurencyjnie ładować wszystkie pliki nagłówkowe użyte w danym module. Zamiast szukać dokładnie tego, czego potrzebujesz nagłówka i ewentualnie uzyskać poprawną listę zależności, dajemy Ci możliwość pobrania grupy plików z dużą szczegółowością. Nie jest to najbardziej efektywne podejście, ale z pewnością znacznie ułatwia pisanie skryptów.

Każdy z plików dołączanych do ns-3 jest umieszczany w katalogu o nazwie Ns3 (podkatalog kompilacji), aby uniknąć konfliktów nazw plików podczas procesu kompilacji. Plik ns3/moduł-rdzeniowy.h odpowiada modułowi ns-3, który znajdziesz w katalogu źródło/rdzeń w wersji, którą zainstalowałeś. Na liście tego katalogu znajdziesz dużą liczbę plików nagłówkowych. Kiedy wykonasz montaż, Waf umieszcza publiczne pliki nagłówkowe w katalogu ns3 w podkatalogu budować/debugować

Jeśli nie masz takiego katalogu, oznacza to, że nie określiłeś katalogu wyjściowego podczas tworzenia wersji ns-3, zbuduj w ten sposób:
$ ./waf konfiguracji --build-profile=debug --out=kompilacja/debugowanie
Budowa $ ./waf
lub
$ ./waf konfiguracji --build-profile=optimized --out=build/optimized
Budowa $ ./waf

lub zbudować/zoptymalizować, w zależności od konfiguracji. Waf automatycznie wygeneruje również plik zawierający moduł, aby załadować wszystkie publiczne pliki nagłówkowe. Ponieważ oczywiście postępujesz zgodnie z tym przewodnikiem w sposób religijny, już to zrobiłeś

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

aby skonfigurować projekt do uruchamiania kompilacji debugowania zawierających przykłady i testy. Ty też to zrobiłeś

$ ./waf

zmontować projekt. Więc teraz, kiedy zajrzysz do katalogu ../../build/debug/ns3, znajdziesz tam między innymi pliki nagłówkowe czterech pokazanych powyżej modułów. Możesz sprawdzić zawartość tych plików i stwierdzić, że zawierają one wszystkie pliki publiczne używane przez odpowiednie moduły.

4.2.3 Przestrzeń nazw ns3

Następna linia w skrypcie pierwszy.cc jest deklaracją przestrzeni nazw.

using namespace ns3;

Projekt ns-3 jest zaimplementowany w przestrzeni nazw C++ o nazwie ns3. Grupuje to wszystkie deklaracje związane z ns-3 w zakres poza globalną przestrzenią nazw, co, miejmy nadzieję, pomoże w integracji z innym kodem. Użycie operatora C++ wprowadza przestrzeń nazw ns-3 do bieżącego (globalnego) regionu deklaratywnego. To fantazyjny sposób powiedzenia, że ​​po tej deklaracji nie będziesz musiał wpisywać operatora zezwolenia ns3::scope przed całym kodem ns-3, aby go użyć. Jeśli nie jesteś zaznajomiony z przestrzeniami nazw, zapoznaj się z prawie każdym podręcznikiem C++ i porównaj przestrzeń nazw ns3 przy użyciu przestrzeni nazw std i deklaracji using namespace std; w przykładach pracy z operatorem wyjściowym cout i strumienie.

4.2.4 Rejestrowanie

Następna linijka skryptu brzmi:

NS_LOG_COMPONENT_DEFINE ("FirstScriptExample");

Będziemy używać tego oświadczenia jako wygodnego miejsca do omówienia naszego systemu dokumentacji doxygen. Jeśli zajrzysz na stronę internetową projektu ns-3, na pasku nawigacyjnym znajdziesz łącze Dokumentacja. Jeśli klikniesz ten link, zostaniesz przeniesiony na stronę z dokumentacją. Istnieje łącze „Najnowsze wydanie”, które przeniesie Cię do dokumentacji najnowszej stabilnej wersji ns-3. Jeśli wybierzesz łącze „Dokumentacja API”, zostaniesz przeniesiony na stronę dokumentacji API ns-3.

Po lewej stronie znajdziesz graficzną reprezentację struktury dokumentacji. Dobrym miejscem na rozpoczęcie jest „książka” Moduły ns-3 w drzewie nawigacyjnym ns-3. Jeśli ujawnisz Moduły, zobaczysz listę dokumentacji modułów ns-3. Jak omówiono powyżej, koncepcja modułu jest tutaj bezpośrednio powiązana z plikami zawartymi w powyższym module. Podsystem logowania ns-3 został omówiony w rozdziale Korzystanie z modułu rejestrującego, więc wrócimy do tego w dalszej części tego samouczka, ale możesz poznać powyższe stwierdzenie, przeglądając moduł rdzeńi następnie otwieram książkę Narzędzia do debugowaniaa następnie wybierz stronę Logowanie. Kliknij Logowanie.

Powinieneś teraz przejrzeć dokumentację doxygen dla modułu Logowanie. Na liście makr u góry strony zobaczysz wpis dla NS_LOG_COMPONENT_DEFINE. Przed kliknięciem linku koniecznie zapoznaj się z „Szczegółowym opisem” modułu rejestracji, aby zrozumieć, jak ogólnie on działa. Aby to zrobić, możesz przewinąć w dół lub wybrać „Więcej…” pod wykresem.

Kiedy już będziesz miał ogólne pojęcie o tym, co się dzieje, przejdź dalej i przejrzyj dokumentację konkretnego NS_LOG_COMPONENT_DEFINE. Nie będę tutaj powielać dokumentacji, ale podsumowując, ta linia deklaruje komponent rejestracyjny o nazwie Pierwszy przykład skryptu, która umożliwia włączenie lub wyłączenie rejestrowania komunikatów konsoli na podstawie nazwy.

4.2.5 Funkcja główna

W kolejnych wierszach skryptu zobaczysz:

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

Jest to po prostu deklaracja głównej funkcji Twojego programu (skryptu). Jak w przypadku każdego programu w C++, musisz zdefiniować funkcję główną, która jest wykonywana w pierwszej kolejności. Nie ma tu nic specjalnego. Twój skrypt ns-3 to po prostu program w C++. Poniższa linia ustawia rozdzielczość czasową na 1 nanosekundę, co jest wartością domyślną:

Time::SetResolution (Time::NS);

Rozdzielczość czasowa lub po prostu rozdzielczość to najmniejsza wartość czasu, jaką można zastosować (najmniejsza możliwa do przedstawienia różnica między dwoma czasami). Rozdzielczość można zmienić dokładnie raz. Mechanizm zapewniający tę elastyczność zużywa pamięć, więc po jawnym ustawieniu rozdzielczości zwalniamy pamięć, uniemożliwiając dalsze aktualizacje. (Jeśli rozdzielczość nie zostanie wyraźnie ustawiona, domyślnie będzie to jedna nanosekunda, a pamięć zostanie zwolniona po rozpoczęciu symulacji.)

Poniższe dwa wiersze skryptu umożliwiają włączenie dwóch komponentów rejestrowania wbudowanych w aplikacje Klient Echo и Serwer Echo:

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

Jeśli przeczytasz dokumentację komponentu Logging, zobaczysz, że istnieje kilka poziomów rejestrowania/szczegółowości, które możesz włączyć w każdym komponencie. Te dwie linie kodu umożliwiają rejestrowanie debugowania na poziomie INFO dla klientów i serwerów echo. Na tym poziomie aplikacja będzie drukować komunikaty podczas wysyłania i odbierania pakietów podczas symulacji.

Teraz zajmiemy się tworzeniem topologii i przeprowadzaniem symulacji. Używamy obiektów pomocniczych topologii, aby maksymalnie ułatwić to zadanie.

4.2.6 Korzystanie z asystentów topologii

Następne dwie linie kodu w naszym skrypcie utworzą w rzeczywistości obiekty Node ns-3, które będą reprezentować komputery w symulacji.

NodeContainer nodes;
nodes.Create (2);

Zanim przejdziemy dalej, znajdźmy dokumentację klasy Kontener węzłów. Innym sposobem dotarcia do dokumentacji danej klasy jest skorzystanie z zakładki Zajęcia na stronach doxygen. Jeśli masz już otwarty Doxygen, po prostu przewiń w górę strony i wybierz zakładkę Zajęcia. Powinieneś zobaczyć nowy zestaw zakładek, z których jedna zawiera listę klas. W tej zakładce zobaczysz listę wszystkich klas ns-3. Przewiń w dół do ns3::Kontener węzłów. Gdy znajdziesz klasę, wybierz ją, aby przejść do dokumentacji tej klasy.

Jak pamiętamy, jedną z naszych kluczowych abstrakcji jest węzeł. Reprezentuje komputer, do którego będziemy dodawać takie rzeczy, jak stosy protokołów, aplikacje i karty peryferyjne. Asystent topologii Kontener węzłów zapewnia wygodny sposób tworzenia, zarządzania i uzyskiwania dostępu do dowolnych obiektów Node, który tworzymy w celu przeprowadzenia symulacji. Pierwsza linia powyżej po prostu deklaruje Kontener węzłów, które nazywamy węzłami. Druga linia wywołuje metodę Create na obiekcie nodes i prosi kontener o utworzenie dwóch węzłów. Jak opisano w doxygen, kontener żąda od systemu ns-3 utworzenia dwóch obiektów Node i przechowuje wewnętrznie wskaźniki do tych obiektów.

Węzły utworzone w skrypcie jeszcze nic nie robią. Kolejnym krokiem w budowaniu topologii jest podłączenie naszych węzłów do sieci. Najprostszą formą sieci, którą obsługujemy, jest połączenie punkt-punkt pomiędzy dwoma węzłami. Teraz utworzymy takie połączenie.

Pomocnik PointToPoint

Tworzymy połączenie punkt-punkt, korzystając ze znanego wzorca, używając obiektu pomocniczego topologii do wykonania prac niskiego poziomu wymaganych dla połączenia. Przypomnijmy, że nasze dwie kluczowe abstrakcje Urządzenie sieciowe и Kanał. W prawdziwym świecie terminy te z grubsza odpowiadają kartom peryferyjnym i kablom sieciowym. Zazwyczaj te dwie rzeczy są ze sobą ściśle powiązane i nikt nie może liczyć na udostępnianie np. urządzeń Ethernet przez kanał bezprzewodowy. Nasi pomocnicy topologii podążają za tą bliską zależnością, dlatego w tym scenariuszu użyjesz pojedynczego obiektu Pomocnik PointToPoint do ustawiania i łączenia obiektów ns-3 Urządzenie PointToPointNet и kanał punkt-punkt. Kolejne trzy linie skryptu:

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

Pierwsza linia,

PointToPointHelper pointToPoint;

tworzy instancję obiektu na stosie Pomocnik PointToPoint. Z najwyższego punktu widzenia następujący wiersz:

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

mówi obiektowi Pomocnik PointToPoint użyj wartości „5 Mbit/s” (pięć megabitów na sekundę) jako „Prędkość transmisji danych".

Z bardziej szczegółowego punktu widzenia ciąg „DataRate” odpowiada temu, co nazywamy atrybutem Urządzenie PointToPointNet. Jeśli spojrzysz doxygen dla klasy ns3::PointToPointNetDevice oraz w dokumentacji metody PobierzTypeId znajdziesz listę atrybutów zdefiniowanych dla urządzenia. Wśród nich znajdzie się atrybut „Prędkość transmisji danych" Większość widocznych dla użytkownika obiektów ns-3 ma podobne listy atrybutów. Używamy tego mechanizmu, aby łatwo skonfigurować symulację bez ponownej kompilacji, jak zobaczysz w następnej sekcji.

Podobny do "Prędkość transmisji danych" w PointToPointNetDevice znajdziesz atrybut „Delay” powiązany z PointToPointChannel. Ostatnia linia

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

mówi Pomocnik PointToPoint użyj wartości „2 ms” (dwie milisekundy) jako wartości opóźnienia propagacji dla później tworzonego łącza punkt-punkt.

Kontener urządzeń sieciowych

W tej chwili mamy to w scenariuszu Kontener węzłów, który zawiera dwa węzły. Mamy Pomocnik PointToPoint, który jest przygotowany do tworzenia obiektów Urządzenia PointToPointNet i łączenie ich za pomocą obiektu PointToPointChannel. Zapytamy, tak jak używaliśmy obiektu pomocniczego topologii NodeContainer do tworzenia węzłów Pomocnik PointToPoint wykonywać dla nas prace związane z tworzeniem, konfiguracją i instalacją naszych urządzeń. Potrzebujemy listy wszystkich utworzonych obiektów Urządzenie sieciowe, więc używamy Kontener urządzeń sieciowych przechowywać je w ten sam sposób, w jaki my to kiedyś robiliśmy Kontener węzłów do przechowywania utworzonych przez nas węzłów. Kolejne dwie linijki kodu,

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

pełną konfigurację urządzenia i kanału. Pierwsza linia deklaruje wspomniany powyżej kontener urządzenia, a druga wykonuje główną pracę. metoda Zainstalować obiekt Pomocnik PointToPoint akceptuje Kontener węzłów jako parametr. Wewnątrz Kontener urządzeń sieciowych dla każdego węzła znajdującego się w Kontener węzłów jest tworzony (dla komunikacji punkt-punkt muszą być dokładnie dwa z nich) Urządzenie PointToPointNet jest tworzony i zapisywany w kontenerze urządzenia. kanał punkt-punkt jest tworzony i dwa są do niego dołączone Urządzenia PointToPointNet. Po utworzeniu obiektów atrybuty przechowywane w Pomocnik PointToPoint, służą do inicjowania odpowiednich atrybutów w tworzonych obiektach.

Po wykonaniu połączenia pointToPoint.Install (węzły) będziemy mieli dwa węzły, każdy z zainstalowanym urządzeniem sieciowym typu punkt-punkt i jednym łączem typu punkt-punkt pomiędzy nimi. Obydwa urządzenia zostaną skonfigurowane do przesyłania danych z szybkością pięciu megabitów na sekundę z opóźnieniem transmisji w kanale wynoszącym dwie milisekundy.

Pomocnik Internetu Stack

Mamy teraz skonfigurowane węzły i urządzenia, ale w naszych węzłach nie zainstalowano stosów protokołów. Zajmą się tym następne dwie linie kodu.

InternetStackHelper stack;
stack.Install (nodes);

Pomocnik Internetu Stack - jest pomocnikiem topologii dla stosów internetowych, podobnym do PointToPointHelper dla urządzeń sieciowych typu punkt-punkt. metoda Zainstalować przyjmuje NodeContainer jako parametr. Po wykonaniu zainstaluje stos internetowy (TCP, UDP, IP itp.) w każdym węźle kontenera.

Pomocnik adresu IPv4

Następnie musimy powiązać nasze urządzenia z adresami IP. Udostępniamy asystenta topologii do zarządzania przydziałem adresów IP. Jedynym interfejsem API widocznym dla użytkownika jest ustawienie podstawowego adresu IP i maski sieci, które będą używane podczas faktycznej dystrybucji adresów (odbywa się to na niższym poziomie w programie pomocniczym). Kolejne dwie linijki kodu w naszym przykładowym skrypcie pierwszy.cc,

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

zadeklaruj obiekt pomocniczy adresu i powiedz mu, że powinien rozpocząć przydzielanie adresów IP z sieci 10.1.1.0, używając do ustalenia maski bitowej 255.255.255.0. Domyślnie przydzielane adresy będą zaczynać się od jednego i zwiększać monotonicznie, więc pierwszym adresem przydzielonym z tej bazy będzie 10.1.1.1, potem 10.1.1.2 itd. W rzeczywistości na niskim poziomie system ns-3 zapamiętuje wszystkie przydzielone adresy IP i generuje błąd krytyczny, jeśli przypadkowo stworzysz sytuację, w której ten sam adres zostanie wygenerowany dwukrotnie (nawiasem mówiąc, ten błąd jest trudny do debugowania).

Poniższa linia kodu,

Ipv4InterfaceContainer interfaces = address.Assign (devices);

dokonuje faktycznego przypisania adresu. W ns-3 nawiązujemy połączenie pomiędzy adresem IP a urządzeniem korzystającym z obiektu Interfejs IPv4. Tak jak czasami potrzebujemy utworzonej przez asystenta listy urządzeń sieciowych do późniejszego wykorzystania, tak czasami potrzebujemy listy obiektów Interfejs IPv4. Kontener interfejsu IPv4 zapewnia tę funkcjonalność.

Zbudowaliśmy sieć punkt-punkt, z zainstalowanymi stosami i przypisanymi adresami IP. Teraz potrzebujemy aplikacji w każdym węźle, aby generować ruch.

4.2.7 Korzystanie z Aplikacji

Kolejną z głównych abstrakcji systemu ns-3 jest Zastosowanie (aplikacja). W tym scenariuszu używamy dwóch specjalizacji klas bazowych Zastosowanie dzwoniono do ns-3 Aplikacja UdpEchoServer и Aplikacja UdpEchoClient. Podobnie jak w poprzednich przypadkach, do konfiguracji i zarządzania obiektami bazowymi wykorzystujemy obiekty pomocnicze. Tutaj używamy Pomocnik UdpEchoServer и Pomoc klienta UdpEchor obiekty ułatwiające nam życie.

Pomocnik UdpEchoServer

Poniższe wiersze kodu w naszym przykładowym skrypcie First.cc służą do konfiguracji aplikacji serwera echa UDP na jednym z utworzonych wcześniej węzłów.

UdpEchoServerHelper echoServer (9);

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

Tworzy się pierwsza linia kodu w powyższym fragmencie Pomocnik UdpEchoServer. Jak zwykle nie jest to sama aplikacja, jest to obiekt, który pomaga nam tworzyć prawdziwe aplikacje. Jedną z naszych konwencji jest przekazywanie wymaganych atrybutów konstruktorowi obiektu pomocniczego. W tym przypadku pomocnik nie może nic pożytecznego zrobić, jeśli nie zostanie podany numer portu, na którym serwer będzie nasłuchiwał pakietów, numer ten również musi być znany klientowi. W tym przypadku przekazujemy numer portu konstruktorowi pomocniczemu. Konstruktor z kolei po prostu to robi Ustawatrybut z przekazaną wartością. Później, jeśli zajdzie taka potrzeba, możesz użyć SetAttribute, aby ustawić inną wartość atrybutu Port.

Podobnie jak wiele innych obiektów pomocniczych, obiekt Pomocnik UdpEchoServer ma metodę Zainstalować. Wykonanie tej metody skutecznie tworzy podstawową aplikację serwera echa i wiąże ją z hostem. Co ciekawe, metoda Zainstalować akceptuje Kontener węzła jako parametr taki sam jak inne Zainstalować metody, które widzieliśmy.

Działająca tutaj niejawna konwersja C++ przyjmuje wynik metody węzeł.Get(1) (który zwraca inteligentny wskaźnik do obiektu węzła - Ptr ) i używa go w konstruktorze dla anonimowego obiektu Kontener węzłówktóry jest następnie przekazywany do metody Zainstalować. Jeśli nie możesz określić w kodzie C++, która sygnatura metody jest kompilowana i wykonywana, spójrz na niejawne konwersje.

Teraz to widzimy echoServer.Install zaraz zainstaluję aplikację Aplikacja UdpEchoServer na znaleziony w Kontener węzłówktórym zarządzamy naszymi węzłami, węzeł o indeksie 1. Metoda Zainstalować zwróci kontener zawierający wskaźniki do wszystkich aplikacji (w tym przypadku jednej, ponieważ przekazaliśmy anonimowy Kontener węzłów, zawierający jeden węzeł) utworzone przez pomocnika.

Aplikacje muszą określić, kiedy rozpocząć generowanie ruchu "początek" i może być konieczne dodatkowe określenie czasu, w którym należy je zatrzymać "zatrzymywać się". Oferujemy obie opcje. Czasy te ustalane są metodami Kontener aplikacji Start и Stop. Metody te akceptują parametry typu Czas. W tym przypadku używamy jawnej sekwencji konwersji C++, aby przyjąć C++ Podwójna 1.0 i przekonwertuj go na obiekt Time tns-3, który używa obiektu Seconds do konwersji na sekundy. Pamiętaj, że reguły konwersji mogą być kontrolowane przez autora modelu, a C++ ma swoje własne reguły, więc nie zawsze możesz liczyć na to, że parametry zostaną przekonwertowane zgodnie z oczekiwaniami. Dwie linie

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

spowoduje uruchomienie (automatyczne włączenie) aplikacji serwera echo jedną sekundę po rozpoczęciu symulacji i zatrzymanie (wyłączenie) po dziesięciu sekundach symulacji. W związku z tym, że zadeklarowaliśmy zdarzenie symulacyjne (zdarzenie zatrzymania aplikacji), które zostanie wykonane za dziesięć sekund, symulowane będzie co najmniej dziesięć sekund działania sieci.

Pomocnik klienta UdpEcho

Aplikacja Klienta przegapić skonfigurowany w sposób prawie podobny do serwera. Istnieje obiekt podstawowy Aplikacja UdpEchoClient, który jest zarządzany
Pomocnik klienta UdpEcho.

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 klienta echa musimy ustawić pięć różnych atrybutów. Pierwsze dwa atrybuty są ustawiane w momencie tworzenia Pomocnik klienta UdpEcho. Przekazujemy parametry, które są używane (wewnątrz pomocnika) do ustawiania atrybutów „Adres zdalny” и „Zdalny port” zgodnie z naszą umową o przekazaniu niezbędnych parametrów konstruktorowi pomocniczemu.

Pamiętajmy, że używaliśmy Kontener interfejsu IPv4 do śledzenia adresów IP, które przypisaliśmy naszym urządzeniom. Interfejs zerowy w kontenerze interfejsów będzie odpowiadał adresowi IP węzła zerowego w kontenerze węzłów. Pierwszy interfejs w kontenerze interfejsów odpowiada adresowi IP pierwszego węzła w kontenerze węzłów. Zatem w pierwszej linii kodu (powyżej) tworzymy pomocnika i informujemy go, że zdalnym adresem klienta będzie adres IP przypisany do hosta, na którym znajduje się serwer. Mówimy również, że musimy zorganizować wysyłanie pakietów do portu dziewiątego.

Atrybut „MaxPackets” informuje klienta o maksymalnej liczbie pakietów, jaką możemy wysłać podczas symulacji. Atrybut „Interval” informuje klienta, jak długo ma czekać między pakietami, a atrybut „PacketSize” informuje klienta, jak duży powinien być ładunek pakietu. Za pomocą tej kombinacji atrybutów mówimy klientowi, aby wysłał pojedynczy pakiet 1024-bajtowy.

Podobnie jak w przypadku serwera echa, ustawiamy atrybuty klienta echa Start и Stop, ale tutaj uruchamiamy klienta sekundę po włączeniu serwera (dwie sekundy po rozpoczęciu symulacji).

4.2.8 Symulator

W tym momencie musimy przeprowadzić symulację. Odbywa się to za pomocą funkcji globalnej Symulator::Uruchom.

Simulator::Run ();

Kiedy wcześniej wywołaliś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 po 1,0 sekundy, 2,0 sekundy i dwa zdarzenia po 10,0 sekundach. Po rozmowie Symulator::Uruchom, system zacznie przeglądać listę zaplanowanych zdarzeń i je realizować. Najpierw uruchomi zdarzenie po 1,0 sekundzie, co uruchomi aplikację serwera echa (to zdarzenie może z kolei zaplanować wiele innych zdarzeń). Następnie uruchomi zdarzenie zaplanowane na t=2,0 sekundy, które uruchomi aplikację kliencką echo. Ponownie, w ramach tego wydarzenia może być zaplanowanych znacznie więcej wydarzeń. Implementacja zdarzenia startowego w kliencie echo rozpocznie fazę przesyłania danych symulacji poprzez wysłanie pakietu do serwera.

Wysłanie pakietu na serwer uruchomi łańcuch zdarzeń, które zostaną automatycznie zaplanowane za kulisami i które zaimplementują mechanikę wysyłania pakietu echa zgodnie z parametrami czasowymi, które ustawiliśmy w skrypcie.

W rezultacie, ponieważ wysyłamy tylko jeden pakiet (pamiętaj o atrybucie MaxPakiety została ustawiona na jeden), łańcuch zdarzeń zainicjowany przez ten pojedynczy sygnał ping klienta zakończy się, a symulacja przejdzie w tryb gotowości. Gdy to nastąpi, pozostałe zaplanowane wydarzenia staną się wydarzeniami Stop dla serwera i klienta. Po wykonaniu tych zdarzeń nie pozostaną żadne zdarzenia do dalszego przetwarzania i Symulator::Uruchom przywróci kontrolę. Symulacja została ukończona.

Pozostaje posprzątać po sobie. Odbywa się to poprzez wywołanie funkcji globalnej Symulator::Zniszcz. Ponieważ wywołano funkcje pomocnicze (lub niskopoziomowy kod ns-3), które są zorganizowane w taki sposób, że do symulatora wstawiono haki, aby zniszczyć wszystkie utworzone obiekty. Nie trzeba było samemu śledzić żadnego z tych obiektów – wystarczyło zadzwonić Symulator::Zniszcz i wyjdź. System ns-3 wykona tę ciężką pracę za Ciebie. Pozostałe linie naszego pierwszego skryptu ns-3, First.cc, właśnie to robią:

Simulator::Destroy ();
return 0;
}

Kiedy symulator się zatrzyma?

ns-3 to symulator zdarzeń dyskretnych (DE). W takim symulatorze każde zdarzenie jest powiązane z czasem jego wykonania, a symulacja jest kontynuowana poprzez przetwarzanie zdarzeń w kolejności ich występowania w miarę postępu symulacji. Zdarzenia mogą powodować planowanie przyszłych zdarzeń (na przykład licznik czasu może przełożyć się na inny termin, aby zakończyć odliczanie w następnym interwale).

Początkowe zdarzenia są zwykle inicjowane przez podmiot, na przykład IPv6 zaplanuje wykrycie usług w sieci, żądania sąsiadów itp. Aplikacja planuje zdarzenie wysłania pierwszego pakietu i tak dalej. Gdy zdarzenie jest przetwarzane, może wygenerować zero, jedno lub więcej zdarzeń. W miarę postępu symulacji mają miejsce zdarzenia, które albo kończą się, albo tworzą nowe. Symulacja zatrzyma się automatycznie, jeśli kolejka zdarzeń będzie pusta lub zostanie wykryte specjalne zdarzenie Stop. Wydarzenie Stop generowane przez funkcję Symulator::Stop (zatrzymanie czasu).

Typowym przypadkiem jest sytuacja, w której Simulator::Stop jest absolutnie konieczny do zatrzymania symulacji: gdy występują zdarzenia samopodtrzymujące się. Wydarzenia samopodtrzymujące się (lub powtarzające się) to wydarzenia, które zawsze są przekładane na inny termin. W rezultacie zawsze sprawiają, że kolejka zdarzeń nie jest pusta. Istnieje wiele protokołów i modułów zawierających powtarzające się zdarzenia, na przykład:

• FlowMonitor – okresowe sprawdzanie utraconych pakietów;

• RIPng – okresowa emisja aktualizacji tablicy routingu;

• itp.

W takich sprawach Symulator::Stop konieczne do prawidłowego zatrzymania symulacji. Dodatkowo, gdy ns-3 jest w trybie emulacji, RealtimeSimulator służy do synchronizacji zegara symulacyjnego z zegarem maszyny i Symulator::Stop konieczne, aby zatrzymać proces.

Wiele programów symulacyjnych zawartych w podręczniku nie wywołuje Symulator::Stop jawnie, ponieważ kończą się automatycznie po wyczerpaniu się zdarzeń w kolejce. Jednak te programy akceptują również wywołanie Simulator::Stop. Na przykład następująca dodatkowa instrukcja w pierwszym przykładowym programie zaplanuje wyraźne zatrzymanie na 11 sekundach:

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

Powyższe w rzeczywistości nie zmieni zachowania tego programu, ponieważ ta konkretna symulacja naturalnie kończy się po 10 sekundach. Ale jeśli zmienisz czas zatrzymania w powyższym stwierdzeniu z 11 sekund na 1 sekundę, zauważysz, że symulacja zatrzymuje się, zanim jakiekolwiek dane wyjściowe trafią na ekran (ponieważ dane wyjściowe pojawiają się po około 2 sekundach 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 w celu wykonania zatrzymania!

4.2.9 Budowanie skryptu

Sprawiliśmy, że tworzenie prostych skryptów stało się banalne. Wszystko, co musisz zrobić, to umieścić swój skrypt w katalogu Scratch, a zostanie on automatycznie zbudowany, jeśli uruchomisz Waf. Spróbujmy. Wróć do katalogu najwyższego poziomu i skopiuj przykłady/tutorial/first.cc do katalogu zadraśnięcie

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

Teraz zbuduj swój pierwszy przykładowy skrypt, używając WAF:

$ ./waf

Powinieneś zobaczyć komunikaty wskazujące, że Twój pierwszy przykład został pomyślnie utworzony.

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)

Teraz możesz uruchomić przykład (pamiętaj, że jeśli budujesz swój program w katalogu podstawowym, musisz go uruchomić z zadraśnięcie):

$ ./waf --run scratch/myfirst

Powinieneś zobaczyć podobne dane wyjściowe:

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

Tutaj widać, że system kompilacji sprawdza, czy plik został zbudowany, a następnie go uruchamia. Widzisz, że wpis komponentu na kliencie echo wskazuje, że wysłał on pojedynczy pakiet 1024-bajtowy do serwera echo 10.1.1.2. Ty także widzisz komponent rejestrujący na serwerze echa, który informuje, że otrzymał 1024 bajty z wersji 10.1.1.1. Serwer echo po cichu odtwarza pakiet, a w dzienniku klienta echo można zobaczyć, że otrzymał on pakiet z powrotem z serwera.

4.3 ns-3 Kod źródłowy

Teraz, gdy użyłeś już niektórych pomocników ns-3, możesz rzucić okiem na część kodu źródłowego, który implementuje tę funkcjonalność. Najnowszy kod można wyświetlić na naszym serwerze internetowym pod następującym linkiem: https://gitlab.com/nsnam/ns-3-dev.git. Zobaczysz tam stronę podsumowującą Mercurial dla naszego drzewa rozwoju ns-3. Na górze strony zobaczysz kilka linków,

summary | shortlog | changelog | graph | tags | files

Śmiało i wybierz łącze do plików. Tak będzie wyglądać najwyższy 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ładowe skrypty znajdują się w katalogu przykłady. Jeśli klikniesz na przykłady, wyświetli się lista podkatalogów. Jeden z plików w podkatalogu tutorial - pierwszy.cc. Jeśli klikniesz pierwszy.cc zobaczysz kod, którego właśnie się nauczyłeś.

Kod źródłowy znajduje się głównie w katalogu src. Możesz wyświetlić kod źródłowy, klikając nazwę katalogu lub klikając łącze do plików po prawej stronie nazwy katalogu. Jeśli klikniesz katalog src, wyświetli się lista podkatalogów src. Jeśli następnie klikniesz podkatalog główny, znajdziesz listę plików. Pierwszym plikiem, który zobaczysz (w momencie pisania tego przewodnika), jest przerwać.h. Jeśli klikniesz na link przerwać.h, zostaniesz wysłany do pliku źródłowego przerwać.h, który zawiera przydatne makra umożliwiające wychodzenie ze skryptów w przypadku wykrycia nietypowych warunków. Kod źródłowy pomocników, których używaliśmy w tym rozdziale, można znaleźć w katalogu src/Aplikacje/pomocnik. Możesz swobodnie przeglądać drzewo katalogów, aby dowiedzieć się, co gdzie jest i zrozumieć styl programów ns-3.

Źródło: www.habr.com

Dodaj komentarz