Розробка

LLTR Частина 1: Перші кроки в OMNeT++ і INET

OMNeT++ (Objective Modular Network Testbed in C++) Discrete Event Simulator – це модульна, компонентно‑орієнтована бібліотека C++ і фреймворк для дискретно‑подійного моделювання, що використовується насамперед для створення симуляторів мереж. Просто кажучи це “симулятор дискретних подій”, що включає: IDE для створення моделей, і сам симулятор (GUI).

INET Framework – “бібліотека” мережевих моделей для OMNeT++.

Повна версія GIF (15.7 MiB)

У попередніх частинах…

0. Автоматичне визначення топології мережі і некеровані комутатори. Місія нездійсненна? (+ classic Habrahabr UserCSS)

У цій частині:

  • створимо свій перший” протокол (на прикладі LLTR Basic);
  • виберемо відповідний симулятор сіті для налагодження протоколу (та створення його моделі);
  • пізнаємо тонкощі налаштування оточення для симулятора і його IDE (конфігурування, компіляції, лінкування, тюнінг, патчінг, ігнорування застарілої документації; та інші англіцизми у великій кількості);
  • зіткнемося з усім, з чим можна зіткнутися при створенні своєї першої моделі свого першого протоколу у не своєму незнайомому симуляторі мережі;
  • пройдемо весь шлях разом:
    • від щастя, принесеного успішної (нарешті!) компіляції першого проекту з порожньою мережею,
    • до повного занурення в експерименти з функціонуючою моделлю протоколу;
  • tutorial, все описано у вигляді tutorial – ми будемо вчитися на помилках – будемо здійснювати їх, і будемо розуміти їх (природу), щоб елегантно/ефективно з ними впорається;
  • репозиторій (git ), в коммитах і тегах якого збережені всі кроки (“Add …”, “Fix …”, “Fix …”, “Modify …”, “Correct …”, …), від початку і до кінця.

Note: додаткова інформація для читачів хаба “Mesh-мережі”.

{ обсяг зображень: 2.2+(2.1) MiB; тексту: 484 KiB; смайликів: 22 шт. }

Note: [про використовувану структуру розділів] структура розділів tutorial/how‑to зазвичай відрізняється від структури розділів у довіднику: у довіднику – структура розділів дозволяє за мінімальну кількість кроків дійти до потрібної інформації (збалансоване дерево); в tutorial/how‑to, де розділи сильно пов’язані логічно, а окремий розділ, по суті, є одним з кроків у послідовності кроків, структура являє собою ієрархію закладок (якорів), яка дозволяє в будь-якому місці tutorial/how‑to нагадати (посилання) про фрагменті описаному раніше.

off‑topic: про html5 тег <section> теги заголовків <h#>

Як добре, що в HTML5 з’явився тег <section>, з його допомогою стало можливим безпосередньо задавати рівень вкладеності розділу (за допомогою маніпуляції вкладеністю тегів <section> один в одного). Структуру тексту тепер можна було явно відобразити у вкладеності (ієрархії) тегів.

Це вплинуло і на теги заголовків <h#>, оскільки тепер структуру розділів визначається вкладеністю тега <section>, то для зазначення назви розділу – достатньо було використовувати усього лише один тег <h1> у вигляді: “<section><h1>назва розділу</h1>текст розділу</section>“.

Я цим користувався вже давно (з самого появи <section>), але створюючи цю статтю, побачив ще одна перевага використання <section>.

Гарне назва розділу має точно відображати його суть, однак бувають випадки, коли потрібно притримати (не розкривати) суть до середини розділу. Тобто, такий розділ повинен спочатку прикинеться “рутинним”, а в середині створити “wow/wtf‑ефект”. Логічно це все – один розділ, але якщо розкрити його назву на самому початку розділу, то сама назва буде спойлером. Уявіть книгу (детектив), на обкладинці якої вся інформація про “вбивцю”.

Тут на сцену виходить” тег <section>. Він дозволяє визначити назву розділу в будь-якому місці всередині себе, тобто не обов’язково в самому початку. Приклад: “<section>текст розділу<h1>назва розділу</h1>продовження тексту розділу</section>“. Виходить, ми можемо одночасно зберегти логічну структуру тексту, і показати назву розділу в потрібний момент. Можна навіть зробити так, щоб назва розділу візуально з’являлося на його початку, після того як читач дійде до певного моменту (до тега <h1> в html).

Ось тільки більше ніж за 9 років існування <section>, браузери так і не навчилися правильно будувати “HTML5 document outline” для забезпечення доступності.

Чому не навчилися? У документі зі складною структурою важко* визначити, починаючи з якого тега (section, article, …) слід почати нумерацію заголовків h1, h2, h3, …). А тепер уявіть, що сам документ розміщено на сторінці подібної (з безліччю додаткових блоків, які не мають відношення до самого документу, але мають заголовки), причому скрізь для заголовків використовується h1. А якщо на одній сторінці не один документ, а кілька? Тим не менш, візуально все виглядає добре (приклад документа).

* – насправді це не важко, в стандарті все описано, але в реальності це не працює (пояснення див. нижче).

