главы 1,2
глава 3 4 Обзор концепции
4.1 Ключевые абстракции
4.1.1 Node (Узел)
4.1.2 Application (Приложение)
4.1.3 Channel (Канал)
4.1.4 Net Device (Сетевое устройство)
4.1.5 Топологические помощники
4.2 Первый скрипт ns-3
4.2.1 Boilerplate код
4.2.2 Подключаемые модули
4.2.3 Пространство имен ns3
4.2.4 Журналирование
4.2.5 Главная функция
4.2.6 Использование топологических помощников
4.2.7 Использование Application
4.2.8 Симулятор
4.2.9 Сборка вашего сценария
4.3 ns-3 Исходный код Первое, что нам нужно сделать перед тем, как начать изучать или писать код ns‑3 — это объяснить несколько основных понятий и абстракций в системе. Многое из этого, для некоторых, может показаться очевидным, но мы рекомендуем уделить время для чтения этого раздела, чтобы убедиться, что вы начинаете на прочной основе. В этом разделе мы рассмотрим некоторые термины, которые обычно используются в сети, но имеют определенное значение в ns‑3. На интернет-жаргоне компьютерное устройство, которое подключается к сети, называется хостом или иногда конечной системой. По той причине, что ns‑3 — это симулятор сети, а не симулятор Интернет, мы намеренно не используем термин хост, так как это тесно связано с Интернетом и его протоколами. Вместо этого мы используем более общий термин, также используемый другими симуляторами, который берет начало в теории графов — узел (node). В ns-3 базовая абстракция вычислительного устройства называется узлом. Эта абстракция представлена в C++ классом Node. Класс NodeNode (узел) даёт методы для управления представлениями вычислительных устройств в симуляциях. Вы должны понимать Node как компьютер, к которому вы добавите функциональность. Вы добавите такие вещи, как приложения, стеки протоколов и периферийные карты с драйверами, позволяющие компьютеру выполнять полезную работу. Мы используем такую же базовую модель в ns-3. Как правило, компьютерное программное обеспечение делится на два широких класса. Системное ПО организует различные компьютерные ресурсы такие как память, циклы процессора, диск, сеть и т. д. в соответствии с некоторой вычислительной моделью. Системное программное обеспечение обычно не использует эти ресурсы для выполнения задач, которые приносят непосредственную пользу пользователю. Пользователь для достижения определенной цели обычно запускает приложение, которое получает и использует ресурсы, контролируемые системным программным обеспечением. Часто линия разделения между системным и прикладным программным обеспечением проводится при изменении уровня привилегий, которое происходит в ловушках операционной системы. В ns‑3 нет реальной концепции операционной системы и соответственно нет понятий уровней привилегий или системных вызовов. У нас, однако, есть идея приложения. Так же как в «реальном мире» для выполнения задач программные приложения работают на компьютерах, приложения ns‑3 работают на узлах ns‑3 для управления симуляциями в симулированном мире. В ns‑3 базовой абстракцией для пользовательской программы, которая генерирует некоторую активность для моделирования, является приложение. Эта абстракция представлена в C++ классом Application (приложение). Класс Application предоставляет методы для управления в симуляциях представлениями нашей версии приложений уровня пользователя. От разработчиков ожидается, что для создания новых приложений они будут специализировать класс Application в смысле объектно-ориентированного программирования. В этом руководстве мы будем использовать специализации класса Application, называемые UdpEchoClientApplication и UdpEchoServerApplication. Как и следовало ожидать, эти приложения составляют набор приложений клиент/сервер используемых для генерации и эхо‑симуляции сетевых пакетов. В реальном мире можно подключить компьютер к сети. Часто среды, по которым передаются данные в этих сетях, называются каналами. Когда вы подключаете кабель Ethernet к розетке на стене, вы подключаете компьютер к каналу связи Ethernet. В смоделированном мире ns‑3 узел подключается к объекту, представляющему канал связи. Здесь основная абстракция коммуникационной подсети называется каналом и представляется в C++ классом Channel (канал). Класс ChannelChannel предоставляет методы для управления взаимодействием объектов подсети и подключения к ним узлов. Каналы также могут быть специализированы разработчиками в смысле объектно-ориентированного программирования. Специализация канала может моделировать что-то простое как провод. Специализированный канал также может моделировать такие сложные вещи как большой Ethernet-коммутатор или трехмерное пространство, полное препятствий в случае беспроводных сетей. Мы будем использовать в этом руководстве специализированные версии канала под названием CsmaChannelCsmaChannel, PointToPointChannelPointToPointChannel и WifiChannelWifiChannel. CsmaChannel, например, моделирует версию коммуникационной подсети которая реализует коммуникационную среду множественного доступа с контролем несущей. Это дает нам Ethernet-подобную функциональность. Раньше было так, что если вы хотите подключить компьютер к сети, вам нужно было купить определенный сетевой кабель и аппаратное устройство, называемое (в терминологии ПК) периферийной платой, которую необходимо установить в компьютер. Если на периферийной плате реализованы некоторые сетевые функции, их называли сетевыми интерфейсными платами или сетевыми картами. Сегодня большинство компьютеров поставляются с интегрированным оборудованием сетевого интерфейса, и пользователи не видят их как отдельные устройства. Сетевая карта не будет работать без программного драйвера, управляющего её оборудованием. В Unix (или Linux), часть периферийного оборудования классифицируется как device. Устройства управляются с помощью драйверов устройств (device drivers), а сетевые устройства (NIC) управляются с использованием драйверов сетевых устройств (network device drivers) и имеют собирательное название сетевые устройства (net devices). В Unix и Linux вы обращаетесь к сетевым устройствам по таким именам, как например eth0. В ns‑3 абстракция сетевого устройства охватывает как программный драйвер, так и моделируемое оборудование. При симуляции сетевое устройство «установлено» в узле, чтобы позволить ему связываться с другими узлами через каналы. Как и в реальном компьютере, узел может быть подключен к нескольким каналам через несколько устройств NetDevices. Сетевая абстракция устройства представлена в C++ классом NetDevice. Класс NetDevice обеспечивает методы управления соединениями с объектами Node и Channel; и могут быть специализированы разработчиками в смысле объектно-ориентированного программирования. В этом руководстве мы будем использовать несколько специализированных версий NetDevice под названиями CsmaNetDevice, PointToPointNetDevice и WifiNetDevice. Так же, как сетевой адаптер Ethernet предназначен для работы с сетью Ethernet, CsmaNetDevice предназначен для работы с CsmaChannel, PointToPointNetDevice предназначен для работы с PointToPointChannel, а WifiNetDevice — предназначен для работы с WifiChannel. В реальной сети вы найдете хост-компьютеры с добавленными (или встроенными) сетевыми картами. В ns‑3 мы бы сказали, что вы будете видеть узлы с подключенными NetDevices. В большой моделируемой сети вам нужно будет организовать соединения между множеством объектов Node, NetDevice и Channel. Поскольку подключение NetDevices к узлам, NetDevices к каналам, назначение IP-адресов и т.д. в ns‑3 являются общей задачей, то чтобы делать это как можно проще, мы предоставляем так называемых топологических помощников. Например, для создания NetDevice необходимо выполнить множество операций ядра ns‑3, добавить MAC-адрес, установить это сетевое устройство в Node, настроить стек протоколов узла, а затем подключить NetDevice к Channel. Еще больше операций будет необходимо, чтобы подключить несколько устройств к многоточечным каналам, а затем соединить отдельные сети в объединенную сеть (Internetworks). Мы предоставляем вспомогательные объекты топологии, которые для вашего удобства объединяют эти многочисленные операции в простую в использовании модель. Если вы установили систему, как было предложено выше, у вас будет релиз ns‑3 в директории с именем repos в вашей домашней директории. Перейдите в директорию release  там вы должны увидеть структуру директории похожую на следующую: Перейдите в директорию examples/tutorial. Вы должны увидеть расположенный там файл с именем first.cc. Это скрипт, который создаст простое соединение точка-точка между двумя узлами и передаст один пакет между узлами. Давайте посмотрим на этот скрипт построчно, для этого откроем first.cc в вашем любимом редакторе. 4.2.1 Boilerplate код
Первая строка в файле — это строка режима редактора emacs. Она говорит emacs об условностях форматирования (стиль кодирования), которые мы использовать в нашем исходном коде. Это всегда довольно спорный вопрос, поэтому мы должны внести ясность, что бы сразу же убрать его с дороги. Проект ns‑3, как и большинство крупных проектов, принял стиль кодирования, которому должен соответствовать весь предоставленный код. Если вы хотите внести свой код в проект, вам в конечном итоге придется соответствовать стандарту кодирования ns‑3, как описано в файле doc/codingstd.txt или показанный на веб-странице проекта: https://www.nsnam.org/develop/contributing-code/coding-style/. Мы рекомендуем вам привыкнуть к внешнему виду кода ns‑3 и применять этот стандарт всякий раз, когда вы работаете с нашим кодом. Вся команда разработчиков и контрибуторы согласились с этим после некоторого ворчания. Строка режима emacs, приведенная выше, упрощает правильное форматирование, если вы используете редактор emacs. Симулятор ns‑3 лицензируется с использованием GNU General Public License. Вы увидите соответствующий юридический заголовок GNU в каждом файле дистрибутива ns‑3. Часто вы можете увидеть уведомление об авторских правах для одного из участвующих учреждений в проекте ns‑3 выше текста GPL и автора, показанное ниже. Собственно код начинается с ряда операторов включения (include). Чтобы помочь нашим пользователям сценариев высокого уровня справиться с большим количеством заголовочных файлов, присутствующих в системе, мы группируем их в соответствии с их использованием в большие модули. Мы предоставляем один заголовочный файл, который будет рекурсивно загружать все заголовочные файлы, используемые в данном модуле. Вместо того, чтобы искать, какой именно заголовок вам нужен, и, возможно получить правильный список зависимостей, мы даем вам возможность загружать группу файлов с большой степенью детализации. Это не самый эффективный подход, но он, безусловно, делает написание сценариев намного проще. Каждый из включаемых файлов ns‑3 помещается в директорию с именем ns3 (поддиректория сборки), чтобы во время процесса сборки избежать конфликтов имен файлов. Файл ns3/core-module.h соответствует модулю ns‑3, который вы найдите в директории src/core в установленном вами релизе. В листинге этой директории вы найдете большое количество заголовочных файлов. Когда вы делаете сборку, Waf помещает общедоступные заголовочные файлы в директорию ns3 в поддиректорию build/debug или build/optimized, в зависимости от вашей конфигурации. Waf будет также автоматически генерировать включаемый файл модуля для загрузки всех общедоступных заголовочных файлов. Поскольку вы, конечно, неукоснительно следуете этому руководству, вы уже сделали чтобы настроить проект на выполнение отладочных сборок, включающих примеры и тесты. Вы также сделали чтобы собрать проект. Так что теперь, когда вы посмотрите в директорию ../../build/debug/ns3, то там, среди прочих, вы найдете заголовочные файлы четырех модулей, показанные выше. Вы можете взглянуть на содержимое этих файлов и обнаружить, что они включают в себя все публичные файлы используемые соответствующими модулями. Следующая строка в скрипте first.cc — это объявление пространства имен. Проект ns‑3 реализован в пространстве имен C++, которое называется ns3. Это группирует все ns‑3-связанные объявления в области видимости за пределами глобального пространства имен, которое, как мы надеемся, поможет в интеграции с другим кодом. Использование оператора C++ вводит пространство имен ns‑3 в текущий (глобальный) декларативный регион. Это причудливый способ сказать, что после этого объявления, вам не нужно будет вводить оператор разрешения ns3 :: scope перед всем кодом ns‑3, чтобы использовать его. Если вы не знакомы с пространствами имен, обратитесь к практически любому учебнику по C++ и сравните пространство имен ns3 с использованием пространства имен std и объявления using namespace std; в примерах работы с оператором вывода cout и потоками. Следующая строка скрипта такая, Мы будем использовать это утверждение как удобное место для обсуждения нашей системы документирования Doxygen. Если вы посмотрите на веб-сайт проекта ns‑3, вы найдете ссылку «Документация» (Documentation) на панели навигации. Если вы выберете эту ссылку, то окажетесь на нашей странице документации. Существует ссылка на «Последний релиз», которая приведет вас к документации для последней стабильной версии ns‑3. Если вы выберете ссылку «API Documentation», вы попадете на страницу документации API ns‑3. С левой стороны страницы вы найдете графическое представление структуры документации. Хорошее место для начала — это «книга» Modules ns‑3 в дереве навигации ns‑3. Если вы раскроете Modules, вы увидите список документации модулей ns‑3. Как обсуждалось выше, концепция модуля здесь напрямую связана с включенными в модуль файлами выше. Подсистема журналирования (ведения журнала, логирования) ns‑3 обсуждается в разделе Использование модуля журналирования, поэтому мы вернемся к ней позже в этом руководстве, но Вы можете узнать о приведенном выше утверждении, посмотрев на модуль Core, а затем открыв книгу Debugging tools, а затем выбрав страницу Logging. Кликните по Logging. Теперь вы должны просмотреть документацию Doxygen для модуля Logging. В списке макросов в верхней части страницы вы увидите запись для NS_LOG_COMPONENT_DEFINE. Перед тем как перейти по ссылке, обязательно посмотрите «Подробное описание» модуля регистрации, чтобы понять его работу в целом. Чтобы сделать это вы можете прокрутить вниз или выбрать «More...» под диаграммой. Как только у вас будет общее представление о том, что происходит, продолжайте и посмотрите документацию на конкретные NS_LOG_COMPONENT_DEFINE. Я не буду дублировать документацию здесь, но подводя итог, скажу, что эта строка объявляет компонент регистрации под названием FirstScriptExample, который позволяет включать и отключать консоль регистрация сообщений по ссылке на имя. В следующих строках скрипта вы увидите, Это просто объявление основной функции вашей программы (скрипта). Как и в любой программе на C++, вам нужно определить главную функцию, она выполняется первой. Здесь нет ничего особенного. Ваш скрипт ns‑3 просто C++ программа. Следующая строка устанавливает разрешение по времени равное 1 наносекунде, что является значением по умолчанию: Разрешение по времени или просто разрешение — это наименьшее значение времени, которое может быть использовано (наименьшая представимая разница между двумя значениями времени). Вы можете изменить разрешение ровно один раз. Механизм, обеспечивающий эту гибкость, потребляет память, поэтому, как только разрешение будет установлено явно, мы освобождаем память, предотвращая дальнейшие обновления. (Если вы не установите разрешение явно, то по умолчанию оно будет равно одной наносекунде, и память будет освобождена при начале симуляции.) Следующие две строки сценария используются для включения двух компонентов ведения журнала, которые встроены в приложения EchoClient и EchoServer: Если вы прочитали документацию по компоненту Logging, вы увидите, что существует несколько уровней подробности регистрации/детализации, которые вы можете включить на каждом компоненте. Эти две строки кода включают ведение журнала отладки на уровень INFO для эхо-клиентов и серверов. На этом уровне приложение во время моделирования будет распечатывать сообщения при отправке и получении пакетов. Теперь мы перейдем непосредственно к делу создания топологии и запуска симуляции. Мы задействуем объекты топологических помощников, чтобы сделать эту работу как можно проще. Следующие две строки кода в нашем скрипте фактически создадут объекты Node ns‑3, которые будут представлять компьютеры в симуляция. Прежде чем мы продолжим, давайте найдем документацию для класса NodeContainer. Еще один способ попасть в документацию для данного класса это через вкладку Classes на страницах Doxygen. Если у вас уже открыт Doxygen, просто прокрутите вверх до верхней части страницы и выберите вкладку Classes. Вы должны увидеть новый набор вкладок, один из которых список классов. Под этой вкладкой вы увидите список всех классов ns‑3. Прокрутите вниз, до ns3 :: NodeContainer. Когда вы найдете класс, выберите его, чтобы перейти к документации для класса. Как мы помним, одной из наших ключевых абстракций является узел. Он представляет компьютер, к которому мы собираемся добавить такие вещи, как стеки протоколов, приложения и периферийные карты. Топологический помощник NodeContainer обеспечивает удобный способ создания, управления и доступа к любым объектам Node, которые мы создаем для запуска симуляции. Первый строка выше просто объявляет NodeContainer, который мы называем nodes. Вторая строка вызывает метод Create для объекта nodes и просит контейнер создать два узла. Как описано в Doxygen, контейнер запрашивает в системе ns‑3 создание двух объектов Node и сохраняет указатели на эти объекты у себя внутри. Созданные в скрипте узлы, пока ничего не делают. Следующим шагом в построении топологии является подключение наших узлов к сети. Самая простая форма сети, которую мы поддерживаем, — это двухточечная связь между двумя узлами. Мы сейчас создадим такое соединение. Мы создаем двухточечное соединение, действуя по знакомому нам шаблону, используем топологический вспомогательный объект для выполнения низкоуровневой работы, необходимой для соединения. Напомним, что две наши ключевые абстракции NetDevice и Channel. В реальном мире эти термины примерно соответствуют периферийным картам и сетевым кабелям. Как правило, эти две вещи тесно связаны друг с другом, и никто не может рассчитывать на обмен, например, устройства Ethernet по беспроводному каналу. Наши топологические помощники следуют этой тесной связи и поэтому вы будете в этом сценарии использовать один объект PointToPointHelper для настройки и подключения ns‑3 объектов PointToPointNetDevice и PointToPointChannel. Следующие три строки в сценарии: Первая строка, создает в стеке экземпляр объекта PointToPointHelper. С точки зрения верхнего уровня следующая строка, говорит объекту PointToPointHelper использовать значение «5 Мбит/с» (пять мегабит в секунду) в качестве «DataRate». С более конкретной точки зрения, строка «DataRate» соответствует тому, что мы называем атрибутом PointToPointNetDevice. Если вы посмотрите на Doxygen для класса ns3::PointToPointNetDevice и в документации к методу GetTypeId вы найдете список атрибутов, определенных для устройства. Среди них будет атрибут «DataRate». Большинство видимых пользователем объектов ns‑3 имеют похожие списки атрибутов. Мы используем этот механизм для простой настройки симуляции без перекомпиляции, как вы увидите в следующем разделе. Подобно «DataRate» в PointToPointNetDevice, вы найдете атрибут «Delay», связанный с PointToPointChannel. Финальная строка, говорит PointToPointHelper использовать значение «2 мс» (две миллисекунды) в качестве значения задержки распространения по каналу точка-точка, который он впоследствии создает. На данный момент у нас в сценарии есть NodeContainer, который содержит два узла. У нас есть PointToPointHelper, который подготовлен для создания объектов PointToPointNetDevices и соединения их с помощью объекта PointToPointChannel. Так же, как мы использовали для создания узлов вспомогательный объект топологии NodeContainer, мы попросим PointToPointHelper выполнить для нас работу, связанную с созданием, настройкой и установкой наших устройств. Нам потребуется список всех созданных объектов NetDevice, поэтому мы используем NetDeviceContainer для их хранения так же, как мы использовали NodeContainer для хранения созданных нами узлов. Следующие две строки кода, завершают настройку устройств и канала. Первая строка декларирует контейнер устройства, упомянутый выше, а вторая выполняет основную работу. Метод Install объекта PointToPointHelper принимает NodeContainer как параметр. Внутри NetDeviceContainer для каждого узла находящегося в NodeContainer создается (для связи точка-точка их должно быть ровно два) PointToPointNetDevice создается и сохраняется в контейнере устройства. PointToPointChannel создается, и к нему присоединяются два PointToPointNetDevices. После создания объектов, атрибуты хранившиеся в PointToPointHelper, используются для инициализации соответствующих атрибутов в созданных объектах. После выполнения вызова pointToPoint.Install (nodes) у нас будет два узла, каждый с установленным сетевым устройством «точка-точка» и одним каналом «точка-точка» между ними. Оба устройства будут настроены на передачу данных со скоростью пять мегабит в секунду с задержкой передачи по каналу в две миллисекунды. Теперь у нас настроены узлы и устройства, но на наших узлах не установлены стеки протоколов. Следующие две строки кода позаботятся об этом. InternetStackHelper — представляет собой топологический помощник для интернет-стеков, подобно PointToPointHelper для двухточечных сетевые устройств. Метод Install принимает NodeContainer в качестве параметра. При выполнении он установит Интернет-стек (TCP, UDP, IP и т. д.) на каждом узле контейнера. Затем нам нужно к наши устройства связать с IP-адресами. Мы предоставляем топологического помощника для управления распределением IP-адресов. Единственный видимый пользователю API — это установка базового IP-адреса и маски сети для использования при выполнении фактического распределения адресов (это делается на более низком уровне внутри помощника). Следующие две строки кода в нашем примере скрипта first.cc, декларируют вспомогательный объект адреса и говорят ему, что он должен начать выделять IP-адреса из сети 10.1.1.0, используя для определения битовую маску 255.255.255.0. По умолчанию выделенные адреса будут начинаться с единицы и увеличиваться монотонно, поэтому первый адрес, выделенный из этой базы, будет 10.1.1.1, затем 10.1.1.2 и т.д. В реальности, на низком уровне система ns‑3 запоминает все выделенные IP-адреса и генерирует фатальную ошибку, если вы случайно создали ситуацию когда один и тот же адрес будет сгенерирован дважды (кстати, эту ошибку трудно отладить). Следующая строка кода, выполняет фактическое назначение адреса. В ns‑3 мы устанавливаем связь между IP-адресом и устройством, используя объект Ipv4Interface. Так же, как нам иногда нужен список сетевых устройств, созданных помощником для дальнейшего использования, нам иногда нужен список объектов Ipv4Interface. Ipv4InterfaceContainer предоставляет эту функциональность. Мы построили сеть точка-точка, с установленными стеками и назначенными IP-адресами. Теперь нам нужны в каждом узле приложения для генерации трафика. Еще одна из основных абстракций системы ns‑3 — это Application (приложение). В этом сценарии мы используем две специализации базового класса Application ns‑3 под названием UdpEchoServerApplication и UdpEchoClientApplication. Как и в предыдущих случаях, мы используем вспомогательные объекты для настройки и управления базовыми объектами. Здесь мы используем UdpEchoServerHelper и UdpEchoClientHelper объекты, чтобы сделать нашу жизнь проще. Следующие строки кода в нашем примере скрипта first.cc, используются для настройки приложения UDP эхо сервера на одном из узлов, которые мы создали ранее. Первая строка кода в приведенном выше фрагменте создаёт UdpEchoServerHelper. Как обычно, это не приложение само по себе, это объект, который помогает нам создавать реальные приложения. Одно из наших соглашений — передавать необходимые атрибуты в конструктор вспомогательного объекта (помощника). В этом случае помощник не может сделать ничего полезного, если ему не предоставлен номер порта на котором сервер будет ожидать пакеты, этот номер также должен быть известен клиенту. В данном случае мы передаем конструктору помощника номер порта. Конструктор, в свою очередь, просто выполняет SetAttribute с переданным значением. Позже, при желании, с помощью SetAttribute вы сможете установить другое значение атрибута «Порт». Подобно многим другим вспомогательным объектам, объект UdpEchoServerHelper имеет метод Install. Выполнение этого метода, фактически приводит к тому, что создается базовое приложение эхо-сервера и привязывается к узлу. Интересно, что метод Install принимает NodeContainter в качестве параметра так же, как и другие Install методы, которые мы видели. Неявное преобразование C++, работающее здесь, принимает результат метода node.Get (1) (который возвращает умный указатель на объект узла — Ptr ) и использует его в конструкторе для анонимного объекта NodeContainer, который затем передается методу Install. Если вы не можете определить в C++ коде, метод с какой сигнатурой компилируется и выполняется, то ищите среди неявных преобразований.
 Теперь мы видим, что echoServer.Install собирается установить приложение UdpEchoServerApplication на найденный в NodeContainer, который мы используем для управления нашими узлами, узел с индексом 1. Метод Install вернет контейнер, который содержит указатели на все приложения (в данном случае одно, поскольку мы передали анонимный NodeContainer, содержащий один узел) созданный помощником. Приложениям требуется указать момент запуска генерации трафика «start» и может потребоваться дополнительно указать время, когда его остановить «stop». Мы предоставляем оба параметра. Эти времена устанавливаются с помощью методов ApplicationContainer Start и Stop. Эти методы принимают параметры типа Time. В этом случае мы используем явную последовательность преобразований C++, чтобы взять C++ double 1.0 и преобразовать его в объект тns‑3 Time, использующий объект Seconds для перевода в секунды. Помните, что правила преобразования могут контролироваться автором модели, и C++ имеет свои собственные правила, поэтому вы не всегда можете рассчитывать на то, что параметры будут преобразованы так, как вы ожидали. Две строки, приведет к тому, что приложение эхо-сервера запустится (включится автоматически) через одну секунду после начала симуляции и остановится (отключится) через десять секунд симуляции. В силу того, что мы объявили событие моделирования (событие остановки приложения), которое будет выполнено через десять секунд, то будет просимулировано не менее десяти секунд работы сети. Клиентское приложение echo настраивается способом, практически аналогичным серверу. Существует базовый объект UdpEchoClientApplication, которым управляет