Чому візуально все виглядає добре? Тут, завдяки стилям, з’явилася додаткова інформація – відповідність між ієрархією section і рівнями заголовків (h#). Так може при побудові “HTML5 document outline” слід скористатися інформацією з CSS? Для цього потрібно додати в CSS додаткове властивість для елементу заголовка, визначає його рівень, наприклад:

body>section>h2 { heading-level: 1; font-size: 1.8 em; }
body>section>section>h2 { heading-level: 2; font-size: 1.4 em; }
body>section>section>section>h2 { heading-level: 3; font-size: 1.17 em; }
body>section>section>section>section>h2 { heading-level: 4; font-size: 1em; }
body>section>section>section>section>section>h2 { heading-level: 5; font-size: 0.83 em; }

Більш суворий варіант – в одній секції допускається використовувати тільки один заголовок. У цьому випадку рівень заголовка задає сама секція:

body>section { heading-level: 1; }
body>section>section { heading-level: 2; }
body>section>section>section { heading-level: 3; }
body>section>section>section>section { heading-level: 4; }
body>section>section>section>section>section { heading-level: 5; }

і неважливо, який у підсумку буде використовуватися тег заголовка: h1 або h5.

Однак, якщо раніше для створення “heading-level outline” достатньо було мати лише розмітки (HTML), то тепер потрібні ще і стилів (CSS). Може можна обмежитися тільки розміткою HTML? Цим питанням ми впритул підійшли до проблеми алгоритму побудови “heading-level outline”, описаного в стандарті. Так от, проблема не в самому алгоритмі, а в тому, що в якості “sectioning root” елемента може виступати тільки обмежений (фіксований) набір тегів. Але у людей часто виникають нестандартні бажання”: “я хочу, щоб на моїй сторінці зі списком статей тег article був ‘sectioning root’ елементом”, “а я хочу, щоб довільна секція стала ‘sectioning root’ елементом”. Раніше їм достатньо було для цього використовувати кілька тегів h1 на одній сторінці (і вони це робили). Так може зробити так, щоб кожна секція (теги: section, article, …) ставала “sectioning root” елементом, якщо заголовок у ній заданий за допомогою тега h1?..

# Перші кроки: “перед моделюванням” / “мозковий штурм”

НЛОприлетелоиоставилоэтотпробелздесь? Зворотна сторона листочка з попередньої статті.

# Деталізація протоколу

На початку визначимо, що нам потрібно включити в протокол. На прикладі LLTR Basic.

Основа LLTR – це ітерації збору статистики на множині хостів під час сканування мережі. Ітерацій в LLTR багато ( >1), тому перше, що потрібно включити в протокол – управління запуском і зупинкою кожній ітерації. Якщо врахувати, що хостів теж багато ( >1), то управління буде полягати в тому, щоб певним способом повідомляти всім хостам час початку ітерації і час закінчення ітерації. Тобто синхронізувати всі хости.

В кожній ітерації є свій unicast src хост і unicast dst хост, тому наступне, що потрібно увімкнути спосіб призначення для кожної ітерації unicast src і dst. Тобто в кожній ітерації один з хостів повинен “усвідомлювати” себе unicast src хостом, мета якого посилати unicast трафік на dst хост.

І останнє. По завершенню всіх ітерацій, всю зібрану статистику з усіх хостів потрібно відправити на один хост для обробки. Цей хост проаналізує зібрану статистику, і побудує топологію мережі.

Також, на цьому кроці, можна подумати про деякі деталі реалізації (обмеження) протоколу. Наприклад, ми хочемо, щоб програма, що використовує LLTR, змогла працювати без root прав, і з простору користувача (тобто без установки в систему спеціального драйвера), значить, LLTR повинен працювати, наприклад, поверх TCP і UDP.

Всі інші робили реалізації, самі визначаться, в процесі створення моделі. Тобто, звичайно, можна відразу ж продумати все до дрібниць, але при цьому є ризик “скотиться в локальний оптимум”, і не помітити “кращий” варіант реалізації. Добре, коли моделей буде кілька – якщо для кожного варіанту реалізації буде своя модель, то з’явиться можливість комбінувати моделі, і крок за кроком йти до кращої реалізації. Згадуючи генетичний алгоритм ;). Наприклад, в одній реалізації/моделі може бути централізоване управління, в іншій – децентралізоване, в третій – комбінація кращих частин з двох попередніх варіантів.

# Вибір симулятора мережі

Тепер настав час визначитися з симулятором мережі, в якому будемо створювати моделі і ставити експерименти.

В основному, від симулятора мережі нам потрібна можливість реалізації свого протоколу. Не всі симулятори дозволяють легко це зробити.

А ось присутність емуляторів ОС реального мережного обладнання світових брендів”, навпаки – не потрібно. Швидше за все, емулятори створять безліч обмежень, які будуть тільки заважати в ході експериментів.

З вибором симулятора мені допомогла стаття Evaluating Network Simulation Tools (наші вимоги до симулятору багато в чому збігалися) і OMNeT++ General ‘Network’ Simulation.

# Установка OMNeT++ і INET

Завантажуємо OMNeT++ 5.0.

І так як OMNeT++ – це всього лише “симулятор дискретних подій”, то знадобиться ще й INET – бібліотека мережевих моделей (протоколи та пристрої). Качаємо INET 3.4.0. Насправді його можна було встановити з IDE, але я рекомендую поставити вручну (пізніше буде ясно чому).

Установка в *nix і в Windows мало чим відрізняється. Продовжу на прикладі Windows.

Розпаковуємо OMNeT++ в %ProgramData% (C:ProgramData), і відкриваємо файл INSTALL.txt (C:ProgramDataomnetpp-5.0INSTALL.txt). У ньому сказано, що докладна інструкція знаходиться в “doc/InstallGuide.pdf”, далі написано, що якщо не хочете її читати, то просто виконайте:

$. setenv
$ ./configure
$ make

Але не поспішайте це робити!

По‑перше, зверніть увагу на першу команду “. setenv“. У директорії “omnetpp-5.0” немає файлу “setenv” (у версії 5.0b1 він був). Він і не потрібен (для Windows), тому просто запускаємо “mingwenv.bat” (раджу перед запуском подивитися, що він робить… щоб уникнути раптового rm ). По закінченні відколеться термінал (mintty).

По‑друге, раджу трохи підправити файл “configure.user” (якщо згаданий параметр закомментирован у файлі, то його потрібно розкоментувати):

  • Якщо хочете використовувати Clang (за замовчуванням), то залиште
    PREFER_CLANG=yes
    і налаштуйте:

    • CFLAGS_RELEASE (опції компілятора):
      CFLAGS_RELEASE='-O2 -march=native -DNDEBUG=1'
  • Якщо хочете використовувати GCC замість Clang (а ви, швидше за все, захочете використовувати саме GCC, побачивши, що написано в 398 рядку файлу “configure.in”), то встановіть
    PREFER_CLANG=no
    і налаштуйте:

    • CFLAGS_RELEASE (опції компілятора). Можна вибрати або
      CFLAGS_RELEASE='-O2 -mfpmath=sse,387 -ffast-math -fpredictive-commoning -ftree-vectorize -march=native -freorder-blocks-and-partition -pipe -DNDEBUG=1'
      або
      CFLAGS_RELEASE='-O2 -fpredictive-commoning -march=native -freorder-blocks-and-partition -pipe -DNDEBUG=1'
      або
      CFLAGS_RELEASE='-O2 -march=native -freorder-blocks-and-partition -pipe -DNDEBUG=1'
      (розташоване в порядку зменшення ймовірності виникнення глюків).
    • Також варто додати CXXFLAGS у вигляді ‘-std=c++11 ‘+CFLAGS_RELEASE. Наприклад:
      CXXFLAGS='-std=c++11 -O2 -fpredictive-commoning -march=native -freorder-blocks-and-partition -pipe -DNDEBUG=1'
    • JAVA_CFLAGS (просто раскомментируем):
      JAVA_CFLAGS=-fno-strict-aliasing
  • PREFER_QTENV=yes
  • Відключаємо 3D візуалізацію:
    WITH_OSG=no
    Вона звичайно гарна, але нам не знадобиться.
  • Паралельне (на безлічі CPU) виконання симуляції (WITH_PARSIM), на жаль, теж варто відключити, проте без нього компонування (linker) завершується невдачею, тому залишимо включеним:
    WITH_PARSIM=yes

Чому його варто відключити?

Якщо його явно не використовувати, то він не потрібен (в теорії). Детальніше в розділі 16.1, 16.3, і 16.3.2 “Parallel Simulation Example” в “doc/InstallGuide.pdf”, або тут.

Тепер в терміналі (mintty) можна виконати:

./configure && make clean MODE=release
make MODE=release –j17

Note: “17” слід замінити на кількість ядер CPU + 1, або на 1.5×ядер.

Застереження для допитливих (збірка 64bit)

В папці tools/win32″ знаходиться MSYS2 його пакети компіляторів можна оновлювати:

  • MSYS2 installer
    msys2.github.io
  • Updating packages & General Package Management
    sourceforge.net/p/msys2/wiki/MSYS2 installation
  • Re-installing
    sourceforge.net/p/msys2/wiki/MSYS2 re-installation
  • Building Packages
    sourceforge.net/p/msys2/wiki/Contributing to MSYS2
  • Downgrading packages
    wiki.archlinux.org/index.php/Downgrading_packages
    &
    packages repo
    sourceforge.net/projects/msys2/files/REPOS/MSYS2/x86_64/

А OMNeT++ можна зібрати під 64bit.

Але OMNeT++ може просто не зібратися на нову версію GCC (так було з першою бэткой п’ятої версії OMNeT++ – без правки исходников вона нормально збиралася тільки з GCC 4.x). А для переходу на 64bit потрібно ще більше зусиль. Для початку потрібно переглянути опції компіляції (fPIC, не потрібен?). Потім, якщо перегорне исходники OMNeT++, то побачите, що там часто використовується тип long замість int32_t, size_t і ptrdiff_t (а також uintptr_t і intptr_t). Чим це загрожує? В *nix в 64bit (LP64) складання розмір long буде 64bit, а в Windows (LLP64) – 32bit (див. моделі даних). Доведеться замінювати long на size_t і ptrdiff_t, але і тут вас будуть чекати “підводні камені”. Наприклад, можна відкрити “src/utils/opp_lcg32_seedtool.cc”, і подивитись на рядок 231 – index або можна залишити 32bit (замінити на int32_t), або зробити 64bit і модифікувати всі битовые_маски+опису+(можливо)немного_логики. Тому частина long змінних потрібно буде залишити 32bit, а іншу частину зробити 64bit. Загалом, для коректної роботи, потрібно виконати всі пункти з:

  • 7 кроків щодо перенесення програми на 64-бітну систему (viva64)
  • Колекція прикладів 64-бітових помилок у реальних програмах (habr:pvs-studio)
  • 20 пасток перенесення Сі++ – коду на 64-бітну платформу (viva64)

Причому те ж саме треба зробити і з численними бібліотеками для OMNeT++, наприклад, з INET.

Загалом, застерігаю від спроб зробити 64bit збірку OMNeT++.

Під *nix я також рекомендую використовувати 32bit збірку (принаймні з версією 5.0 і менше).

Можливо, коли‑небудь Andrey2008 візьметься перевірити код OMNeT++ і INET… А поки пропоную просто знайти і переглянути все “FIXME“/”Fix” у коді ;).

P. S. згадки про те, що код OMNeT++ перевіряли статичним аналізатором коду – відсутні, а ось у файлах “ChangeLog” INET 3.4.0 можна знайти 70 згадок про усунення дефектів після сканування в Coverity.