UdpEchoClientHelper. Однако для эхо-клиента нам нужно установить пять разных атрибутов. Первые два атрибута устанавливаются во время создания UdpEchoClientHelper. Мы передаем параметры, которые используются (внутри помощника) для установки атрибутов «RemoteAddress» и «RemotePort» в соответствии с нашим соглашением, о передаче необходимых параметров в конструктор помощника. Напомним, что мы использовали Ipv4InterfaceContainer для отслеживания IP-адресов, которые мы присвоили нашим устройствам. Нулевой интерфейс в контейнере интерфейсов будет соответствовать IP-адресу нулевого узла в контейнере узлов. Первый интерфейс в контейнере интерфейсов соответствует IP-адресу первого узла в контейнере узлов. Итак, в первой строке кода (сверху) мы создаем помощника и сообщаем ему, что удаленным адресом клиента будет IP-адрес, назначенный узлу, на котором находится сервер. Мы также говорим, что нужно организовать отправку пакетов на девятый порт. Атрибут «MaxPackets» сообщает клиенту максимальное количество пакетов, которое мы можем отправить во время моделирования. Атрибут «Interval» сообщает клиенту, как долго ждать между пакетами, и атрибут «PacketSize» сообщает клиенту, насколько велика должны быть полезная нагрузка пакета. Этой комбинацией атрибутов мы говорим клиенту отправить один 1024-байтовый пакет. Как и в случае с эхо-сервером, мы устанавливаем эхо-клиенту атрибуты Start и Stop, но здесь мы запускаем клиент через секунду после включения сервера (через две секунды после начала симуляции). На этом этапе нам нужно запустить симуляцию. Это делается с помощью глобальной функции Simulator::Run. Когда мы ранее вызывали методы, мы фактически запланировали события в симуляторе на 1,0 секунды, 2,0 секунды и два события на 10,0 секунды. После вызова Simulator::Run, система начнет просматривать список запланированных событий и выполнять их. Сначала он запустит событие через 1,0 секунды, что активирует приложение эхо-сервера (это событие может, в свою очередь, запланировать много других событий). Затем он запустит событие, запланированное на t = 2,0 секунды, которое запустит приложение эхо-клиента. Опять же, это событие может запланировать еще много событий. Реализация события запуска в эхо-клиенте начнет этап передачи данных моделирования, отправив пакет на сервер. Акт отправки пакета на сервер вызовет цепочку событий, которые будут автоматически запланированы за сценой и которые будут реализовывать механику отправки пакета эхо-сигналов в соответствии с параметрами синхронизации, которые мы установили в сценарии. В итоге, поскольку мы отправляем только один пакет (напомним, атрибут MaxPackets был установлен в единицу), цепочка событий инициированный этим единственным клиентским эхо-запросом закончится, и симуляция перейдет в режим ожидания. Как только это произойдет, оставшимися запланированными событиями будут события Stop для сервера и клиента. Когда эти события выполнятся, событий для дальнейшей обработки не останется и Simulator::Run вернет управление. Моделирование завершено. Осталось только прибрать за собой. Это делается путем вызова глобальной функции Simulator::Destroy. Поскольку вызывались функции помощников (или код низкого уровня ns‑3), которые организованы так, чтобы в симуляторе были вставлены хуки для уничтожения всех объектов, которые были созданы. Вам не требуется отслеживать какие-либо из этих объектов самостоятельно — все, что вам нужно было сделать это вызвать Simulator::Destroy и выйти. Система ns‑3 сделает эту трудную работу за вас. Оставшиеся строки нашего первого ns‑3 скрипта, first.cc, делают именно это: ns‑3 — симулятор дискретных событий (DE). В таком симуляторе каждое событие связано со временем его выполнения, а симуляция продолжается обработкой событий в порядке их возникновения по ходу симуляции. События могут стать причиной планирования будущих событий (например, таймер может перепланировать себя, чтобы закончить счет в следующем интервале). Начальные события обычно инициируются объектом, например, IPv6 будет планировать определение сервисов в сети, запросы соседей и т.д. Приложение планирует первое событие отправки пакета и т.д. Когда событие обрабатывается, оно может генерировать ноль, одно или несколько событий. По мере выполнения симуляции, происходят события, просто заканчиваясь или порождая новые. Симуляция остановится автоматически, если очередь событий окажется пустой или будет обнаружен специальное событие Stop. Событие Stop генерируется функцией Simulator::Stop (остановить время). Существует типичный случай, когда Simulator::Stop абсолютно необходима для остановки симуляции: когда есть самоподдерживающиеся события. Самоподдерживающиеся (или повторяющиеся) события — это события, которые всегда перепланируются. Как следствие, они всегда сохраняют очередь событий не пустой. Существует много протоколов и модулей, содержащих повторяющиеся события, например: • FlowMonitor — периодическая проверка на потерянные пакеты; • RIPng — периодическая трансляция обновления таблиц маршрутизации; • и т.д. В таких случаях Simulator::Stop необходим для корректной остановки симуляции. Кроме того, когда ns‑3 находится в режиме эмуляции, RealtimeSimulator используется, чтобы синхронизировать часы симуляции с часами машины, и Simulator::Stop необходим для остановки процесса. Многие из программ симуляции в учебнике не вызывают Simulator::Stop явно, так как они завершаются автоматически с исчерпанием событий в очереди. Однако эти программы также примут вызов Simulator::Stop. Например, следующий дополнительный оператор в первом примере программы запланирует явную остановку на 11 секунде: Вышеуказанное фактически не изменит поведение этой программы, так как это конкретное моделирование естественным образом заканчивается через 10 секунд. Но если бы вы изменили время остановки в приведенном выше операторе с 11 секунд до 1 секунды, вы заметили бы, что моделирование останавливается до того, как любой вывод попадет на экран (поскольку вывод происходит примерно через 2 секунды времени симуляции). Важно вызвать Simulator::Stop до вызова Simulator::Run; в противном случае Simulator::Run может никогда не вернуть управление основной программе для выполнения остановки! Мы сделали создание ваших простых скриптов тривиальным. Все, что вам нужно сделать, это поместить ваш скрипт в каталог scratch, и он будет автоматически собран, если вы запустите Waf. Давай попробуем. Вернетесь в каталог верхнего уровня и скопируйте examples/tutorial/first.cc в каталог scratch Теперь соберите свой первый пример сценария, используя waf: Вы должны увидеть сообщения о том, что ваш первый пример был успешно создан. Теперь вы можете запустить пример (обратите внимание, что если вы собираете свою программу в директории scratch, то и запускать его вы должны из scratch): Вы должны увидеть похожий вывод: Здесь вы видите, что система сборки проверяет, что файл был собран, а затем запускает его. Вы видите запись компонент на эхо-клиенте указывает, что он отправил один 1024-байтовый пакет на эхо-сервер 10.1.1.2. Вы тоже см. компонент ведения журнала на эхо-сервере, чтобы сказать, что он получил 1024 байта от 10.1.1.1. Эхо-сервер молча повторяет пакет, и вы видите в журнале эхо-клиента, что он получил свой пакет обратно с сервера. Теперь, когда вы использовали некоторые из помощников ns‑3, вы можете взглянуть на некоторые исходные коды, которые реализует эту функциональность. Самый свежий код можно просмотреть на нашем веб-сервере по следующей ссылке: https://gitlab.com/nsnam/ns-3-dev.git. Там вы увидите сводную страницу Mercurial для нашего дерева разработки ns‑3. В верхней части страницы вы увидите несколько ссылок, Идите дальше и выберите ссылку на файлы. Вот как будет выглядеть верхний уровень большинства наших репозиториев: Наши примеры сценариев находятся в директории examples. Если вы нажмете на примеры, вы увидите список поддиректорий. Один из файлов в поддиректории tutorial — first.cc. Если вы нажмете на first.cc вы увидите код, который вы только что изучили. Исходный код находится в основном в директории src. Вы можете просмотреть исходный код, нажав на имя директории или нажав на ссылку файлы справа от имени директории. Если вы кликнете на директория src, вы получите список поддиректорий src. Если вы затем кликнете по поддиректории core, вы найдете список файлов. Первый файл, который вы увидите (на момент написания этого руководства) — abort.h. Если вы нажмете на ссылку abort.h, вы будете отправлены на исходный файл для abort.h, который содержит полезные макросы для выхода из скриптов, если обнаружены ненормальные условия. Исходный код для помощников, которые мы использовали в этой главе, можно найти в директории src/Applications/helper. Не стесняйтесь рыться в дереве директорий, чтобы понять, что где и разобраться в стиле программ ns‑3. Разработчик ПО для радиосвязи