OMNeT++ використовує Eclipse в якості IDE. Для зручності можна створити ярлик на IDE “%ProgramData%omnetpp-5.0ideomnetpp.exe” і розташувати його в легкодоступному місці. У директорії “ide/jre/” знаходиться JRE v1.8.0_66-b18. Якщо в системі вже встановлено сумісний JRE/JDK, то директорію “ide/jre/” можна спокійно видалити, замінивши символьним посиланням на розташування системного JRE.

При першому запуску Eclipse пропонує помістити workspace в директорію “samples”, проте краще розташувати її у будь-якій іншій зручній вам директорії поза “%ProgramData%”. Головне, щоб в дорозі до нової директорії використовувалися тільки латинські букви (+ символи), і не було прогалин.

Після закриття Welcome, IDE запропонує встановити INET (як було написано вище), і імпортувати приклади – відмовтеся від обох пунктів.

Налаштування Eclipse, опції JVM, додаткові плагіни і теми

Опції JVM. Додати в файл “ide/omnetpp.ini” (для правки підійде будь-який редактор, розуміє LF переклад рядка; notepad не підійде), зберігши порожню останній рядок:

-XX:+UseParNewGC
-XX:+UseConcMarkSweepGC
-XX:+AggressiveOpts
-XX:+TieredCompilation
-XX:CompileThreshold=100

Щоб зробити Eclipse, таким як на картинці – заглянь всередину картинки.

Настав час встановити INET. Директорію “inet” з скачаного раніше архіву (inet-3.4.0-src.tgz) потрібно перенести в workspace. У директорії є файл “INSTALL” з покроковим описом установки. Можна скористатися ним (розділ “If you are using the IDE”), але тільки не збирайте (Build) проект!

Імпортуємо INET:

  1. В Eclipse відкрити: File > Import.
  2. Вибрати: General / Existing Projects to the Workspace.
  3. В якості root directory” вибрати розташування workspace.
  4. Переконайтеся, що опція “Copy projects into workspace” виключена.
  5. Після натискання на кнопку “Finish”, дочекайтеся закінчення індексації проекту (% виконання див. унизу, у рядку статусу – “C/C++ Indexer”).

Налаштуємо проект:

  • A. відключимо непотрібні для LLTR компоненти;
  • B. перемкнемо збірку на реліз;
  • C. позбудемося глюків “OMNeT++ Make Builder” (opp_makemake) – раніше, при його виборі, часто відбувалася перегенерирация Makefile, навіть коли цього не вимагалося;
  • D. включимо паралельну компіляцію;
  • E. включимо оптимізації;
  • F. включимо підсвічування синтаксису для c++11, в кількох місцях;
  • G. підправити баг пов’язаний з “#include” (трапляється, якщо кілька разів міняти “Current builder”; може статися і в інших випадках).

Перед налаштуванням {A} треба підправити один з файлів проекту. У файлі “inet/.oppfeatures” є рядок “inet.examples.visualization” потрібно додати після неї порожню рядок, в якій написати “inet.tutorials.visualization“, бажано зберігши відступ зліва (за аналогією з іншими параметрами “nedPackages” у файлі). Якщо це не зробити, то нічого страшного не станеться, просто після налаштування в “Problems” (Alt+Shift+Q,X) будуть завжди висіти помилки, пов’язані з “inet.tutorials.visualization“. Можна спочатку зробити {A}, та подивитися на помилки, а потім підправити файл “inet/.oppfeatures” – при цьому Eclipse попередить про порушення цілісності в налаштуваннях, і запропонує профиксить їх (погоджуємося на це).

Приступимо (панель “Project Explorer” > проект “inet” > контекстне меню > Properties):

  1. Розділ “OMNeT++” > підрозділ “Project Features”
    1. {A} прибираємо всі, крім:
      • TCP Common
      • TCP (INET)
      • IPv4 protocol
      • UDP protocol
      • Ethernet
    2. кнопка “Apply”.
  2. Розділ “С/С++ Build”:
    1. кнопка “Manage Configurations…” > зробити активним “gcc-release” {B};
    2. вибрати конфігурацію “gcc-release [ Active ]” {B}.
    3. Підрозділ “Tool Chain Editor”:
      1. у якості “Current builder” вибрати “GNU Make Builder” для обох конфігурацій: “gcc-debug” і “gcc-release” {C}, увагу: якщо в майбутньому змінити “Current builder”, то доведеться все переналаштовувати заново!
      2. кнопка “Apply”.
    4. Вкладка “Behavior” (повернуться в корінь розділу “С/С++ Build”):
      1. встановити “Use parallel jobs” рівним N (N можна вибрати або кількість ядер CPU + 1, або 1.5×ядер) – це дозволить використовувати всі ядра CPU для компіляції {D} (налаштовуємо для “gcc-debug” і “gcc-release”).
    5. Вкладка “Build Settings”:
      1. відключити “Use default build command”;
      2. рядок “Build command” замінити на “make MODE=release CONFIGNAME=${ConfigName} -j17” (“17” замінити на попереднє значення в рядку, тобто на вибраний N) {E}, те ж саме можна зробити і для конфігурації “gcc-debug”, замінивши у рядку “MODE=release” на “MODE=debug“, після цього не забудь переключитися назад на “gcc-release [ Active ]”.
    6. кнопка “Apply”.
  3. Розділ “С/С++ General”:
    1. Підрозділ “Paths and Symbols”:
      1. Вкладка “Includes”:
        1. кнопка Add: додати директорію “../src” з обраними “Add to all configurations” і “Add to all languages” {G} – спочатку “../src” є в мові “C++ GNU”, але, в невизначений момент, він може стертися з списку;
        2. кнопка “Apply”, і перевір, що “../src” з’явилося у всіх мовах і конфігураціях.
      2. Вкладка “Symbols”:
        1. кнопка Add: додати символ “__cplusplus” зі значенням “201103L” і вибраними “Add to all configurations” і “Add to all languages” – {F} докладніше;
        2. кнопка “Apply”, і перевір, що в конфігурації “gcc-debug” у “__cplusplus” значення “201103L“.
      3. Вкладка “Source Location”:
        1. Перевір, що в списку один пункт, і він вказує на “/inet/src{G}, якщо там щось інше (наприклад, просто “/inet“), то видаляй те, що є і додай (“Add Folder…”) “/inet/src“. Потім кнопка “Apply”, і повернення до {A}, оскільки всі фільтри при видаленні були стерті. До речі, “/inet” насправді можна залишити – з ним теж все нормально збирається, але краще звузити до оригінального “/inet/src“.
    2. Підрозділ “Preprocessor Include Paths, Marcos etc.” > вкладка “Providers”:
      1. Вибрати “CDT GCC Build-in Compiler Settings”:
        1. У групі “Language Settings Provider Options” натиснути на посилання “Workspace Settings”:
          1. вкладка “Discovery”: знову вибрати “CDT GCC Build-in Compiler Settings” і додати “-std=c++11 ” перед “${FLAGS}“”Command to get compiler specs”, повинно вийде приблизно так `${COMMAND} -std=c++11 ${FLAGS} -E -P -v -dD "${INPUTS}"` {F}, докладніше тут і тут;
          2. кнопка “Apply”, “Ok” (закриваємо вікно).
        2. перемістити “CDT GCC Build-in Compiler Settings” вище “CDT Managed Build System Entries” (для обох конфігурацій: “gcc-release” і “gcc-debug”) {F}, докладніше – після цього ми втратимо можливість перевизначати символи “CDT GCC Build-in Compiler Settings” через “CDT Managed Build System Entries” (“З/З++ General” > “Paths and Symbols” > “Symbols”), перевизначити можна буде тільки через додавання значень “CDT User Settings Entries” у вкладці “Entries” для кожної мови окремо (альтернатива: не міняємо порядок, т. к. в “CDT Managed Build System Entries” вже виправили значення “__cplusplus“; не міняємо порядок, видаляємо всі згадки “__cplusplus” з “CDT Managed Build System Entries”, і стежимо, щоб він там не з’являвся в майбутньому);
        3. кнопка “Apply”, і перевірити, що у вкладці “Entries” мови “GNU C++” в “CDT GCC Build-in Compiler Settings” (чекбокс [в нижній частині вікна] “Show build-in values” повинен бути включений) є запис “__cplusplus=201103L” (вона буде ближче до кінця).
    3. Підрозділ “Indexer”:
      1. у якості “Build configuration for indexer” вибрати “gcc-release” {B};
      2. кнопка “Apply”.

Деякі проблеми можуть виникнути з {E}. Поясню. Якщо все нормально, то Eclipse повинен підхопити ті параметри, які були задані в “configure.user” перед конфігуруванням OMNeT++ (./configure). У такому разі Eclipse передасть потрібні параметри у g++ через make. Однак не завжди все йде, як планувалося, і краще перевірити, що відбувається в реальності. Перевірити можна, дописавши в “Build command” {E}--just-print” або “--trace“, і, запустивши збірку (панель “Project Explorer” > проект “inet” > контекстне меню > “Clean Project” і “Build Project”), відкрити “Console” (Alt+Shift+Q,C), у ньому має виводиться щось схоже на “g++ -c -std=c++11 -O2 -fpredictive-commoning -march=native -freorder-blocks-and-partition -pipe -DNDEBUG=1 ...“. Якщо цього немає, то можна піти раді з уже згаданої статті.

Або підправити змінні оточення

Знову відкриваємо налаштування проекту (панель “Project Explorer” > проект “inet” > контекстне меню > Properties):

  1. Розділ “С/С++ Build”:
    1. Підрозділ “Build Variables” (перевір, що поточна конфігурація “gcc-release [ Active ]”):
      1. кнопка “Add…”, ім’я “CFLAGS“, тип “String”, значення “-O2 -fpredictive-commoning -march=native -freorder-blocks-and-partition -pipe“;
      2. кнопка “Add…”, ім’я “CXXFLAGS“, тип “String”, значення “-std=c++11 -O2 -fpredictive-commoning -march=native -freorder-blocks-and-partition -pipe“;
      3. кнопка “Apply”.
    2. Підрозділ “Environment”:
      1. кнопка “Add…”, ім’я “CFLAGS“, значення “${CFLAGS}“;
      2. кнопка “Add…”, ім’я “CXXFLAGS“, значення “${CXXFLAGS}“;
      3. кнопка “Apply”.

До речі, при деякій вправності, параметри запуску g++ можна було подивитися, не використовуючи прапори “--just-print” та “--trace“, а використовуючи Process Explorer. В Process Explorer також можна подивитися, у що розкривається “-march=native” при передачі в “cc1plus.exe”.

Тепер, нарешті, можна зібрати INET! Перевірте, що зараз активна конфігурація “gcc-release” {B}, і якщо раніше додавали прапори “--just-print” або “--trace” для перевірки {E}, то їх треба прибрати. Збираємо панель “Project Explorer” > проект “inet” > контекстне меню > “Clean Project” і “Build Project”), за процесом можна спостерігати в “Console” (Alt+Shift+Q,C).

Якщо все пройшло добре, то рекомендую закрити Eclipse, і зробити бекап файлу “.cproject” та директорії “.settings” з налаштуваннями проекту {B-G}, а також файлів: “.oppfeatures”, “.oppfeaturestate”, “.nedexclusions” – {A}.

Нарешті, настройка завершена, і можна перейти до найцікавішого.

# Створення першого проекту

Note: Перше, що я зробив після налаштування оточення – став вивчати вміст директорії “doc” у OMNeT++ і INET. Це були Simulation Manual і User Guide, пізніше до них приєднався Stack Overflow (у вигляді stackoverflow.com і у вигляді стану мозку). Нижче я покажу, як можна зробити перші кроки, не читаючи всю документацію, і розповім, з якими “особливостями” можна зіткнутися.

Note: Для тих, хто ще не встиг встановити собі OMNeT++ і INET, але вже хоче подивитися на код, текст нижче містить посилання на джерело INET в GitHub. Всі посилання ведуть на исходники версії 3.4.0 (ці посилання будуть доступні завжди, навіть якщо в майбутніх версіях розташування файлів в INET зміниться).

Перед створенням свого проекту добре б подивитися на вже готові моделі в INET, подивитися, як вони влаштовані. Може в ньому вже реалізовано те, що нам потрібно?

Після нетривалого блукання по дереву INET в “Project Explorer”, можна натрапити на директорію “inet/src/inet/applications”, і виявити в ній “udpapp” (UDP Application). UDP знадобиться нам для broadcast розсилки. Всередині директорії лежать кілька моделей, і, судячи з назви і розміром вихідного, найпростіший з них, це “UDPEchoApp”. Там є ще і “UDPBasicApp”, але він виявився не таким вже й “Basic”. Кожна модель складається з “.cc”, “.h” і “.ned” файлів. Поки не ясно, навіщо потрібні “.ned” файли, але судячи по їх утриманню (наявності рядки "parameters:“) у них можуть описуватися параметри моделі.

Продовжимо пошуки цікавих моделей. Подивимося, які приклади (inet/examples) є в INET. І нам пощастило, що в ньому є приклад з назвою “broadcast” (inet/examples/inet/broadcast)! Цей приклад крім файлів “.cc”, “.h” і “.ned”, містить ще “.ini” і “.xml файли. Пора розібратися, навіщо ці файли потрібні:

  • .ned – файл/мова, що описує модель мережі (Network), або найпростіші блоки (Simple modules) “цеглинки”, з яких можна збирати модулі (Compound module). В цілому це виглядає так (картинка), тобто можна зібрати модель мережі, і провести кілька експериментів не написавши жодного рядка на C++.
  • omnetpp.ini – файл, в якому можна встановити/змінити параметри моделі. Якщо потрібно провести декілька експериментів з різними параметрами, то всіх їх можна перерахувати (Named Configurations) в цьому ж файлі.
  • .xml – просто файл з налаштуваннями, які зчитує один з використовуваних модулів (IPv4NetworkConfigurator).

На жаль, цей приклад (“broadcast”) нам не підійде, оскільки в його мережу включені маршрутизатори. Однак, за аналогією з ним, можна створити свій проект.

Note: Далі я продовжу посилатися на різні розділи Simulation Manual. Як бачите, він достатньо великий, браузеру потрібен час (RAM) для його відкриття. Для вирішення цієї проблеми я зробив невеликий JS‑bookmarklet. Після його запуску всі посилання, що ведуть на розділи Simulation Manual, перестануть плодити вкладки (пожираючи ресурси), і почнуть перемикати розділи в одній єдиній додатковій вкладці (насправді він просто прописує target для кожної посилання на Simulation Manual). Bookmarklet розташований у першому коментарі до цієї статті. І, для того, щоб відрізнити посилання на Simulation Manual від інших посилань, bookmarklet змінює їх колір.

Чому bookmarklet знаходиться в коментарі, а не прямо тут?

При повторному відкритті статті bookmarklet доведеться запускати заново. На Хабре автори статей можуть у будь-який момент змінити вміст статті. Вміст коментаря можна змінити тільки протягом перших 5-и хвилин. При першому запуску bookmarklet ви напевно перевірили, що він робить.
⇒ запускати bookmarklet з тіла статті потенційно небезпечно – вони можуть у будь-який момент зміниться; якщо ж bookmarklet розміщений в коментарі, то достатньо перевірити його всього один раз (після закінчення 5-и хвилин з моменту публікації коментаря) – у майбутньому він не зміниться.

# Створюємо проект

Порожній проект “LLTR”, з директоріями “src” і “simulations”, і єдиною конфігурацією “gcc-release” (File → New → OMNeT++ Project…):

Залишилося налаштувати проект також як і “inet”, і можна буде рухатися далі. В основному, настройка буде відрізнятися відсутністю необхідності налаштовувати “gcc-debug” (т. к. він відсутній в “LLTR”), з додаванням в залежності “inet”. Більш конкретно: замість {A,B,G} треба відкрити розділ “Project References”, і включити залежність від “inet”.

# Структура проекту

Якщо подивитеся на файли, які створив Wizard, то побачите, що файл “package.ned” зустрічається двічі: в директорії “src”, і в “simulations”. Вміст теж відрізняється – “package lltr;” і “package lltr.simulations;” відповідно. Один з цих файлів нам не знадобиться.

Якщо провести аналогію зі структурою проекту INET, то директорія “inet/src” – це “LLTR/src”, а “inet/examples” – це “LLTR/simulations”. Тобто в “LLTR/simulations” краще розміщувати файли “.ned” c Network, а в “LLTR/src” – складові частини мережі (модулі).

Існує ще один нюанс – в INET дуже хороша внутрішня структура директорій, і якщо в майбутньому нам буде потрібно змінити один із стандартних модулів в INET, то краще буде створити новий модуль, і покласти його поруч з оригіналом в INET. Те ж саме можна застосувати і до модуля, створеного з нуля – знайти йому відповідне місце в INET.

У світлі сказаного, “.ned” в директорії “LLTR/src” нам не потрібен (все буде в “inet/src”), також як і не потрібен додатковий подпакет “package lltr.simulations;” в “LLTR/simulations”. Тому переносимо “package.ned” з “LLTR/src” у “LLTR/simulations”.

# Пробний запуск

Спробуйте запустити LLTR. Для цього достатньо відкрити файл “LLTR/simulations/omnetpp.ini”, і натиснути Run > Run As > 1 OMNeT++ Simulation):

При цьому Eclipse запропонує створити нову конфігурацію “simulations” для запуску симулятора. Погоджуємося, і відразу ж стикаємося з проблемою: “LLTR/src/LLTR.exe” не був знайдений. Все вірно, адже “LLTR.exe” ніхто не збирав, тому спочатку збираємо проект (меню Project → Build Project), а потім знову запускаємо симулятор (тим же самим способом).

Після запуску симулятора з’явилося попередження “No network specified in the configuration.”, його можна виправити, додавши рядок “network = lltr.Network” у секцію “[General]” файлу “omnetpp.ini“, і додавши рядок “network Network {}” кінець файлу “package.ned“. Цим ми створили порожню мережі “.ned” файлі), і налаштували (в “.ini” файлі) симулятор на завантаження цієї мережі (Network – ім’я мережі) при запуску.

Тепер можна спробувати знову запустити симулятор (Run > Run As > 1 OMNeT++ Simulation), і замість помилки повинно відкритися сіре поле (прямокутник) мережі Network на зеленому тлі.

Note: Є різниця між запуском через (Run > Run As > 1 OMNeT++ Simulation), і через (Run > 1 simulations): у першому випадку запуск проходить швидше, т. к. у другому випадку, перед запуском симулятора, Eclipse починає збирати проект.

Note: (або можна форкнуть – тег a1_v0.1.0 (“a” – article) “git checkout -b "my_branch" tags/a1_v0.1.0“)

# Рекомендації по використанню репозиторію

Репозиторій я створював таким чином, щоб:

  • кожен крок з tutorial збігався з комітом в git;
  • можна було легко послатися з tutorial на конкретний отримати для цього кожен значущий комміт має свій тег;
  • можна було клонувати (завантажити) до себе тільки коміти, що відносяться до поточної частини (article) – для цього кожна частина має свою гілку (формат імені: “article_#”), що вказує на останній комміт/тег частини;
  • будь зміг легко створювати свою версію вихідного коду з репозиторію, “крокуючи” по tutorial.

Note: без гілок “article_#” можна було б обійтися, і вказувати, при клонуванні, назва останнього тега частини (яке ще треба знайти), але з гілкою простіше/швидше.

Як забрати репозиторій “до себе”? Краще всього, спочатку його форкнуть на GitHub, а потім свій форк:

  • або клонувати весь “git clone“;
  • або клонувати тільки коміти за поточну частину “git clone --branch "article_#" --single-branch” (без використання “--depth“), а для отримання комітів наступній частині використовувати “git remote set-branches –add” (і якщо щось піде не так…)

Далі, для створення особистої гілки на основі конкретного тега, можна використовувати “git checkout -b "my_branch" tags/"tag_name"“.

Як створювати свою версію коду, тобто змінювати код? Якщо в майбутньому не виникне бажання зробити Pull Request, то нічого вам не заважає робити з форком що хочете >:-), проте я раджу, при появі змін, які хочеться зберегти, робити так):

Однакова схема найменування тегів допоможе в майбутньому уникнути колізій, навіть не дивлячись на те, що теги Pull Request не переносяться.

Note: Якщо я в майбутньому буду вносити зміни в репозиторій, то я поступлю також: оригінальний код збережеться, а змінений буде йти паралельно оригінальному (з “торованими” усіма змінами з інших (майбутніх) тегів, і з новими іменами тегів). Тільки замість додавання “-u” до імен нових тегів тегам, я буду збільшувати номер. Наприклад, теги оригінального коду “a1_v0.1.0“, “a1_v0.2.0“, … – теги зміненого коду “a1_v0.1.1“, “a1_v0.2.1“, … При наступній зміні, номер ще раз збільшиться: “a1_v0.1.2“, “a1_v0.2.2“, …

Note: tutorial всі місця, завершальні черговий “крок”, помічені позначкою git , і поруч з ним буде посилання на відповідний git tag.

Note: git diff використовувався стандартний, патчі генерувалися автоматично, і вони рідко будуть показувати логічного зв’язку у сталися зміни (зокрема, при додаванні нового коду та зміну рівня вкладеності / форматування існуючого коду) (тут би згодилася відстеження змін на рівні AST), схоже на цей проект для Java.

# Створення першої моделі (Link Layer Topology Reveal)

# Крок -1: збираємо мережа

Відкриємо “package.ned” в режимі графічного редагування схеми (вкладка “Design” знизу), і спробуємо накидати мережу з КДПВ:

Мережа побудована з тих же модулів, які були використані в прикладі broadcast:

  • хости – StandardHost;
  • світчі – EtherSwitch.

А ось як “проводи” (каналу зв’язку) обраний Eth100M (швидкість: 100 Mbps; довжина: 10 метрів). До речі, чому саме 10 метрів, де вони задаються, і чи можна змінити це значення? (відповідь трохи нижче)

Якщо переключиться в режим редагування коду (вкладка “Source” знизу), то ви повинні побачити приблизно це (git tag a1_v0.2.0) . Пояснення структури:

package "<a href="https://omnetpp.org/doc/omnetpp/manual/#sec:ned-ref:packages">ім'я пакета</a>"; //<a href="https://omnetpp.org/doc/omnetpp/manual/#sec:ned-ref:directory-structure">особливості найменування</a>

import "<a href="https://omnetpp.org/doc/omnetpp/manual/#sec:ned-lang:imports-and-name-resolution">ім'я підключеного пакета</a>";

network "<a href="https://omnetpp.org/doc/omnetpp/manual/#sec:ned-lang:warmup:network">назва описуваної мережі</a>"
{
 @display("візуальні параметри мережі, наприклад, розмір області");
 <a href="https://omnetpp.org/doc/omnetpp/manual/#sec:ned-ref:submodules">submodules</a>:
 "<a href="https://omnetpp.org/doc/omnetpp/manual/#sec:ned-lang:submodules">назва вузла</a>": "тип вузла" { @display("візуальні параметри сайту, наприклад, місце розташування"); }
 <a href="https://omnetpp.org/doc/omnetpp/manual/#sec:ned-ref:connections">connections</a>:
 "назва сайту"."<a href="https://omnetpp.org/doc/omnetpp/manual/#sec:ned-lang:gates">точка з'єднання</a>" <--> "тип каналу зв'язку" <--> "назва сайту"."точка з'єднання";
}

Warning: На жаль Хабр поки не підтримує вставку посилань за допомогою тега <a>...</a> – замість посилань зараз в код “вставляються” самі теги. І так відбувається не тільки з цим блоком коду, але і з усіма нижчою, в яких використовувалася вставка посилань, або виділення частин коду за допомогою тегів<strong>...</strong>, <em>...</em>).

Тимчасова заміна блоку кодуpackage "ім'я пакета"; //особливості найменування

import "ім'я підключається пакету";

network "назва описуваної мережі"
{
@display("візуальні параметри мережі, наприклад, розмір області");
submodules:
"назва вузла": "тип вузла" { @display("візуальні параметри сайту, наприклад, місце розташування"); }
connections:
"назва сайту"."точка з'єднання" <--> "тип каналу зв'язку" <--> "назва сайту"."точка з'єднання";
}

Окремо варто сказати про “точки з’єднання” (Gates) і канали зв’язку:

  1. Gates можуть бути оголошені як вектори, у цьому випадку підключаться до них можна явно, вказавши номер gate “"назва сайту"."gate"["номер"]“, або автоматично – инкрементально"назва сайту"."gate"++“.
  2. Параметри каналу або можуть бути задані в місці використання (наприклад: “... <--> { delay = 100ms; } <--> ...“), або можуть мати ім’я/тип, на яке можна посилатися (як у прикладі broadcast: “... <--> C <--> ...“), або можуть мати тип і бути перевизначені на місці (наприклад: “... <--> FastEthernet {per = 1e-6;} <--> ...“)або…
  3. Gates можуть бути односпрямованими (тип при оголошенні: output / input; з’єднувачі при підключенні: --> / <--), і двонаправленими (тип при оголошенні: inout; з’єднувач при підключенні: <--> ). Двонаправлені складаються з двох однонаправлених, до яких можна звернутися безпосередньо, дописавши суфікс "$i” або “$o“.

Warning: На жаль парсер Хабра поки зміг обробити лише 1⁄3 публікації за відведені йому 20 секунд (приходить помилка 504 “Gateway Time-out”). Але навіть ці 1⁄3 дозволили виявити ще один баг в парсере. Невеликий приклад (початкова розмітка):

Виконайте в точності цю <a href="#set">команду: "<code>set: p=1.87548</code>"</a> в терміналі свого портативного ядерного реактора.

Після парсера:

Виконайте в точності цю <a href="#set"><code>команду: "set: p=1.87548</code>"</a> в терміналі свого портативного ядерного реактора.

Побачити цей баг у дії можна, поглянувши на кінець 3‑го пункту в списку вище. Його оригінальна розмітка виглядала так:

можна звернутися безпосередньо, <a href="https://omnetpp.org/doc/omnetpp/manual/#sec:ned-ref:inout-gates">дописавши суфікс "<strong><code>$i</code></strong>" або "<strong><code>$o</code></strong>"</a>.

Хороша новина в тому, що парсер зараз переписують. І, щоб не чекати закінчення цього процесу, я розмістив повну версію публікації на GitHub Pages:

Note: У повній версії я спростив активацію target в посилань на Simulation Manual – замість запуску bookmarklet‘а, достатньо натиснути на кнопку тут. До того ж при відкритті повної версії через кнопку «Читати далі →», активація відбудеться автоматично.

Note: Розмітка, CSS і JS цієї сторінки можуть здатися дивними – вся справа в тому, що процес підготовки публікації у мене розбитий на 3 етапи, і в GitHub Pages знаходиться трохи змінений результат 2‑го етапу (звичайний HTML, придатний для перегляду в браузері, і, при цьому, максимально близький до хабра‑розмітки). 3‑й етап – це модифікація розмітки під Хабр.

# В наступних частинах / To be continued…

  • 2. Алгоритм визначення топології мережі по зібраної статистики
  • 3. OMNeT++ продовження
  • 4. Математика
  • 5. OMNeT++ продовження 2
  • 6. Реалізація
  • 7. Експеримент (назва‑спойлер: “в кінці Джон помре”)

# Зворотний зв’язок

Невелике опитування. Перше питання допоможе мені краще визначити час для публікації наступній частині. Другий – покращити статтю. Інші питання – чисте цікавість.

Related Articles

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *

Close