Académique Documents
Professionnel Documents
Culture Documents
com
OMNeT++
Manuel de simulation
Variante 5.4.1
Copyright © 2016 András Varga et OpenSim Ltd.
Manuel de simulation OMNeT++ –
Chapitres
Contenu v
1 introduction 1
2 Aperçu 3
3 Le langage NED 11
4 Modules simples 49
sept
La bibliothèque de simulation 157
15 Essais 349
iii
18 Intégration du noyau de simulation 385
UNE
Référence NED 395
Indice 504
Manuel de simulation OMNeT++ –
Contenu
Contenu v
1 introduction 1
1.1 Qu'est-ce qu'OMNeT++ ? .................................... 1
1.2 Organisation de ce manuel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
2 Aperçu 3
2.1 Concepts de modélisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2.1.1 Modules hiérarchiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2.1.2 Types de modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2.1.3 Messages, Portails, Liens . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.1.4 Modélisation des transmissions de paquets . . . . . . . . . . . . . . . . . . . . . . . . 5
2.1.5 Paramètres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.1.6 Méthode de description de la topologie . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.2 Programmation des algorithmes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.3 Utilisation d'OMNeT++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.3.1 Construction et exécution de simulations . . . . . . . . . . . . . . . . . . . . . . . 6
2.3.2 Contenu de la distribution . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
3 Le langage NED 11
3.1 Présentation du NDE ....................................... 11
3.2 Démarrage rapide NED ...................................... 12
3.2.1 Le réseau .................................... 12
3.2.2 Présentation d'un canal .............................. 14
3.2.3 Les modules simples d'application, de routage et de file d'attente ................ 14
3.2.4 Le module composé de nœuds . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
3.2.5 Assemblage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
3.3 Modules simples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
3.4 Modules composés . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
3.5 Canaux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
v
3.6 Paramètres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
3.6.1 Affectation d'une valeur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
3.6.2 Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
3.6.3 volatile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
3.6.4 Unités . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
3.6.5 Paramètres XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
3.7 Portes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
3.8 Sous-modules ........................................ 30
3.9 Connexions ........................................ 32
3.9.1 Spécification du canal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
3.9.2 Noms des chaînes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
3.10Connexions multiples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
3.10.1Exemples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
3.10.2Modèles de connexion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
3.11 Sous-module paramétrique et types de connexion . . . . . . . . . . . . . . . . . . . . . 37
3.11.1Types de sous-modules paramétriques . . . . . . . . . . . . . . . . . . . . . . . . . . 37
3.11.2 Sous-modules paramétriques conditionnels . . . . . . . . . . . . . . . . . . . . . . 39
3.11.3Types de connexion paramétrique . . . . . . . . . . . . . . . . . . . . . . . . . . 40
3.12Annotations de métadonnées (propriétés) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
3.12.1Indices de propriété . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
3.12.2Modèle de données . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
3.12.3Remplacer et étendre les valeurs de propriété . . . . . . . . . . . . . . . . . . . 43
3.13 Héritage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
3.14Forfaits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
3.14.1Aperçu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
3.14.2Résolution de nom, importations ............................ 45
3.14.3Résolution de nom avec "like" ........................... 46
3.14.4 Le package par défaut . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
4 Modules simples 49
4.1 Concepts de simulation ................................... 49
4.1.1 Simulation d'événements discrets ............................ 49
4.1.2 La boucle d'événements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
4.1.3 Événements et ordre d'exécution des événements dans OMNeT++ . . . . . . . . . . . . . . . 50
4.1.4 Temps de simulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
4.1.5 Mise en œuvre de la FES . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
4.2 Composants, modules simples, voies . . . . . . . . . . . . . . . . . . . . . . . . 52
4.3 Définition des types de modules simples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
4.3.1 Présentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
4.3.2 Constructeur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
4.3.3 Initialisation et finalisation . . . . . . . . . . . . . . . . . . . . . . . . . . 55
4.4 Ajout de fonctionnalités à cSimpleModule . . . . . . . . . . . . . . . . . . . . . . . . 58
4.4.1 handleMessage() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
4.4.2 activité() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
4.4.3 Comment éviter les variables globales . . . . . . . . . . . . . . . . . . . . . . . . . . 68
4.4.4 Réutilisation du code de module via le sous-classement . . . . . . . . . . . . . . . . . . . . . 68
4.5 Accès aux paramètres du module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
4.5.1 Paramètres volatils et non volatils . . . . . . . . . . . . . . . . . . . . . . 69
4.5.2 Modification de la valeur d'un paramètre . . . . . . . . . . . . . . . . . . . . . . . . . . 70
4.5.3 Autres méthodes cPar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
4.5.4 Emulation de tableaux de paramètres . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
4.5.5 handleParameterChange() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
4.6 Accès aux portails et aux connexions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
4.6.1 Objets Gate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
4.6.2 Connexions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
4.6.3 Le canal de connexion ............................ 77
4.7 Envoi et réception de messages ............................ 78
4.7.1 Messages personnels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
4.7.2 Envoi de messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
4.7.3 Diffusions et retransmissions . . . . . . . . . . . . . . . . . . . . . . . . 80
4.7.4 Envoi différé . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
4.7.5 Envoi de messages directs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
4.7.6 Transmissions de paquets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
4.7.7 Recevoir des messages avec activity() . . . . . . . . . . . . . . . . . . . . . . . . 86
4.8 Canaux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
4.8.1 Présentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
4.8.2 L'API de canal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
4.8.3 Exemples de canaux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
4.9 Arrêt de la simulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
4.9.1 Terminaison normale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
4.9.2 Génération d'erreurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
4.10 Machines à états finis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
4.10.1Aperçu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
4.11Naviguer dans la hiérarchie des modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
4.11.1 Vecteurs de modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
4.11.2Identifiants des composants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
4.11.3Monter et descendre dans la hiérarchie des modules . . . . . . . . . . . . . . . . . 96
4.11.4Recherche de modules par chemin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
4.11.5 Itération sur les sous-modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
4.11.6Naviguer dans les connexions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
4.12 Appels directs de méthode entre modules . . . . . . . . . . . . . . . . . . . . . . . . . 98
4.13Création de modules dynamiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
4.13.1Quand utiliser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
4.13.2Aperçu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
4.13.3Créer des modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
4.13.4Suppression de modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
4.13.5Suppression de module et finition() ........................... 101
4.13.6Création de connexions ............................... 102
4.13.7 Suppression de connexions .............................. 103
4.14Signaux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
4.14.1 Considérations de conception et justification . . . . . . . . . . . . . . . . . . . . . . 104
4.14.2Le mécanisme des signaux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
4.14.3Écouter les modifications du modèle . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
4.15Enregistrement de statistiques basées sur le signal . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
4.15.1 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
4.15.2Déclarer des statistiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
4.15.3Enregistrement des statistiques pour les signaux enregistrés dynamiquement . . . . . . . . . . . 117
4.15.4Ajout de filtres de résultats et d'enregistreurs par programmation . . . . . . . . . . . . 118
4.15.5 Émission de signaux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
4.15.6Écrire des filtres et des enregistreurs de résultats . . . . . . . . . . . . . . . . . . . . . . 120
sept
La bibliothèque de simulation 157
7.1 Fondamentaux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
7.1.1 Utilisation de la bibliothèque . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
7.1.2 La classe de base cObject . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
7.1.3 Itérateurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
7.1.4 Erreurs d'exécution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
7.2 Journalisation à partir des modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
7.2.1 Sortie du journal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
7.2.2 Niveaux de journalisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
7.2.3 Instructions de journal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162
7.2.4 Catégories de journaux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
7.2.5 Composition et nouvelles lignes . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
7.2.6 Mise en œuvre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
7.3 Générateurs de nombres aléatoires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
7.3.1 Implémentations RNG . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
7.3.2 RNG globaux et composants locaux . . . . . . . . . . . . . . . . . . . . . . . 165
7.3.3 Accès aux RNG . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
7.4 Génération de variables aléatoires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
7.4.1 Méthodes des composants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167
7.4.2 Classes de flux de nombres aléatoires . . . . . . . . . . . . . . . . . . . . . . . . 168
7.4.3 Fonctions du générateur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169
7.4.4 Nombres aléatoires à partir d'histogrammes . . . . . . . . . . . . . . . . . . . . . . . 169
7.4.5 Ajout de nouvelles distributions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169
7.5 Classes de conteneurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169
7.5.1 Classe de file d'attente : cQueue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169
7.5.2 Tableau extensible : cArray . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171
7.6 Prise en charge du routage : cTopologie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172
7.6.1 Présentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172
7.6.2 Utilisation de base . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172
7.6.3 Chemins les plus courts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174
7.6.4 Manipulation du graphique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175
7.7 Correspondance de modèle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176
7.7.1 cPatternMatcher . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176
7.7.2 cMatchExpression . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177
7.8 Collecte de statistiques récapitulatives et d'histogrammes . . . . . . . . . . . . . . . . . . . . 179
7.8.1 cStdDev . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180
7.8.2 cHistogramme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181
7.8.3 cPSquare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185
7.8.4 cKSplit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185
7.9 Enregistrement des résultats de la simulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187
7.9.1 Vecteurs de sortie : cOutVector . . . . . . . . . . . . . . . . . . . . . . . . . . . 187
7.9.2 Scalaires de sortie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188
7.10Veilles et instantanés . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189
7.10.1Montres de base . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189
7.10.2 Surveillances de lecture-écriture ................................ 190
7.10.3 Montres structurées ................................ 191
7.10.4Montres STL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191
7.10.5Instantanés . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191
7.10.6Obtenir l'utilisation de la pile de coroutines . . . . . . . . . . . . . . . . . . . . . . . . . . 193
7.11Définition de nouvelles fonctions NED .............................. 194
7.11.1Définir_Fonction_NED() .............................. 194
7.11.2Définir_NED_Math_Fonction() .......................... 198
7.12 Dérivation de nouvelles classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199
7.12.1cObjet ou pas ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199
7.12.2cMéthodes virtuelles d'objet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200
7.12.3 Inscription aux cours . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201
7.12.4Détails . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201
7.13Gestion de la propriété des objets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204
7.13.1 L'arbre de propriété . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204
7.13.2Gestion de la propriété . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 206
15 Essais 349
15.1Aperçu .......................................... 349
15.1.1Vérification, Validation .............................. 349
15.1.2Tests unitaires, tests de régression . . . . . . . . . . . . . . . . . . . . . . . . . 349
15.2 L'outil opp_test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 350
15.2.1Présentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 350
15.2.2 Terminologie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 353
15.2.3Tester la syntaxe du fichier .................................. 353
15.2.4Description de l'essai .................................. 353
15.2.5 Génération de code de test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 353
15.2.6Critères de réussite . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 355
15.2.7Étapes de traitement supplémentaires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 357
15.2.8Non résolu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 358
15.2.9 Synopsis opp_test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 358
15.2.10Rédaction du script de contrôle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 359
15.3 Essais de fumée . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 359
15.4 Tests d'empreintes digitales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 360
15.4.1 Calcul d'empreintes digitales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 360
15.4.2 Tests d'empreintes digitales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 362
15.5Tests unitaires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 362
15.6Tests des modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 362
15.7Tests statistiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 362
15.7.1 Essais de validation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 363
15.7.2Tests de régression statistique . . . . . . . . . . . . . . . . . . . . . . . . . . . . 363
15.7.3Mise en œuvre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 363
UNE
Référence NED 395
A.1 Syntaxe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 395
A.1.1 Extension du nom de fichier NED . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 395
A.1.2 Codage de fichier NED . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 395
A.1.3 Mots réservés . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 395
A.1.4 Identifiants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 396
A.1.5 Sensibilité à la casse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 396
A.1.6 Littéraux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 396
A.1.7 Commentaires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 396
A.1.8 Grammaire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 396
A.2 Définitions intégrées . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 397
A.3 Forfaits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 398
A.3.1 Déclaration de paquet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 398
A.3.2 Structure des répertoires, package.ned . . . . . . . . . . . . . . . . . . . . . . . . 398
A.4 Composants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 399
A.4.1 Modules simples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 399
A.4.2 Modules composés . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 399
A.4.3 Réseaux ...................................... 399
A.4.4 Canaux ...................................... 400
A.4.5 Interfaces des modules ................................. 400
A.4.6 Interfaces de canaux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 400
A.4.7 Résolution de la classe d'implémentation C++ . . . . . . . . . . . . . . . . . . . . 401
A.4.8 Propriétés . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 401
A.4.9 Paramètres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 403
A.4.10Affectations de modèles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 404
A.4.11Portes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 405
A.4.12Sous-modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 406
A.4.13Connexions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 407
A.4.14Connexions conditionnelles et en boucle, groupes de connexion . . . . . . . . . . . 411
A.4.15Types internes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 411
A.4.16Unicité du nom . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 411
A.4.17Ordre d'affectation des paramètres . . . . . . . . . . . . . . . . . . . . . . . . . . . 412
A.4.18Résolution de nom de type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 414
A.4.19Résolution des types paramétriques . . . . . . . . . . . . . . . . . . . . . . . . . 414
A.4.20Implémentation d'une interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . 417
A.4.21 Héritage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 417
A.4.22 Ordre de construction du réseau . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 418
A.5 Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 419
A.5.1 Constantes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 419
A.5.2 Opérateurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 419
A.5.3 Paramètres de référencement et variables de boucle . . . . . . . . . . . . . . . . . . 420
A.5.4 Lenom de typeOpérateur. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 420
A.5.5 LeindiceOpérateur. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 421
A.5.6 Leexiste()Opérateur. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 421
A.5.7 Letaille de()Opérateur. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 421
A.5.8 Fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 421
A.5.9 Unités de mesure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 422
Indice 504
Manuel de simulation OMNeT++ – Introduction
Chapitre 1
introduction
OMNeT++ est un cadre de simulation de réseau à événements discrets modulaire orienté objet. Il a une
architecture générique, il peut donc être (et a été) utilisé dans divers domaines problématiques :
OMNeT++ lui-même n'est pas un simulateur de quelque chose de concret, mais fournit plutôt une infrastructure et des
outils pour l'écriture simulations. L'un des ingrédients fondamentaux de cette infrastructure est une architecture de
composants pour les modèles de simulation. Les modèles sont assemblés à partir de composants réutilisables appelés
modules. Les modules bien écrits sont vraiment réutilisables et peuvent être combinés de différentes manières comme
les blocs LEGO.
Les modules peuvent être connectés les uns aux autres via des portes (d'autres systèmes les appelleraient des ports) et
combinés pour former des modules composés. La profondeur d'imbrication des modules n'est pas limitée. Les modules
communiquent par transmission de messages, où les messages peuvent transporter des structures de données
arbitraires. Les modules peuvent transmettre des messages le long de chemins prédéfinis via des portes et des
connexions, ou directement vers leur destination ; ce dernier est utile pour les simulations sans fil, par exemple. Les
modules peuvent avoir des paramètres qui peuvent être utilisés pour personnaliser le comportement du module et/ou
pour paramétrer la topologie du modèle. Les modules au niveau le plus bas de la hiérarchie des modules sont appelés
modules simples et ils encapsulent le comportement du modèle. Des modules simples sont programmés en C++ et
utilisent la bibliothèque de simulation.
Les simulations OMNeT++ peuvent être exécutées sous diverses interfaces utilisateur. Les interfaces utilisateur
graphiques et animées sont très utiles à des fins de démonstration et de débogage, et les interfaces utilisateur de ligne
de commande sont idéales pour l'exécution par lots.
1
Manuel de simulation OMNeT++ – Introduction
Le simulateur ainsi que les interfaces utilisateur et les outils sont hautement portables. Ils sont testés sur les systèmes
d'exploitation les plus courants (Linux, Mac OS/X, Windows), et ils peuvent être compilés prêts à l'emploi ou après des
modifications insignifiantes sur la plupart des systèmes d'exploitation de type Unix.
OMNeT++ prend également en charge la simulation distribuée parallèle. OMNeT++ peut utiliser plusieurs
mécanismes de communication entre les partitions d'une simulation distribuée parallèle, par exemple MPI ou des
canaux nommés. L'algorithme de simulation parallèle peut facilement être étendu ou de nouveaux peuvent être
ajoutés. Les modèles n'ont besoin d'aucune instrumentation spéciale pour fonctionner en parallèle - c'est juste
une question de configuration. OMNeT++ peut même être utilisé pour la présentation en classe d'algorithmes de
simulation parallèle, car les simulations peuvent être exécutées en parallèle même sous l'interface graphique qui
fournit des informations détaillées sur ce qui se passe.
OMNEST est la version commerciale d'OMNeT++. OMNeT++ est gratuit uniquement pour une utilisation
académique et à but non lucratif ; à des fins commerciales, il faut obtenir des licences OMNEST auprès de
Simulcraft Inc.
• Les chapitres 8 et 14 expliquent comment personnaliser les graphiques du réseau et comment écrire des
commentaires de code source NED à partir desquels la documentation peut être générée.
• Les chapitres 9, 10, 11 et 12 traitent de questions pratiques telles que la construction et l'exécution de simulations
et l'analyse des résultats, et décrivent les outils fournis par OMNeT++ pour prendre en charge ces tâches.
• Les annexes fournissent une référence sur le langage NED, les options de configuration, les formats de
fichiers et d'autres détails.
2
Manuel de simulation OMNeT++ – Présentation
Chapitre 2
Aperçu
Un modèle OMNeT++ se compose de modules qui communiquent avec la transmission de messages. Les modules
actifs sont appelésmodules simples; ils sont écrits en C++, en utilisant la bibliothèque de classes de simulation. Les
modules simples peuvent être regroupés enmodules composéset ainsi de suite; le nombre de niveaux
hiérarchiques est illimité. L'ensemble du modèle, appelé réseau dans OMNeT++, est lui-même un module
composé. Les messages peuvent être envoyés via des connexions qui s'étendent sur des modules ou directement
à d'autres modules. Le concept de modules simples et composés est similaire aux modèles DEVS atomiques et
couplés.
Dans la Fig. 2.1, les cases représentent les modules simples (fond gris) et les modules composés. Les
flèches reliant les petites cases représentent les connexions et les portes.
Réseau
Modules simples
Module composé
Les modules communiquent avec des messages qui peuvent contenir des données arbitraires, en plus des attributs
habituels tels qu'un horodatage. Les modules simples envoient généralement des messages via des portes, mais il est
également possible de les envoyer directement à leurs modules de destination. Les portes sont les interfaces d'entrée et
de sortie des modules : les messages sont envoyés via des portes de sortie et arrivent via des portes d'entrée. Une porte
d'entrée et une porte de sortie peuvent être liées par une connexion. Les connexions sont créées au sein d'un seul
niveau de hiérarchie de modules ; à l'intérieur d'un module composé, des portes correspondantes de deux sous-
modules, ou une porte d'un sous-module et une porte du module composé peuvent être connectées. Les connexions
couvrant des niveaux hiérarchiques ne sont pas autorisées, car elles
3
Manuel de simulation OMNeT++ – Présentation
gêner la réutilisation du modèle. En raison de la structure hiérarchique du modèle, les messages voyagent généralement
à travers une chaîne de connexions, commençant et arrivant dans des modules simples. Les modules composés agissent
comme des "boîtes en carton" dans le modèle, relayant de manière transparente les messages entre leur domaine
intérieur et le monde extérieur. Des paramètres tels que le délai de propagation, le débit de données et le taux d'erreur
binaire peuvent être affectés aux connexions. On peut également définir des types de connexion avec des propriétés
spécifiques (appelées canaux) et les réutiliser à plusieurs endroits. Les modules peuvent avoir des paramètres. Les
paramètres sont principalement utilisés pour transmettre des données de configuration à des modules simples et pour
aider à définir la topologie du modèle. Les paramètres peuvent prendre des valeurs de chaîne, numériques ou
booléennes. Comme les paramètres sont représentés comme des objets dans le programme, les paramètres - en plus de
maintenir des constantes - peuvent agir de manière transparente comme des sources de nombres aléatoires, les
distributions réelles étant fournies avec la configuration du modèle. Ils peuvent inviter interactivement l'utilisateur à
entrer la valeur, et ils peuvent également contenir des expressions faisant référence à d'autres paramètres. Les modules
composés peuvent transmettre des paramètres ou des expressions de paramètres à leurs sous-modules.
OMNeT++ fournit des outils efficaces permettant à l'utilisateur de décrire la structure du système réel. Certaines
des principales caractéristiques sont les suivantes :
Les modules simples et composés sont des instances detypes de modules. Lors de la description du modèle,
l'utilisateur définit les types de modules ; les instances de ces types de modules servent de composants pour des
types de modules plus complexes. Enfin, l'utilisateur crée le module système en tant qu'instance d'un type de
module préalablement défini ; tous les modules du réseau sont instanciés en tant que sous-modules et sous-sous-
modules du module système.
Lorsqu'un type de module est utilisé comme bloc de construction, peu importe qu'il s'agisse d'un
module simple ou composé. Cela permet à l'utilisateur de diviser un module simple en plusieurs
modules simples intégrés dans un module composé, ou vice versa, pour agréger les fonctionnalités
d'un module composé en un seul module simple, sans affecter les utilisateurs existants du type de
module.
4
Manuel de simulation OMNeT++ – Présentation
Les types de modules peuvent être stockés dans des fichiers séparément du lieu de leur utilisation réelle. Cela signifie
que l'utilisateur peut regrouper les types de modules existants et créerbibliothèques de composants. Cette fonctionnalité
sera abordée plus tard, au chapitre 11.
Les modules communiquent en échangeant messages. Dans une simulation réelle, les messages peuvent représenter
des trames ou des paquets dans un réseau informatique, des emplois ou des clients dans un réseau de file d'attente ou
d'autres types d'entités mobiles. Les messages peuvent contenir des structures de données arbitrairement complexes.
Des modules simples peuvent envoyer des messages soit directement à leur destination, soit le long d'un chemin
prédéfini, via des portes et des connexions.
Le "temps de simulation locale" d'un module avance lorsque le module reçoit un message. Le message peut
provenir d'un autre module ou du même module (auto-messages sont utilisés pour implémenter des
temporisateurs).
portes sont les interfaces d'entrée et de sortie des modules ; les messages sont envoyés par les portes de
sortie et arrivent par les portes d'entrée.
Chaque lien (aussi appelé lien) est créé au sein d'un seul niveau de la hiérarchie des modules : au sein
d'un module composé, on peut connecter les portes correspondantes de deux sous-modules, ou une
porte d'un sous-module et une porte du module composé (Fig. 2.1).
En raison de la structure hiérarchique du modèle, les messages voyagent généralement à travers une série de
connexions, commençant et arrivant dans des modules simples. Les modules composés agissent comme des
"boîtes en carton" dans le modèle, relayant de manière transparente les messages entre leur domaine intérieur et
le monde extérieur.
Pour faciliter la modélisation des réseaux de communication, les connexions peuvent être utilisées pour
modéliser des liens physiques. Les connexions prennent en charge les paramètres suivants :débit de données,
délai de propagation, le taux d'erreur binaire et taux d'erreur sur les paquets, et peut être désactivé. Ces
paramètres et les algorithmes sous-jacents sont encapsulés danscanaliser objets. L'utilisateur peut paramétrer les
types de canaux fournis par OMNeT++, et également en créer de nouveaux.
Lorsque des débits de données sont utilisés, un objet paquet est par défaut livré au module cible au
moment de la simulation qui correspond à la fin de la réception du paquet. Ce comportement n'étant
pas adapté à la modélisation de certains protocoles (par exemple Ethernet semi-duplex), OMNeT++
offre la possibilité au module cible de spécifier qu'il souhaite que l'objet paquet lui soit livré au début
de la réception du paquet.
2.1.5 Paramètres
Les modules peuvent avoir des paramètres. Les paramètres peuvent être affectés soit dans les fichiers NED, soit dans le
fichier de configurationomnetpp.ini.
Les paramètres peuvent être utilisés pour personnaliser le comportement d'un module simple et pour paramétrer la topologie
du modèle.
Les paramètres peuvent prendre des valeurs de chaîne, numériques ou booléennes, ou peuvent contenir des arbres de données
XML. Les valeurs numériques incluent des expressions utilisant d'autres paramètres et appelant des fonctions C, des variables
aléatoires de différentes distributions et des valeurs saisies de manière interactive par l'utilisateur.
5
Manuel de simulation OMNeT++ – Présentation
Les paramètres à valeur numérique peuvent être utilisés pour construire des topologies de manière flexible. Dans
un module composé, les paramètres peuvent définir le nombre de sous-modules, le nombre de portes et la
manière dont les connexions internes sont établies.
L'utilisateur définit la structure du modèle dans les descriptions du langage NED (Network
Description). Le langage NED sera discuté en détail dans le chapitre 3.
Les modules simples d'un modèle contiennent des algorithmes sous forme de fonctions C++. La flexibilité et la puissance
totales du langage de programmation peuvent être utilisées, prises en charge par la bibliothèque de classes de
simulation OMNeT++. Le programmeur de simulation peut choisir entre une description axée sur les événements et une
description de style de processus, et utiliser librement des concepts orientés objet (héritage, polymorphisme, etc.) et des
modèles de conception pour étendre les fonctionnalités du simulateur.
Les objets de simulation (messages, modules, files d'attente, etc.) sont représentés par des classes C++. Ils ont été
conçus pour fonctionner ensemble efficacement, créant un puissant cadre de programmation de simulation. Les
classes suivantes font partie de la bibliothèque de classes de simulation :
• message, paquet
Les classes sont également spécialement instrumentées, permettant de parcourir les objets d'une simulation en cours
d'exécution et d'afficher des informations à leur sujet telles que le nom, le nom de la classe, les variables d'état ou le
contenu. Cette fonctionnalité permet de créer une interface graphique de simulation où tous les éléments internes de la
simulation sont visibles.
Cette section fournit des informations sur le travail avec OMNeT++ dans la pratique. Des questions telles que les fichiers
modèles et la compilation et l'exécution de simulations sont abordées.
• Description(s) de la topologie du langage NED (.ned fifichiers) qui décrivent la structure du module avec des paramètres,
des portes, etc. Les fichiers NED peuvent être écrits à l'aide de n'importe quel éditeur de texte, mais l'IDE OMNeT++
fournit un excellent support pour l'édition graphique et textuelle bidirectionnelle.
6
Manuel de simulation OMNeT++ – Présentation
• Les définitions de messages (.msg files) qui permettent de définir des types de messages et d'y ajouter des
champs de données. OMNeT++ traduira les définitions de message en classes C++ complètes.
• Noyau de simulation. Celui-ci contient le code qui gère la simulation et la bibliothèque de classes de
simulation. Il est écrit en C++, compilé dans une bibliothèque partagée ou statique.
• Les interfaces des utilisateurs. Les interfaces utilisateur OMNeT++ sont utilisées dans l'exécution de la simulation,
pour faciliter le débogage, la démonstration ou l'exécution par lots des simulations. Ils sont écrits en C++,
compilés dans des bibliothèques.
Les programmes de simulation sont construits à partir des composants ci-dessus. D'abord, .msg fiLes fichiers sont traduits en
code C++ à l'aide deopp_msgc.programme. Ensuite, toutes les sources C++ sont compilées et liées au noyau de simulation et à
une bibliothèque d'interface utilisateur pour former un exécutable de simulation ou une bibliothèque partagée. Les fichiers NED
sont chargés dynamiquement dans leurs formes de texte d'origine lorsque le programme de simulation démarre.
La simulation peut être compilée en tant qu'exécutable de programme autonome ou en tant que bibliothèque partagée
à exécuter à l'aide d'OMNeT++.opp_runutilitaire. Lorsque le programme est lancé, il lit d'abord les fichiers NED, puis le
fichier de configuration généralement appeléomnetpp.ini.Le fichier de configuration contient des paramètres qui
contrôlent la manière dont la simulation est exécutée, les valeurs des paramètres du modèle, etc. Le fichier de
configuration peut également prescrire plusieurs exécutions de simulation ; dans le cas le plus simple, ils seront exécutés
par le programme de simulation les uns après les autres.
La sortie de la simulation est écrite dans des fichiers de résultats : fichiers vectoriels de sortie, fichiers
scalaires de sortie et éventuellement les propres fichiers de sortie de l'utilisateur. OMNeT++ contient un
environnement de développement intégré (IDE) qui fournit un environnement riche pour analyser ces
fichiers. Les fichiers de sortie sont des fichiers texte orientés ligne qui permettent de les traiter avec une
variété d'outils et de langages de programmation, y compris Matlab, GNU R, Perl, Python et des tableurs.
L'objectif principal des interfaces utilisateur est de rendre les éléments internes du modèle visibles pour l'utilisateur, de
contrôler l'exécution de la simulation et éventuellement de permettre à l'utilisateur d'intervenir en modifiant des
variables/objets à l'intérieur du modèle. Ceci est très important dans la phase de développement/débogage du projet de
simulation. Tout aussi important, une expérience pratique permet à l'utilisateur de se faire une idée du comportement
du modèle. L'interface utilisateur graphique peut également être utilisée pour démontrer le fonctionnement d'un
modèle.
Le même modèle de simulation peut être exécuté avec différentes interfaces utilisateur, sans modification des fichiers de
modèle eux-mêmes. L'utilisateur testerait et déboguerait généralement la simulation avec une interface utilisateur
graphique puissante, et l'exécuterait finalement avec une interface utilisateur simple et rapide qui prend en charge
l'exécution par lots.
sept
Manuel de simulation OMNeT++ – Présentation
Bibliothèques de composants
Les types de modules peuvent être stockés dans des fichiers séparés du lieu de leur utilisation réelle, permettant à l'utilisateur de
regrouper les types de modules existants et de créer des bibliothèques de composants.
Un exécutable de simulation peut stocker plusieurs modèles indépendants qui utilisent le même ensemble de
modules simples. L'utilisateur peut spécifier dans le fichier de configuration quel modèle doit être exécuté. Cela
permet de créer un grand exécutable contenant plusieurs modèles de simulation et de le distribuer en tant
qu'outil de simulation autonome. La flexibilité du langage de description de la topologie prend également en
charge cette approche.
Une installation OMNeT++ contient les sous-répertoires suivants. Selon la plate-forme, des répertoires
supplémentaires peuvent également être présents, contenant des logiciels fournis avec OMNeT++.)
Le système de simulation lui-même :
8
Manuel de simulation OMNeT++ – Présentation
La version Windows d'OMNeT++ contient une redistribution du compilateur MinGW gcc, ainsi qu'une copie
de MSYS qui fournit les outils Unix couramment utilisés dans les Makefiles. Le répertoire MSYS contient
également diverses bibliothèques open source tierces nécessaires pour compiler et exécuter OMNeT++.
outils/ Outils et compilateurs spécifiques à la plate-forme (par exemple MinGW/MSYS sous Windows)
9
Manuel de simulation OMNeT++ – Présentation
dix
Manuel de simulation OMNeT++ – Le langage NED
chapitre 3
Le langage NED
L'utilisateur décrit la structure d'un modèle de simulation dans le langage NED. NED signifie description de
réseau. NED permet à l'utilisateur de déclarer des modules simples, de les connecter et de les assembler en
modules composés. L'utilisateur peut étiqueter certains modules composés commeréseaux; c'est-à-dire des
modèles de simulation autonomes. Les canaux sont un autre type de composant, dont les instances peuvent
également être utilisées dans des modules composés.
Le langage NED possède plusieurs fonctionnalités qui lui permettent de bien s'adapter aux grands projets :
Hiérarchique.La manière traditionnelle de gérer la complexité consiste à introduire des hiérarchies. Dans
OMNeT++, tout module qui serait trop complexe en tant qu'entité unique peut être décomposé
en modules plus petits et utilisé comme module composé.
Basé sur les composants.Les modules simples et les modules composés sont intrinsèquement réutilisables, ce qui
non seulement réduit la copie de code, mais plus important encore, permet aux bibliothèques de
composants (comme INET Framework, MiXiM, Castalia, etc.) d'exister.
Interfaces.Les interfaces de module et de canal peuvent être utilisées comme espace réservé là où normalement
un type de module ou de canal serait utilisé, et le type de module ou de canal concret est
déterminé au moment de l'établissement du réseau par un paramètre. Les types de modules
concrets doivent "implémenter" l'interface qu'ils peuvent remplacer. Par exemple, étant donné
un type de module composé nomméMobileHostcontient unmobilitésous-module du type
Mobilité (où Mobilitéest une interface de module), le type réel demobilitépeut être choisi parmi
les types de modules qui ont mis en œuvreIMobility (RandomWalkMobility, TurtleMobility, etc.)
Héritage.Les modules et les canaux peuvent être sous-classés. Les modules et canaux dérivés peuvent
ajouter de nouveaux paramètres, portes et (dans le cas des modules composés) de nouveaux sous-
modules et connexions. Ils peuvent définir des paramètres existants sur une valeur spécifique et
également définir la taille de porte d'un vecteur de porte. Cela permet, par exemple, de prendre une
GenericTCPCClientApp module et dériver un Application Client FTP de celui-ci en fixant certains
paramètres à une valeur fixe ; ou pour dériver unWebClientHost module composé d'un Hôte de base
module composé en ajoutant un WebClientApp sous-module et le connecter au hérité TCP sous-
module.
Paquets. Le langage NED dispose d'une structure de package de type Java, pour réduire le risque de
11
Manuel de simulation OMNeT++ – Le langage NED
conflits de noms entre différents modèles. NEDPATH (semblable à celui de Java CLASSPATH) a également
été introduit pour faciliter la spécification des dépendances entre les modèles de simulation.
Types intérieurs. Les types de canaux et les types de modules utilisés localement par un module composé peuvent être
défini dans le module composé, afin de réduire la pollution de l'espace de noms.
Annotations de métadonnées. Il est possible d'annoter les types de module ou de voie, les paramètres, les portes
et sous-modules en ajoutant des propriétés. Les métadonnées ne sont pas utilisées directement par le noyau de
simulation, mais elles peuvent contenir des informations supplémentaires pour divers outils, l'environnement
d'exécution ou même pour d'autres modules du modèle. Par exemple, la représentation graphique d'un module
(icône, etc.) ou la chaîne d'invite et l'unité de mesure (milliwatt, etc.) d'un paramètre sont déjà spécifiées en tant
qu'annotations de métadonnées.
REMARQUE: Le langage NED a considérablement changé dans la version 4.0. L'héritage, les interfaces, les
packages, les types internes, les annotations de métadonnées, les portes inout ont tous été ajoutés dans la version
4.0, ainsi que de nombreuses autres fonctionnalités. Étant donné que la syntaxe de base a également changé, les
anciens fichiers NED doivent être convertis à la nouvelle syntaxe. Il existe des outils automatisés à cette fin, de
sorte que l'édition manuelle n'est nécessaire que pour tirer parti des nouvelles fonctionnalités NED.
Le langage NED a une représentation arborescente équivalente qui peut être sérialisée en XML ; c'est-à-dire que
les fichiers NED peuvent être convertis en XML et inversement sans perte de données, y compris les
commentaires. Cela réduit la barrière pour la manipulation par programme des fichiers NED ; par exemple,
extraire des informations, refactoriser et transformer NED, générer NED à partir d'informations stockées dans
d'autres systèmes tels que des bases de données SQL, etc.
Dans cette section nous introduisons le langage NED via un exemple complet et raisonnablement concret : un
réseau de communication.
Notre réseau hypothétique est constitué de nœuds. Sur chaque nœud, il y a une application en cours d'exécution qui
génère des paquets à intervalles aléatoires. Les nœuds sont également des routeurs eux-mêmes. Nous supposons que
l'application utilise une communication basée sur les datagrammes, de sorte que nous pouvons omettre la couche de
transport du modèle.
3.2.1 Le réseau
Nous définirons d'abord le réseau, puis dans les sections suivantes, nous continuerons à définir les nœuds du
réseau.
Soit la topologie du réseau comme dans la Figure 3.1.
12
Manuel de simulation OMNeT++ – Le langage NED
sous-modules:
nœud1 : nœud ;
nœud2 : nœud ;
nœud3 : nœud ;
...
Connexions:
node1.port++ <--> {datarate=100Mbps;} <--> node2.port++; node2.port++
<--> {datarate=100Mbps;} <--> node4.port++; node4.port++ <-->
{datarate=100Mbps;} <--> node6.port++; . . .
Le code ci-dessus définit un type de réseau nommé Réseau. Notez que le langage NED utilise la syntaxe
familière des accolades et "//" pour indiquer les commentaires.
REMARQUE: Les commentaires dans NED rendent non seulement le code source plus lisible,
mais dans l'IDE OMNeT++, ils sont également affichés à divers endroits (info-bulles, aide au
contenu, etc.) et font partie de la documentation extraite des fichiers NED. Le système de
documentation NED, un peu commeJavaDoc ou Doxygène, sera décrit au chapitre 14.
Le réseau contient plusieurs nœuds, nommés nœud1, nœud2, etc. du type de module NED Nœud. Nous
définirons Nœud dans les sections suivantes.
La seconde moitié de la déclaration définit comment les nœuds doivent être connectés. La double flèche
signifie une connexion bidirectionnelle. Les points de connexion des modules sont appelés portes, et les
port++notation ajoute une nouvelle porte à laPort[]vecteur de porte. Les portes et les connexions seront
traitées plus en détail dans les sections 3.7 et 3.9. Les nœuds sont connectés à un canal qui a un débit de
données de 100 Mbps.
REMARQUE: Dans de nombreux autres systèmes, l'équivalent des portes OMNeT++ sont appeléesports.
Nous avons retenu le termeportailpour réduire les collisions avec d'autres utilisations du mot autrement
surchargéPort: port routeur, port TCP, port E/S, etc.
13
Manuel de simulation OMNeT++ – Le langage NED
Le code ci-dessus serait placé dans un fichier nomméNet6.ned.C'est une convention de mettre chaque définition
NED dans son propre fichier et de nommer le fichier en conséquence, mais ce n'est pas obligatoire de le faire.
On peut définir n'importe quel nombre de réseaux dans les fichiers NED, et pour chaque simulation,
l'utilisateur doit spécifier le réseau à configurer. La façon habituelle de spécifier le réseau est de mettre le
réseau option dans la configuration (par défaut l'optionomnetpp.ini file):
[Général]
réseau = Réseau
Il est fastidieux de devoir répéter le débit de données pour chaque connexion. Heureusement, NED fournit
une solution pratique : on peut créer un nouveau type de canal qui encapsule le paramètre de débit de
données, et ce type de canal peut être défini à l'intérieur du réseau afin qu'il n'encombre pas l'espace de
noms global.
Le réseau amélioré ressemblera à ceci :
//
// Un réseau
//
réseau Réseau
{
les types:
canaliser Cs'étendned.DatarateChannel {
débit de données = 100 Mbps ;
}
sous-modules:
nœud1 : nœud ;
nœud2 : nœud ;
nœud3 : nœud ;
...
Connexions:
nœud1.port++ <--> C <--> nœud2.port++ ;
node2.port++ <--> C <--> node4.port++;
nœud4.port++ <--> C <--> nœud6.port++ ; . . .
Les sections suivantes couvriront les concepts utilisés (types internes, canaux, Canal de débit de données
type intégré, héritage) en détail.
Les modules simples sont les blocs de construction de base pour d'autres modules (composés), désignés
par le Facilemot-clé. Tout comportement actif dans le modèle est encapsulé dansFacilemodules. Le
comportement est défini avec une classe C++ ; Les fichiers NED ne déclarent que l'interface visible de
l'extérieur du module (portes, paramètres).
Dans notre exemple, nous pourrions définir Nœud sous forme de module simple. Cependant, ses fonctionnalités sont
assez complexes (génération de trafic, routage, etc.), il est donc préférable de l'implémenter avec plusieurs plus petits
14
Manuel de simulation OMNeT++ – Le langage NED
types de modules simples que nous allons assembler dans un module composé. Nous aurons un module simple pour la
génération de trafic (application),un pour le routage (Routage),et un pour la mise en file d'attente des paquets à envoyer
(File d'attente).Par souci de brièveté, nous omettons les corps de ces deux derniers dans le code ci-dessous.
FacileApplication
{
paramètres:
entier destAddress ;
...
@display("i=bloc/navigateur"); portes:
saisir dans;
sortir en dehors;
FacileRoutage
{
...
}
FacileFile d'attente
{
...
}
Par convention, les déclarations de module simples ci-dessus vont dans leApp.ned, Routage.nedet
Queue.ned files.
REMARQUE: Notez que les noms de type de module (Application, routage, file d'attente)commencent par une
lettre majuscule, et les noms de paramètres et de portes commencent par une minuscule - c'est la convention de
nommage recommandée. La capitalisation est importante car la langue est sensible à la casse.
Maintenant, nous pouvons assemblerApplication, Routageet File d'attentedans le module composéNœud. Un module
composé peut être considéré comme une « boîte en carton » qui regroupe d'autres modules dans une unité plus grande,
qui peut en outre être utilisée comme élément de base pour d'autres modules ; les réseaux sont aussi une sorte de
module composé.
moduleNœud
{
15
Manuel de simulation OMNeT++ – Le langage NED
paramètres:
entieradresse;
@display("i=misc/node_vs,gold"); portes:
inoutPort[];
sous-modules:
application : application ;
routage : Routage ;
file d'attente[taille de(port)] : file
d'attente ; Connexions:
routage.localOut --> app.in ;
routage.localIn <-- app.out ; pour
je=0..taille de(port)-1 { routing.out[i] -->
queue[i].in ; routage.in[i] <-- queue[i].out ;
queue[i].line <--> port[i] ;
}
}
Les modules composés, comme les modules simples, peuvent avoir des paramètres et des portes. Notre
Nœud module contient unadresseparamètre, plus unporte vecteurde taille indéterminée, nomméPort.La
taille réelle du vecteur de porte sera déterminée implicitement par le nombre de voisins lorsque nous
créerons un réseau à partir de nœuds de ce type. Le type dePort[]estinout,qui permet des connexions
bidirectionnelles.
Les modules qui composent le module composé sont répertoriés soussous-modules. NotreNœud le type
de module composé a unapplicationet unroutagesous-module, plus unfile d'attente[]vecteur de sous-
modulequi contient unFile d'attentemodule pour chaque port, comme spécifié par [sizeof(port)]. (Il est légal
de se référer à [taillede(port)]car le réseau est construit dans l'ordre descendant, et le nœud est déjà créé et
connecté au niveau du réseau lorsque sa structure de sous-module est construite.)
Dans leConnexionssection, les sous-modules sont connectés les uns aux autres et au module parent. Les flèches
simples sont utilisées pour connecter les portes d'entrée et de sortie, et les doubles flèches connectent les portes
d'entrée, et unpourboucle est utilisée pour connecter leroutagemodule à chacunfile d'attentemodule, et de
connecter la liaison sortante/entrante (ligneporte) de chaque file d'attente au correspondant
16
Manuel de simulation OMNeT++ – Le langage NED
3.2.5 Assemblage
Nous avons créé les définitions NED pour cet exemple, mais comment sont-elles utilisées par OMNeT++ ?
Lorsque le programme de simulation est lancé, il charge les fichiers NED. Le programme doit déjà contenir
les classes C++ qui implémentent les modules simples nécessaires,Application, Routageet File d'attente;
leur code C++ fait partie de l'exécutable ou est chargé à partir d'une bibliothèque partagée. Le programme
de simulation charge également la configuration (omnetpp.ini),et en déduit que le modèle de simulation à
exécuter est leRéseauréseau. Ensuite, le réseau est instancié pour la simulation.
Le modèle de simulation est construit selon un mode de précommande descendant. Cela signifie qu'à partir d'un module
système vide, tous les sous-modules sont créés, leurs paramètres et tailles de vecteurs de porte sont affectés, et ils sont
entièrement connectés avant que les éléments internes du sous-module ne soient construits.
***
Dans les sections suivantes, nous passerons en revue les éléments du langage NED et les examinerons plus
en détail.
Les modules simples sont les composants actifs du modèle. Les modules simples sont définis avec les
Facilemot-clé.
Un exemple de module simple :
FacileFile d'attente
{
paramètres:
entiercapacité;
@display("i=bloc/file"); portes:
saisir dans;
sortir en dehors;
Les deuxparamètreset portesLes sections sont facultatives, c'est-à-dire qu'elles peuvent être omises s'il n'y a pas
de paramètre ou de porte. De plus, leparamètresle mot-clé lui-même est également facultatif ; il peut être omis
même s'il existe des paramètres ou des propriétés.
Notez que la définition NED ne contient aucun code pour définir le fonctionnement du module : cette
partie est exprimée en C++. Par défaut, OMNeT++ recherche les classes C++ du même nom que le
type NED (donc ici,File d'attente).
On peut spécifier explicitement la classe C++ avec le @classerpropriété. Les classes avec des qualificateurs d'espace de
noms sont également acceptées, comme illustré dans l'exemple suivant qui utilise lemalib :: File d'attente classer:
17
Manuel de simulation OMNeT++ – Le langage NED
FacileFile d'attente
{
paramètres:
entiercapacité;
@class(mylib::Queue);
@display("i=bloc/file"); portes:
saisir dans;
sortir en dehors;
S'il existe plusieurs modules dont les classes d'implémentation C++ sont dans le même espace de noms,
une meilleure alternative à @classerest le @espace de noms propriété. L'espace de noms C++ donné avec
@espace de nomssera ajouté au nom de la classe normale. Dans l'exemple suivant, les classes C++ seront
mylib::App, mylib::Routeur et malib :: File d'attente :
@namespace(mylib);
Facile Application {
...
}
Facile Routeur {
...
}
Le @espace de noms La propriété peut non seulement être spécifiée au niveau du fichier comme dans l'exemple ci-dessus, mais
également pour les packages. Lorsqu'il est placé dans un fichier appelépackage.ned, l'espace de noms s'appliquera à tous les
composants de ce package et ci-dessous.
Les classes d'implémentation C++ doivent être sous-classées à partir de cModuleSimple classe de
bibliothèque ; le chapitre 4 de ce manuel décrit en détail comment les écrire.
Les modules simples peuvent être étendus (ou spécialisés) via le sous-classement. La motivation du sous-
classement peut être de définir certains paramètres ouverts ou tailles de porte à une valeur fixe (voir 3.6 et 3.7),
ou de remplacer la classe C++ par une autre. Maintenant, par défaut, le type de module NED dérivé sera hériter la
classe C++ à partir de sa base, il est donc important de se rappeler que vous devez écrire @classer si vous voulez
qu'il utilise la nouvelle classe.
L'exemple suivant montre comment spécialiser un module en définissant un paramètre sur une
valeur fixe (et en laissant la classe C++ inchangée) :
Facile File d'attente
{
entier capacité;
...
}
capacité = 10 ;
}
18
Manuel de simulation OMNeT++ – Le langage NED
Dans l'exemple suivant, l'auteur a écrit unFile d'attente de prioritéclasse C++ et veut avoir un type NED
correspondant, dérivé deFile d'attente.Cependant, cela ne fonctionne pas comme prévu :
FacileFile d'attente de priorités'étendFile d'attente// tort! utilise toujours la classe Queue C++ {
@class(PriorityQueue);
}
Un module composé regroupe d'autres modules dans une unité plus grande. Un module composé peut avoir des portes
et des paramètres comme un module simple, mais aucun comportement actif ne lui est associé.1
REMARQUE: Lorsqu'il est tentant d'ajouter du code à un module composé, encapsulez le code dans
un module simple et ajoutez-le en tant que sous-module.
Une déclaration de module composé peut contenir plusieurs sections, toutes facultatives :
moduleHéberger
{
les types:
...
paramètres:
...
portes:
...
sous-modules:
...
Connexions:
...
}
Les modules contenus dans un module composé sont appelés sous-modules, et ils sont listés dans le sous-
modules section. On peut créer des tableaux de sous-modules (c'est-à-dire des vecteurs de sous-modules), et le
type de sous-module peut provenir d'un paramètre.
Les connexions sont répertoriées sous leConnexionspartie de la déclaration. On peut créer des connexions
en utilisant des constructions de programmation simples (boucle, conditionnel). Le comportement de
connexion peut être défini en associant un canal à la connexion ; le type de canal peut également provenir
d'un paramètre.
Les types de module et de voie utilisés uniquement localement peuvent être définis dans leles typessection en tant que
types internes, afin qu'ils ne polluent pas l'espace de noms.
1Bien que la classe C++ d'un module composé puisse être remplacée par le @classerpropriété, il s'agit d'une fonctionnalité qui ne
devrait probablement jamais être utilisée. Encapsulez le code dans un module simple et ajoutez-le en tant que sous-module.
19
Manuel de simulation OMNeT++ – Le langage NED
Les modules composés peuvent être étendus via le sous-classement. L'héritage peut également ajouter de nouveaux
sous-modules et de nouvelles connexions, pas seulement des paramètres et des portes. De plus, on peut se référer à des
sous-modules hérités, à des types hérités, etc. Ce qui n'est pas possible, c'est de "déshériter" ou de modifier des sous-
modules ou des connexions.
Dans l'exemple suivant, nous montrons comment assembler des protocoles communs dans un "stub" pour les hôtes sans fil, et
ajouter des agents utilisateurs via des sous-classes.2
moduleWirelessHostBase {
portes:
saisir radioIn ;
sous-modules:
tcp : TCP ;
IP : IP ;
réseau local sans fil : Ieee80211 ;
Connexions:
tcp.ipOut --> ip.tcpIn ; tcp.ipIn <--
ip.tcpOut ; ip.nicOut++ -->
wlan.ipIn ; ip.nicIn++ <--
wlan.ipOut ; wlan.radioIn <--
radioIn ;
}
sous-modules:
WebAgent : WebAgent ;
Connexions:
webAgent.tcpOut --> tcp.appIn++ ;
webAgent.tcpIn <-- tcp.appOut++ ;
}
leHôte sans fil module composé peut encore être étendu, par exemple avec un port Ethernet :
portes:
inoutethg ;
sous-modules:
eth : EthernetNic ;
Connexions:
ip.nicOut++ --> eth.ipIn ; ip.nicIn+
+ <-- eth.ipOut ; eth.phy <--> ethg;
2Les types de module, les noms de porte, etc. utilisés dans l'exemple sont fictifs et ne sont pas basés sur un cadre de modèle réel basé sur
OMNeT ++
20
Manuel de simulation OMNeT++ – Le langage NED
3.5 Canaux
Les canaux encapsulent les paramètres et le comportement associés aux connexions. Les canaux sont
comme de simples modules, dans le sens où il y a des classes C++ derrière eux. Les règles pour trouver la
classe C++ pour un type de canal NED sont les mêmes que pour les modules simples : le nom de classe par
défaut est le nom du type NED sauf s'il y a un @classerpropriété (@espace de noms est également
reconnu), et la classe C++ est héritée lorsque le canal est sous-classé.
Ainsi, le type de canal suivant s'attendrait à unCanal personnaliséClasse C++ à présenter :
canaliser Canal personnalisé// nécessite une classe CustomChannel C++ {
La différence pratique par rapport aux modules est que l'on a rarement besoin d'écrire une classe C++
de canal personnalisée car il existe des types de canaux prédéfinis à partir desquels on peut sous-
classer, héritant de leur code C++. Les types prédéfinis sont :ned.IdealChannel, ned.DelayChannelet
ned.DatarateChannel. ("ne"est le nom du package ; on peut s'en débarrasser en important les types
avec leimport ned.*directif. Les packages et les importations sont décrits dans la section 3.14.)
IdéalChanneln'a pas de paramètres et laisse passer tous les messages sans délai ni effet secondaire. Une
connexion sans objet de voie et une connexion avec unIdéalChannelse comporter de la même manière.
Toujours,IdéalChannela ses utilisations, par exemple lorsqu'un objet canal est requis pour qu'il puisse
transporter une nouvelle propriété ou un nouveau paramètre qui va être lu par d'autres parties du modèle
de simulation.
DelayChannela deux paramètres :
• désactivéeest un paramètre booléen dont la valeur par défaut estfaux;lorsqu'il est réglé survrai,l'objet canal
supprimera tous les messages.
Canal de débit de donnéesa quelques paramètres supplémentaires par rapport àRetard Canal :
• débit de donnéesest undoubleparamètre qui représente le débit de données du canal. Les valeurs doivent
être spécifiées en bits par seconde ou ses multiples comme unité (bps, kbps, Mbps, Gbps, etc.) Zéro est
traité spécialement et se traduit par une durée de transmission nulle, c'est-à-dire qu'il représente une
bande passante infinie. Zéro est également la valeur par défaut. Le débit de données est utilisé pour
calculer la durée de transmission des paquets.
• breet parsignifie Bit Error Rate et Packet Error Rate, et permettent une modélisation basique des
erreurs. Ils s'attendent à undoubledans le [0, 1]intervalle. Lorsque le canal décide (sur la base de
nombres aléatoires) qu'une erreur s'est produite lors de la transmission d'un paquet, il définit un
indicateur d'erreur dans l'objet paquet. Le module récepteur est censé vérifier le drapeau et rejeter le
paquet comme étant corrompu s'il est défini. Le défautbreet parsont nuls.
REMARQUE: Il n'y a pas de paramètre de canal qui précise si le canal délivre l'objet message au
module destinataire en fin ou en début de réception ; cela est décidé par le code C++ du module
simple cible. Voir lesetDeliverOnReception-Start() méthode de cGate.
L'exemple suivant montre comment créer un nouveau type de canal en se spécialisant Datarate-
Channel :
21
Manuel de simulation OMNeT++ – Le langage NED
REMARQUE: Les trois types de canaux intégrés sont également utilisés pour les connexions où le type de canal
n'est pas explicitement spécifié.
On peut ajouter des paramètres et des propriétés aux canaux via le sous-classement et modifier ceux qui
existent déjà. Dans l'exemple suivant, nous introduisons le calcul basé sur la distance du délai de
propagation :
canaliser DatarateChannel2 s'étendned.DatarateChannel {
doubledistance @unité(m);
retard = cette.distance / 200000km * 1s;
}
Les paramètres sont principalement destinés à être lus par la classe C++ sous-jacente, mais de nouveaux paramètres peuvent
également être ajoutés en tant qu'annotations à utiliser par d'autres parties du modèle. Par exemple, unCoût Le paramètre peut
être utilisé pour les décisions de routage dans le module de routage, comme illustré dans l'exemple ci-dessous. L'exemple
montre également des annotations utilisant des propriétés (@colonne vertébrale).
@colonne vertébrale;
doublecoût =défaut(1);
}
3.6 Paramètres
Les paramètres sont des variables qui appartiennent à un module. Les paramètres peuvent être utilisés dans la
construction de la topologie (nombre de nœuds, etc.) et pour fournir une entrée au code C++ qui implémente des
modules et des canaux simples.
Les paramètres peuvent être de typedouble, entier, bourdonner, chaîne de caractèreset XML; ils peuvent également être
déclarés volatil. Pour les types numériques, une unité de mesure peut également être spécifiée (@unitépropriété), pour
augmenter la sécurité du type.
Les paramètres peuvent obtenir leur valeur à partir des fichiers NED ou de la configuration (omnetpp.ini).Une
valeur par défaut peut également être donnée (défaut(...)),qui est utilisé si le paramètre n'est pas affecté
autrement.
L'exemple suivant montre un module simple qui a cinq paramètres, dont trois ont des valeurs par
défaut :
FacileApplication
{
paramètres:
chaîne de caractèresprotocole; // protocole à utiliser : "UDP" / "IP" / "ICMP" / ... // adresse de
entierdestAddress ; destination
22
Manuel de simulation OMNeT++ – Le langage NED
Les paramètres peuvent obtenir leurs valeurs de plusieurs manières : à partir du code NED, à partir de la
configuration (omnetpp.ini),ou même, interactivement de l'utilisateur. NED permet d'assigner des paramètres à
plusieurs endroits : dans les sous-classes via l'héritage ; dans les définitions de sous-module et de connexion où le
type NED est instancié ; et dans les réseaux et les modules composés qui contiennent directement ou
indirectement le sous-module ou la connexion correspondante.
Par exemple, on pourrait spécialiser ce qui précède Applicationtype de module via héritage avec la définition
suivante :
FacilePingApp s'étendApplication {
paramètres:
protocole = "ICMP/ECHO"
sendInterval = défaut(1s);
longueurpaquet = défaut(64 octets);
}
Cette définition définit la protocole paramètre à une valeur fixe ("ICMP/ECHO"), et modifie les valeurs
par défaut du sendInterval et paquetLongueur paramètres. protocole est maintenant enfermé dans
PingApp, sa valeur ne peut pas être modifiée par d'autres sous-classes ou par d'autres moyens.
sendInterval et paquetLongueur ne sont toujours pas affectés ici, seules leurs valeurs par défaut ont
été écrasées.
Voyons maintenant la définition d'un Héberger module composé qui utilise PingApp comme sous-module :
moduleHéberger
{
sous-modules:
ping : PingApp {
paquetLength = 128B ; // ping toujours avec des paquets de 128 octets
}
...
}
Cette définition définit la paquetLongueur paramètre à une valeur fixe. Il est maintenant codé en dur que Hébergers
envoyer des paquets ping de 128 octets ; ce paramètre ne peut pas être modifié à partir de NED ou de la configuration.
Il est non seulement possible de définir un paramètre à partir du module composé qui contient le sous-module, mais
également à partir de modules plus haut dans l'arborescence des modules. Un réseau qui emploie plusieurs Héberger
modules pourraient être définis comme ceci :
23
Manuel de simulation OMNeT++ – Le langage NED
réseau Réseau
{
sous-modules:
hôte[100] : Hôte {
ping.timeToLive = défaut(3);
ping.destAddress = défaut(0);
}
...
}
Le paramétrage peut également être placé dans le paramètresbloc du module composé parent, qui offre une
flexibilité supplémentaire. La définition suivante configure les hôtes de sorte que la moitié d'entre eux envoie un
ping à l'hôte n°50 et l'autre moitié envoie un ping à l'hôte n°0 :
réseau Réseau
{
paramètres:
hôte[*].ping.timeToLive = défaut(3);
hôte[0..49].ping.destAddress = défaut(50);
hôte[50..].ping.destAddress = défaut(0);
sous-modules:
hôte[100] : hôte ;
...
}
Notez l'utilisation de l'astérisque pour correspondre à n'importe quel index et .. pour correspondre aux plages d'index.
S'il y avait un certain nombre d'hôtes individuels au lieu d'un vecteur de sous-module, la définition du réseau
pourrait ressembler à ceci :
réseau Réseau
{
paramètres:
hôte*.ping.timeToLive = défaut(3);
hôte{0..49}.ping.destAddress = défaut(50);
hôte{50..}.ping.destAddress = défaut(0);
sous-modules:
host0 : hôte ;
hôte1 : hôte ;
hôte2 : hôte ;
...
host99 : hôte ;
}
Un astérisque correspond à toute sous-chaîne ne contenant pas de point, et un .. dans une paire d'accolades
correspond à un nombre naturel intégré dans une chaîne.
Dans la plupart des affectations que nous avons vues ci-dessus, le côté gauche du signe égal contenait un point et
souvent également un caractère générique (astérisque ou plage numérique) ; nous appelons ces devoirsaffectations de
modèles ou missions profondes.
Il y a un autre caractère générique qui peut être utilisé dans les affectations de modèle, et c'est le double astérisque ; il
correspond à n'importe quelle séquence de caractères, y compris les points, de sorte qu'il peut correspondre à plusieurs
éléments de chemin. Un exemple:
24
Manuel de simulation OMNeT++ – Le langage NED
réseau Réseau
{
paramètres:
* * . timeToLive = défaut(3);
* * . adresse_dest = défaut(0);
sous-modules:
host0 : hôte ;
hôte1 : hôte ;
...
}
Notez que certaines affectations dans les exemples ci-dessus ont modifié les valeurs par défaut, tandis que d'autres définissent
des paramètres sur des valeurs fixes. Les paramètres qui n'ont pas reçu de valeur fixe dans les fichiers NED peuvent être affectés
à partir de la configuration (omnetpp.ini).
IMPORTANT: Une valeur non par défaut attribuée à partir de NED ne peut pas être écrasée ultérieurement dans
NED ou à partir de fichiers ini ; il devient "codé en dur" en ce qui concerne les fichiers ini et l'utilisation de NED. En
revanche, les valeurs par défaut peuvent être écrasées.
Un paramètre peut être affecté dans la configuration en utilisant une syntaxe similaire à celle des affectations de
modèles NED (en fait, il serait plus exact historiquement de le dire dans l'autre sens, que les affectations de
modèles NED utilisent une syntaxe similaire aux fichiers ini) :
* * . ping.sendInterval = 500ms
Ou si l'on est certain que seuls les modules ping ont sendInterval paramètres, ce qui suit suffira :
S'il n'y a pas d'affectation pour un paramètre dans NED ou dans le fichier ini, la valeur par défaut (donnée
avec =défaut(...)dans NED) seront appliqués implicitement. S'il n'y a pas de valeur par défaut, l'utilisateur
sera invité, à condition que le programme de simulation soit autorisé à le faire ; sinon il y aura une erreur.
(Le mode interactif est généralement désactivé pour les exécutions par lots où il ferait plus de mal que de
bien.)
Il est aussi possible d'appliquer explicitement la valeur par défaut (cela peut parfois être utile) :
Enfin, on peut explicitement demander au simulateur d'inviter l'utilisateur de manière interactive pour la valeur
(là encore, à condition que l'interactivité soit activée, sinon cela entraînera une erreur) :
* * . sendInterval = demander
25
Manuel de simulation OMNeT++ – Le langage NED
REMARQUE: Comment décider d'assigner un paramètre depuis NED ou depuis un fichier ini ?
L'avantage des fichiers ini est qu'ils permettent une séparation plus nette desmaquetteet
expériences. Les fichiers NED (avec le code C++) sont considérés comme faisant partie du modèle
et comme étant plus ou moins constants. Les fichiers ini, en revanche, servent à expérimenter le
modèle en l'exécutant plusieurs fois avec différents paramètres. Ainsi, les paramètres censés
changer (ou avoir un sens à changer) pendant l'expérimentation doivent être placés dans des
fichiers ini.
3.6.2 Expressions
Les valeurs des paramètres peuvent être données avec des expressions. Les expressions du langage NED ont une
syntaxe de type C, avec quelques variations sur les noms d'opérateurs : les XOR binaires et logiques sont # et ##,
tandis que ˆa été réaffecté àle pouvoir deplutôt. L'opérateur + effectue la concaténation de chaînes ainsi que
l'addition numérique. Les expressions peuvent utiliser diverses fonctions numériques, de chaîne, stochastiques et
autres (fabs(), toUpper(), uniform(), erlang_k(),etc.).
REMARQUE: La liste des fonctions NED se trouve dans l'annexe D. L'utilisateur peut également étendre NED avec
de nouvelles fonctions.
Les expressions peuvent faire référence aux paramètres de module, au vecteur de porte et aux tailles de vecteur de
module (en utilisant le taille de) et l'indice du module courant dans un vecteur de sous-module (indice).
Les expressions peuvent faire référence à des paramètres du module composé en cours de définition,
du module courant (avec lacette.préfixe), et aux paramètres des sous-modules déjà définis, avec la
syntaxesubmodule.parametername (ou sous-module[index].nomparamètre).
3.6.3 volatile
En raison de lvolatilmodificateur, l'implémentation C++ du module de file d'attente est censée relire letemps de
serviceparamètre chaque fois qu'une valeur est nécessaire ; c'est-à-dire pour chaque travail desservi. Ainsi, si
temps de servicese voit attribuer une expression comme uniforme(0.5s, 1.5s), chaque travail aura un temps de
service différent et aléatoire. Pour mettre en évidence cet effet, voici comment on peut avoir un paramètre variant
dans le temps en exploitant lesimHeure() Fonction NED qui renvoie le temps de simulation actuel :
* * . serviceTime = simTime()<1000s ? 1s : 2s# file d'attente qui ralentit après 1000s
En pratique, les paramètres volatils sont généralement utilisés comme source configurable de nombres aléatoires pour
les modules.
26
Manuel de simulation OMNeT++ – Le langage NED
REMARQUE: Cela ne signifie pas qu'un paramètre non volatil ne peut pas se voir attribuer une valeur
aléatoire telle que uniforme (0,5 s, 1,5 s). C'est possible, mais cela aurait un effet totalement différent :
la simulation utiliserait un temps de service constant, disons 1.2975367s, choisi au hasard au début de
la simulation.
3.6.4 Unités
On peut déclarer un paramètre pour avoir une unité de mesure associée, en ajoutant le @unité
propriété. Un exemple:
FacileApplication
{
paramètres:
double volatilsendInterval @unité(s) =défaut(exponentiel(350ms)); entier volatil
paquetLength @unit(byte) =défaut(4Ko);
...
}
Le @unités) et @unité (octet) les déclarations spécifient l'unité de mesure du paramètre. Les valeurs affectées aux
paramètres doivent avoir la même unité ou une unité compatible, c'est-à-dire @unités) accepte les millisecondes,
les nanosecondes, les minutes, les heures, etc., et @unité (octet) accepte également les kilo-octets, mégaoctets,
etc.
REMARQUE: La liste des unités acceptées par OMNeT++ est listée en annexe, voir A.5.9. Unités inconnues (
bogomips, etc.) peuvent également être utilisés, mais il n'y a pas de conversions pour eux, c'est-à-dire que
les préfixes décimaux ne seront pas reconnus.
Le runtime OMNeT++ effectue une vérification unitaire complète et rigoureuse des paramètres pour garantir la « sécurité
unitaire » des modèles. Les constantes doivent toujours inclure l'unité de mesure.
Le @unitéLa propriété d'un paramètre ne peut pas être ajoutée ou remplacée dans les sous-classes ou dans les
déclarations de sous-module.
Parfois, les modules ont besoin de structures de données complexes en entrée, ce qui ne peut pas être bien fait avec les
paramètres de module. Une solution consiste à placer les données d'entrée dans un fichier de configuration
personnalisé, à transmettre le nom du fichier au module dans un paramètre de chaîne et à laisser le module lire et
analyser le fichier.
C'est un peu plus facile si la configuration utilise la syntaxe XML, car OMNeT++ contient un support intégré pour
les fichiers XML. À l'aide d'un analyseur XML (LibXML2 ou Expat), OMNeT++ lit et valide le fichier par DTD (si le
document XML contient un DOCTYPE), met le fichier en cache (afin que les références à celui-ci à partir de
plusieurs modules entraînent le chargement du fichier une seule fois) , permet la sélection de parties du
document à l'aide d'une notation de sous-ensemble XPath et présente le contenu dans une arborescence d'objets
de type DOM.
Cette capacité est accessible via le type de paramètre NED XML, et le xmldoc() une fonction. On peut
pointerXML-type paramètres de module à un fichier XML spécifique (ou à un élément à l'intérieur
d'un fichier XML) via lexmldoc() une fonction. On peut attribuerXMLparamètres de NED et de
omnetpp.ini.
L'exemple suivant déclare unXMLparamètre et lui attribue un fichier XML. Le nom du fichier s'entend
comme étant relatif au répertoire de travail.
27
Manuel de simulation OMNeT++ – Le langage NED
FacileGénérateur de trafic {
paramètres:
XMLprofil;
portes:
sortiren dehors;
}
moduleNœud {
sous-modules:
TrafGen1 : TrafGen {
profil =xmldoc("data.xml");
}
...
}
xmldoc() permet aussi de sélectionner un élémentdansun fichier XML. Dans le cas où l'on a un modèle qui
contient de nombreux modules nécessitant une entrée XML, cette fonctionnalité permet à l'utilisateur de se
débarrasser des innombrables petits fichiers XML en les agrégeant dans un seul fichier XML. Par exemple,
le fichier XML suivant contient deux profils identifiés avec les IDgen1et gen2:
<?xml>
<racine>
<identifiant de profil="gen1">
<param>3</param>
<param>5</param>
</profil>
<identifiant de profil="gen2">
<param>9</param>
</profil>
</racine>
Et on peut assigner chaque profil à un sous-module correspondant en utilisant une expression de type
XPath :
moduleNœud {
sous-modules:
TrafGen1 : TrafGen {
profil =xmldoc("all.xml", "/root/profile[@id='gen1']");
}
TrafGen2 : TrafGen {
profil =xmldoc("all.xml", "/root/profile[@id='gen2']");
}
}
Il est également possible de créer un document XML à partir d'une constante chaîne, en utilisant laxml()une
fonction. Ceci est particulièrement utile pour créer une valeur par défaut pourXMLparamètres. Un exemple:
FacileGénérateur de trafic {
paramètres:
XMLprofil =XML("<racine/>");// document vide par défaut
...
}
lexml()fonction, commexmldoc(), prend également en charge un deuxième paramètre XPath facultatif pour sélectionner une
sous-arborescence.
28
Manuel de simulation OMNeT++ – Le langage NED
3.7 Portes
Les portes sont les points de connexion des modules. OMNeT++ a trois types de portes :saisir, sortir et inout, cette
dernière étant essentiellement une porte d'entrée et une porte de sortie collées ensemble.
Une porte, qu'elle soit d'entrée ou de sortie, ne peut être connectée qu'à une seule autre porte. (Pour les portes
de modules composés, cela signifie une connexion "extérieure" et une "intérieure".) Il est possible, bien que
généralement déconseillé, de connecter séparément les côtés entrée et sortie d'une porte inout (voir section 3.9).
On peut créer des portes simples et des vecteurs de porte. La taille d'un vecteur de porte peut être donnée entre
crochets dans la déclaration, mais il est également possible de le laisser ouvert en écrivant simplement une paire
de crochets vides ("[]").
Lorsque la taille du vecteur de porte est laissée ouverte, on peut toujours la spécifier plus tard, lors de la sous-
classe du module ou lors de l'utilisation du module pour un sous-module dans un module composé. Cependant, il
n'a pas besoin d'être précisé car on peut créer des connexions avec leportail++ opérateur qui développe
automatiquement le vecteur de porte.
La taille de la porte peut être interrogée à partir de diverses expressions NED avec letaille de()opérateur.
NED exige normalement que toutes les portes soient connectées. Pour assouplir cette exigence, on peut annoter
les portes sélectionnées avec le @lâchepropriété, qui désactive la vérification de la connectivité pour cette porte.
De plus, les portes d'entrée qui existent uniquement pour que le module puisse recevoir des messages via
envoyer-Direct() (voir 4.7.5) doit être annoté avec @entrée directe.Il est également possible de désactiver la
vérification de la connectivité pour toutes les portes d'un module composé en spécifiant leautoriser non
connectémot-clé dans la section des connexions du module.
Voyons quelques exemples.
Dans l'exemple suivant, leClassificateurmodule a une entrée pour recevoir des travaux, qu'il enverra à
l'une des sorties. Le nombre de sorties est déterminé par un paramètre du module :
FacileClassificateur {
paramètres:
entiernumCatégories ;
portes:
saisir dans;
sortir out[numCatégories] ;
}
Ce qui suitÉviermodule a également sondans[]porte définie comme un vecteur, afin qu'elle puisse être connectée
à plusieurs modules :
FacileÉvier {
portes:
saisir dans[];
}
Les lignes suivantes définissent un nœud pour construire une grille carrée. Les portes autour des
bords de la grille devraient rester non connectées, d'où le @lâcheannotation:
FacileNoeudGrille {
portes:
inoutvoisin[4] @loose ;
}
WirelessNodeci-dessous est censé recevoir des messages (transmissions radio) via l'envoi direct, donc son
radioInla porte est marquée d'un @entrée directe.
29
Manuel de simulation OMNeT++ – Le langage NED
Dans l'exemple suivant, nous définissonsTreeNodecomme ayant des portes pour connecter n'importe quel nombre d'enfants,
puis sous-classez-le pour obtenir unBinaryTreeNodepour régler la taille de la porte sur deux :
FacileArbreNoeud {
portes:
inout parent;
inout enfants[];
}
FacileBinaryTreeNodes'étendArbreNoeud {
portes:
enfants[2] ;
}
Un exemple pour définir la taille du vecteur de porte dans un sous-module, en utilisant le mêmeTreeNodetype de module
comme ci-dessus :
moduleArbreBinaire {
sous-modules:
nœuds[31] : TreeNode {
portes:
enfants[2] ;
}
Connexions:
...
}
3.8 Sous-modules
Les modules dont un module composé est composé sont appelés ses sous-modules. Un sous-module a un nom,
et c'est une instance d'un type de module composé ou simple. Dans la définition NED d'un sous-module, ce type
de module est généralement donné de manière statique, mais il est également possible de spécifier le type avec
une expression de chaîne. (Cette dernière caractéristique,types de sous-modules paramétriques, sera discuté
dans la section 3.11.1.)
NED prend également en charge les tableaux de sous-modules (vecteurs) et les sous-modules conditionnels. La taille du vecteur de sous-
module, contrairement à la taille du vecteur de porte, doit toujours être spécifiée et ne peut pas être laissée ouverte comme avec les
portes.
Il est possible d'ajouter de nouveaux sous-modules à un module composé existant via le sous-classement ;
cela a été décrit dans la section 3.4.
La syntaxe de base des sous-modules est illustrée ci-dessous :
moduleNœud
{
sous-modules:
routage : Routage ; // un sous-module
file d'attente[taille de(port)] : file d'attente ;// vecteur de sous-module
30
Manuel de simulation OMNeT++ – Le langage NED
...
}
Comme déjà vu dans les exemples de code précédents, un sous-module peut également avoir un bloc d'accolades
comme corps, où l'on peut affecter des paramètres, définir la taille des vecteurs de porte et ajouter/modifier des
propriétés comme la chaîne d'affichage (@affichage). Il n'est pas possible d'ajouter de nouveaux paramètres et portes.
Les chaînes d'affichage spécifiées ici seront fusionnées avec la chaîne d'affichage du type pour obtenir la
chaîne d'affichage effective. L'algorithme de fusion est décrit au chapitre 8.
moduleNœud
{
portes:
inout Port[];
sous-modules:
routage : Routage {
paramètres: // ce mot clé est facultatif
table de routage = "table de routage.txt" ; // attribuer un paramètre
portes:
dans[taille de(Port)]; // définir la taille du vecteur de porte en
dehors[taille de(Port)];
}
file d'attente[taille de(port)] : file d'attente {
@display("t=identifiant file d'attente $id"); // modifier la chaîne d'affichage
identifiant = 1000+indice; // utiliser l'index de sous-module pour générer différents identifiants
}
Connexions:
...
}
moduleHéberger
{
paramètres:
bourdonneravecTCP =défaut(vrai);
sous-modules:
tcp : TCPsiavecTCP ; . . .
Notez qu'avec les vecteurs de sous-module, la définition d'une taille de vecteur nulle peut être utilisée comme alternative
à la siétat.
31
Manuel de simulation OMNeT++ – Le langage NED
3.9 Connexions
Les connexions sont définies dans leConnexionssection des modules composés. Les connexions ne peuvent pas
s'étendre sur plusieurs niveaux de hiérarchie ; on peut connecter deux portes de sous-module, une porte de sous-
module et "l'intérieur" des portes du module parent (composé), ou deux portes du module parent (bien que cela
soit rarement utile), mais il n'est pas possible de se connecter à n'importe quelle porte à l'extérieur le module
parent, ou à l'intérieur des sous-modules composés.
Les portes d'entrée et de sortie sont reliées par une flèche normale et les portes d'entrée par une double
flèche "<-->". Pour connecter les deux portes avec un canal, utilisez deux flèches et placez la spécification du
canal entre les deux. La même syntaxe est utilisée pour ajouter des propriétés telles que @affichageà la
connexion.
Quelques exemples ont déjà été montrés dans la section NED Quickstart (3.2); voyons un peu plus.
Il a été mentionné qu'une porte d'entrée est essentiellement une porte d'entrée et une porte de sortie collées
ensemble. Ces sous-portes peuvent également être adressées (et connectées) individuellement si nécessaire,
comme port$iet port$o (ou pour les portes vectorielles, commeport$i[k]et port$o[k]).
Les portes sont spécifiées commemodulespec.gatespec(pour connecter un sous-module), ou commegatespec(pour
connecter le module composé).spécification de moduleest soit un nom de sous-module (pour les sous-modules
scalaires), soit un nom de sous-module plus un index entre crochets (pour les vecteurs de sous-module). Pour les portes
scalaires,gatespecest le nom de la porte ; pour les vecteurs de porte, il s'agit soit du nom de la porte plus un index entre
crochets, soitnom de la porte++.
lenom de la porte++ la notation entraîne l'utilisation du premier index de porte non connecté. Si toutes les portes
du vecteur de porte donné sont connectées, le comportement est différent pour les sous-modules et pour le
module composé englobant. Pour les sous-modules, le vecteur de porte se développe de un. Pour un module
composé, une fois la dernière porte connectée, ++ s'arrêtera avec une erreur.
REMARQUE: Pourquoi n'est-il pas possible de développer un vecteur porte du module composé ? La structure du modèle
est construite dans l'ordre descendant, de sorte que les nouvelles portes seraient laissées non connectées à l'extérieur, car
il n'y a aucun moyen dans NED de "revenir en arrière" et de les connecter par la suite.
Lorsque l'opérateur ++ est utilisé avec $je ou $o (par exemple g$i++ ou g$o++, voir plus loin), il ajoutera en fait une paire
de portes (entrée + sortie) pour maintenir des tailles de porte égales pour les deux directions.
Spécifications du canal (-->spécification de canal--> à l'intérieur d'une connexion) sont similaires aux sous-modules à bien
des égards. Voyons quelques exemples !
Les connexions suivantes utilisent deux types de canaux définis par l'utilisateur, Ethernet100 et Colonne
vertébrale. Le code montre la syntaxe d'affectation des paramètres (Coût et longueur) et en spécifiant une chaîne
d'affichage (et les propriétés NED en général) :
Lors de l'utilisation de types de canaux intégrés, le nom du type peut être omis ; il sera déduit des noms de
paramètres.
32
Manuel de simulation OMNeT++ – Le langage NED
Si débit de données, ber ou parest assigné, ned.DatarateChannel sera choisi. Sinon, siretardou
désactivéeest présent, il sera ned.DelayChannel ; sinon c'est ned.IdealChannel. Naturellement, si
d'autres noms de paramètres sont affectés dans une connexion sans type de canal explicite, ce sera
une erreur (avec "ned.DelayChannel n'a pas un tel paramètre » ou message similaire).
Les paramètres de connexion, de la même manière que les paramètres de sous-module, peuvent
également être affectés à l'aide d'affectations de motifs, bien que les noms de canaux à associer aux motifs
soient un peu plus compliqués et moins pratiques à utiliser. Un canal peut être identifié par le nom de sa
porte source plus le nom du canal ; le nom de la chaîne est actuellement toujourscanaliser. Elle est illustrée
par l'exemple suivant :
moduleFile d'attente
{
paramètres:
source.out.canaliser.délai = 10ms ;
queue.out.canaliser.délai = 20ms ; sous-
modules:
source : source ;
file d'attente : file d'attente ;
évier : évier ;
Connexions:
source.out --> ned.DelayChannel --> queue.in ; queue.out -->
ned.DelayChannel <--> sink.in ;
L'utilisation de connexions bidirectionnelles est un peu plus délicate, car les deux directions doivent être couvertes
séparément :
réseau Réseau
{
paramètres:
hostA.g$o[0].canaliser.datarate = 100Mbps ;// la connexion A -> B hostB.g$o[0].
canaliser.datarate = 100Mbps ;// la connexion B -> A hostA.g$o[1].canaliser.datarate =
// la connexion A -> C // la
1Gbps ; hostC.g$o[0].canaliser.datarate = 1Gbps ; sous-modules:
connexion C -> A
hôteA : hôte ;
hôteB : hôte ;
hôteC : hôte ;
Connexions:
hostA.g++ <--> ned.DatarateChannel <--> hostB.g++ ; hostA.g++ <-->
ned.DatarateChannel <--> hostC.g++ ;
De plus, avec la syntaxe ++, il n'est pas toujours facile de déterminer quels indices de porte correspondent
aux connexions à configurer. Si les objets de connexion pouvaient recevoir des noms pour remplacer le
nom par défaut "canaliser",cela faciliterait l'identification des connexions dans les modèles. Cette
fonctionnalité est décrite dans la section suivante.
Le nom par défaut donné aux objets canal est "canaliser".Depuis OMNeT++ 4.3, il est possible de
spécifier explicitement le nom et également de remplacer le nom par défaut par type de canal. le
33
Manuel de simulation OMNeT++ – Le langage NED
Le but des noms de canaux personnalisés est de faciliter l'adressage lorsque les paramètres de canal sont
attribués à partir de fichiers ini.
La syntaxe pour nommer un canal dans une connexion est similaire à la syntaxe du sous-module :nom :
genre. Puisque les deuxNomet tapersont facultatifs, les deux-points doivent être là aprèsNommême sitaper
manque, afin de lever l'ambiguïté.
Exemples:
r1.pppg++ <--> eth1 : EthernetChannel <--> r2.pppg++ ; a.out --> foo :
{delay=1ms;} --> b.in ;
a.out --> barre : --> b.in ;
En l'absence de nom explicite, le nom du canal vient du @nom par défaut propriété du type de canal si
elle existe.
canaliser Eth10G s'étendned.DatarateChannel Comme IEth {
@defaultname(eth10G);
}
Il y a un hic avec @nom par défaut cependant : si le type de canal est spécifié avec un **.canaliser- Nom
.liketype= ligne dans un fichier ini, puis le @ du type de canalnom par défaut ne peut pas être utilisé comme
nom du canal dans cette ligne de configuration, car le type de canal ne serait connu qu'à la suite de
l'utilisation de cette même ligne de configuration. Pour illustrer le problème, considérons ce qui précède
Eth10G channel, et un module composé contenant la connexion suivante :
r1.pppg++ <--> <> Comme IEth <--> r2.pppg++ ;
L'anomalie peut être évitée en utilisant un nom de canal explicite dans la connexion, sans utiliser
@defaultname, soit en spécifiant le type via un paramètre de module (par exemple en écrivant <param>
commeau .lieu
. . de <> Comme ...).
Des constructions de programmation simples (boucle, conditionnelle) permettent de créer facilement plusieurs
3.10.1 Exemples
Chaîne
moduleChaîne
paramètres:
entiercompter;
sous-modules:
34
Manuel de simulation OMNeT++ – Le langage NED
nœud[compte] : nœud {
portes:
port[2] ;
}
connexions autoriséesnon connecté:
pourje = 0..count-2 {
nœud[i].port[1] <--> nœud[i+1].port[0] ;
}
}
Arbre binaire
FacileBinaryTreeNode {
portes:
inout à gauche;
inout à droite;
inout parent;
}
moduleArbreBinaire {
paramètres:
entierla taille;
sous-modules:
nœud[2^hauteur-1] : BinaryTreeNode ;
connexions autoriséesnon connecté:
pourje=0..2^(hauteur-1)-2 {
nœud[i].left <--> nœud[2*i+1].parent ;
nœud[i].right <--> nœud[2*i+2].parent ;
}
}
Notez que toutes les portes des modules ne seront pas connectées. Par défaut, une porte non connectée produit
un message d'erreur d'exécution au démarrage de la simulation, mais ce message d'erreur est désactivé ici avec
leautoriser non connectémodificateur. Par conséquent, il est de la responsabilité des modules simples de ne pas
émettre sur une porte non connectée.
Graphique aléatoire
Les connexions conditionnelles peuvent être utilisées pour générer des topologies aléatoires, par exemple. Le code
suivant génère un sous-graphe aléatoire d'un graphe complet :
moduleGraphique aléatoire {
paramètres:
entiercompter;
doubleconnexité ; // 0.0<x<1.0 sous-modules
:
nœud[compte] : nœud {
portes:
dans[compter] ;
out[compter] ;
35
Manuel de simulation OMNeT++ – Le langage NED
}
connexions autoriséesnon connecté:
pourje=0..compte-1, pourj=0..count-1 {
nœud[i].out[j] --> nœud[j].in[i]
sii!=j && uniform(0,1)<connexité ;
}
}
Notez l'utilisation de la autoriser non connectémodificateur ici aussi, pour désactiver les messages d'erreur produits par le code
de configuration du réseau pour les portes non connectées.
Plusieurs approches peuvent être utilisées pour créer des topologies complexes qui ont une structure régulière ; trois
d'entre eux sont décrits ci-dessous.
Ce modèle prend un sous-ensemble des connexions d'un graphe complet. Une condition est utilisée pour
"extraire" l'interconnexion nécessaire du graphe complet :
pouri=0..N-1, pourj=0..N-1 {
nœud[i].out[...] --> nœud[j].in[...] sicondition(i,j);
}
Le modèle parcourt tous les nœuds et crée les connexions nécessaires pour chacun d'eux. Il peut être
généralisé ainsi :
pouri=0..Nnœuds, pourj=0..Nconns(i)-1 {
nœud[i].out[j] --> nœud[rightNodeIndex(i,j)].in[j] ;
}
Le module composé Hypercube (qui sera présenté plus tard) est un exemple clair de cette approche.
BinaryTree peut également être considéré comme un exemple de ce modèle où la boucle j interne est
déroulée.
L'applicabilité de ce modèle dépend de la facilité avec laquelle le rightNodeIndex(je, j) fonction peut
être formulée.
Un troisième modèle consiste à répertorier toutes les connexions dans une boucle :
36
Manuel de simulation OMNeT++ – Le langage NED
pouri=0..Nconnexions-1 {
nœud[leftNodeIndex(i)].out[...] --> nœud[rightNodeIndex(i)].in[...] ;
}
Ce modèle peut être utilisé si leftNodeIndex(je) et rightNodeIndex(je) les fonctions de mappage peuvent être
suffisamment formulées.
leChaîne module est un exemple de cette approche où les fonctions de mappage sont extrêmement simples :
leftNodeIndex(je) = je et rightNodeIndex(je) = je + 1. Le modèle peut également être utilisé pour créer un sous-
ensemble aléatoire d'un graphique complet avec un nombre fixe de connexions.
Dans le cas de structures irrégulières où aucun des modèles ci-dessus ne peut être utilisé, on peut recourir à la
liste de toutes les connexions, comme on le ferait dans la plupart des simulateurs existants.
Un type de sous-module peut être spécifié avec un paramètre de module du type chaîne de caractères, ou en général,
avec n'importe quelle expression de type chaîne. La syntaxe utilise leComme mot-clé.
Il crée un vecteur de sous-module dont le type de module proviendra dunodeTypeparamètre. Par exemple,
sinodeTypeest réglé sur "SensorNode",alors le vecteur de module sera composé de nœuds de capteur, à
condition que ce type de module existe et qu'il se qualifie. Ce que cela signifie, c'est que leINode doit être
un existantinterfaces de modules, lequel àSensorNodele type de module doit implémenter (plus à ce sujet
plus tard).
Comme déjà mentionné, on peut écrire une expression entre les crochets angulaires. L'expression peut
utiliser les paramètres du module parent et des sous-modules définis précédemment, et doit produire une
valeur de chaîne. Par exemple, le code suivant est également valide :
réseau Net6
{
paramètres:
chaîne de caractèresnodeTypePrefix ;
entierune variante;
sous-modules:
node[6] : <nodeTypePrefix + "Node" + chaîne de caractères(variante)> Comme INode {
...
}
37
Manuel de simulation OMNeT++ – Le langage NED
moduleSensorNodeComme INode {
paramètres:
entier adresse;
...
portes:
inout Port[];
...
}
Le "<nodeType> comme INode” La syntaxe a un problème lorsqu'elle est utilisée avec des vecteurs de sous-
module : ne permet pas de spécifier différents types pour différents indices. La syntaxe suivante est mieux
adaptée aux vecteurs de sous-module :
L'expression entre les crochets angulaires peut être complètement omise, laissant une paire de crochets
angulaires vides, <> :
moduleNœud
{
sous-modules:
nic : <> Comme INic; // expression de nom de type laissée non spécifiée
...
}
Maintenant, le nom du type de sous-module devrait être défini via des affectations de modèle de nom de type. Les
affectations de modèle de nom de type ressemblent aux affectations de modèle pour les paramètres du sous-module,
seul le nom du paramètre est remplacé par lenom de typemot-clé. Les affectations de modèle de nom de type peuvent
également être écrites dans le fichier de configuration. Dans un réseau qui utilise ce qui précède Nœud Type NED, les
affectations de modèles de nom de type ressembleraient à ceci :
réseau Réseau
{
paramètres:
nœud[*].nic.nom de type= "Ieee80211g" ; sous-
modules:
nœud : nœud[100] ;
}
Une valeur par défaut peut également être spécifiée entre les chevrons ; il sera utilisé s'il n'y a pas
d'affectation de nom de type pour le module :
moduleNœud
{
sous-modules:
nic : <défaut("Ieee80211b")> Comme INic;
38
Manuel de simulation OMNeT++ – Le langage NED
...
}
Il doit y avoir exactement un type de module qui porte le nom simple Ieee80211b et implémente
également l'interface du module INic, sinon un message d'erreur sera émis. (Les importations en
Nœud's le fichier NED ne joue aucun rôle dans la résolution de type.) S'il existe deux types de ce type
ou plus, on peut lever l'ambiguïté en spécifiant le nom complet du type de module, c'est-à-dire un
nom qui inclut également le nom du package :
moduleNœud
{
sous-modules:
nic : <défaut("acme.wireless.Ieee80211b")> Comme INic; // nom inventé
...
}
Lors de la création de modules composés réutilisables, il est souvent utile de pouvoir rendre un sous-module
paramétrique également facultatif. Une solution consiste à laisser l'utilisateur définir le type de sous-module avec
un paramètre de chaîne et de ne pas créer le module lorsque le paramètre est défini sur la chaîne vide. Comme
ça:
moduleNœud
{
paramètres:
chaîne de caractèrestcpType = défaut("TCP");
sous-modules:
tcp : <tcpType> Comme ITcp sitcpType !="" ;
}
Cependant, ce modèle, lorsqu'il est utilisé de manière intensive, peut conduire à un grand nombre de paramètres de chaîne.
Heureusement, il est également possible d'obtenir le même effet avecnom de type, sans utiliser de paramètres
supplémentaires :
moduleNœud
{
sous-modules:
TCP : <défaut("TCP")> Comme ITcp si nom de type!="";
}
réseau Réseau
{
paramètres:
nœud1.tcp.nom de type= "TcpExt" ; // laissez node1 utiliser un TCP personnalisé
node2.tcp.nom de type= "" ; // pas de TCP dans node2 sous-modules:
nœud1 : nœud ;
39
Manuel de simulation OMNeT++ – Le langage NED
nœud2 : nœud ;
}
Notez que cette astuce ne fonctionne pas avec les vecteurs de sous-module. La raison en est que la condition s'applique
au vecteur dans son ensemble, tandis que le type est par élément.
Il est également souvent utile de pouvoir vérifier, par exemple dans la section des connexions, si un
sous-module conditionnel a été créé ou non. Ceci peut être fait avec leexiste()opérateur. Un exemple:
moduleNœud
{
...
Connexions:
ip.tcpOut --> tcp.ipIn si existe(ip) && existe(tcp);
}
Limitation: existe()ne peut être utilisé que après l'occurrence du sous-module dans le module
composé.
Les types de connexion paramétriques fonctionnent de la même manière que les types de sous-modules paramétriques, et la
syntaxe est également similaire. Un exemple basique qui utilise un paramètre du module parent :
L'expression peut utiliser des variables de boucle, des paramètres du module parent ainsi que des paramètres de
sous-modules (par exemplehôte[2].channelType).
L'expression de type peut également être absente, puis le type devrait être spécifié à l'aide des affectations
de modèle de nom de type :
a.g++ <--> <>Comme IMyChannel <--> b.g++ ;
a.g++ <--> <>Comme IMyChannel {@display("ls=red");} <--> b.g++;
Les propriétés NED sont des annotations de métadonnées qui peuvent être ajoutées aux modules,
paramètres, portes, connexions, fichiers NED, packages et pratiquement tout dans NED. @affichage,
@class, @namespace, @unit, @prompt, @loose, @directIn sont toutes des propriétés qui ont été
mentionnées dans les sections précédentes, mais ces exemples ne font qu'effleurer la surface de
l'utilisation des propriétés.
40
Manuel de simulation OMNeT++ – Le langage NED
En utilisant les propriétés, on peut attacher des informations supplémentaires aux éléments NED. Certaines propriétés
sont interprétées par NED, par le noyau de simulation ; d'autres propriétés peuvent être lues et utilisées à partir du
modèle de simulation, ou fournir des conseils pour les outils d'édition NED.
Les propriétés sont attachées au type, donc on ne peut pas avoir différentes propriétés définies par instance.
Toutes les instances de modules, connexions, paramètres, etc. créés à partir d'un emplacement particulier dans
les fichiers NED ont des propriétés identiques.
L'exemple suivant montre la syntaxe d'annotation de divers éléments NED :
@namespace(foo); // propriété de fichier
moduleExemple
{
paramètres:
@nœud; // propriété du module
@display("i=appareil/pc"); // propriété du module
entierune @unité(s) = défaut(1); // propriété de paramètre portes:
Il est parfois utile d'avoir plusieurs propriétés portant le même nom, par exemple pour déclarer
plusieurs statistiques produites par un module simple. Indices immobiliers rendre cela possible.
Un index de propriété est un identificateur ou un nombre entre crochets après le nom de la propriété, tel
que besoin et gigue dans l'exemple suivant :
FacileApplication {
@statistic[eed](title="délai de bout en bout des paquets reçus";unit=s); @statistic[jitter]
(title="jitter des paquets reçus");
}
Cet exemple déclare deux statistiques comme @statistique Propriétés, @statistique[eed] et @statistique
[jitter]. Les valeurs de propriété entre parenthèses sont utilisées pour fournir des informations
supplémentaires, comme un nom plus descriptif (titre="..." ou une unité (unité=s). Les index de propriété sont
également facilement accessibles à partir de l'API C++ ; par exemple il est possible de se demander quels
indices existent pour le "statistique" propriété, et il renverra une liste contenant "j'ai besoin" et "gigue").
Dans le @statistique Par exemple, l'index était textuel et significatif, mais aucun n'est réellement
requis. L'exemple factice suivant montre l'utilisation d'indices numériques qui peuvent être
41
Manuel de simulation OMNeT++ – Le langage NED
Notez que sans l'index, les lignes définiraient en fait le même @fou propriété et écraseraient les
valeurs de l'autre.
Les index permettent également de remplacer les entrées par héritage :
Les propriétés peuvent contenir des données, données entre parenthèses ; le modèle de données est assez flexible. Pour commencer, les
propriétés peuvent ne contenir aucune valeur ou une seule valeur :
@nœud;
@nœud(); // identique à @node
@class(FtpApp2);
Ils peuvent contenir des paires clé-valeur, séparées par des points-virgules :
Dans les paires clé-valeur, chaque valeur peut être une liste (séparée par des virgules) :
@foo(coords=47.549,19.034;labels=vehicle,router,critical);
Les exemples ci-dessus sont des cas particuliers du modèle de données général. Selon le modèle de données, les
propriétés contiennentliste de valeurs clés paires, séparées par des points-virgules. Articles dansliste de valeurs sont
séparés par des virgules. Partout oùclé est manquant, les valeurs vont sur la liste de valeurs du clé par défaut, la chaîne
vide.
Les éléments de valeur peuvent contenir des mots, des nombres, des constantes de chaîne et d'autres caractères,
mais pas des chaînes arbitraires. Chaque fois que la syntaxe ne permet pas une certaine valeur, elle doit être
placée entre guillemets. Cette citation n'affecte pas la valeur car l'analyseur supprime automatiquement une
couche de guillemets ; Donc, @classe (TCP) et @classe("TCP") sont exactement les mêmes. Si les guillemets eux-
mêmes doivent faire partie de la valeur, une couche supplémentaire de guillemets et d'échappement est la
solution : @foo("\"une chaîne\"").
Il y a aussi quelques conventions. On peut utiliser des propriétés pour étiqueter les éléments NED ; par exemple, un @héberger
La propriété pourrait être utilisée pour marquer tous les types de module qui représentent divers hôtes. Cette propriété pourrait
être reconnue par exemple par des outils d'édition, par un code de découverte de topologie à l'intérieur du modèle de
simulation, etc.
La convention pour une telle propriété "marqueur" est que toute donnée supplémentaire qu'elle contient (c'est-à-
dire entre parenthèses) est ignorée, à l'exception d'un seul mot faux, qui a la signification spéciale de « désactiver
» la propriété. Ainsi, tout modèle ou outil de simulation qui interprète les propriétés doit gérer toutes les
42
Manuel de simulation OMNeT++ – Le langage NED
Les propriétés définies sur un type de module ou de canal peuvent être mises à jour à la fois par sous-classement et lors
de l'utilisation du type comme sous-module ou canal de connexion. On peut ajouter de nouvelles propriétés, et aussi
modifier celles existantes.
Lors de la modification d'une propriété, la nouvelle propriété est fusionnée avec l'ancienne. Les règles de fusion sont
assez simples. De nouvelles clés sont simplement ajoutées. Si une clé existe déjà dans l'ancienne propriété, les éléments
de sa liste de valeurs écrasent les éléments à la même position dans l'ancienne propriété. Un seul trait d'union (−) en tant
qu'élément de la liste de valeurs sert d '«anti-valeur», il supprime l'élément à la position correspondante.
Quelques exemples:
base @soutenir
Nouveau @prop(a)
résultat @prop(a)
base @prop(a,b,c)
Nouveau @soutenir(,-)
résultat @prop(a,,c)
base @prop(foo=a,b)
Nouveau @prop(foo=A,,c;bar=1,2)
résultat @prop(foo=A,b,c;bar=1,2)
REMARQUE: Les règles de fusion ci-dessus font partie de NED, mais le code qui interprète les propriétés
peut avoir des règles spéciales pour certaines propriétés. Par exemple, le @unitéla propriété des
paramètres n'est pas autorisée à être remplacée, et @affichageest fusionnée avec des règles spéciales bien
que similaires (voir chapitre 8).
3.13 Héritage
La prise en charge de l'héritage dans le langage NED n'est décrite que brièvement ici, car plusieurs détails
et exemples ont déjà été présentés dans les sections précédentes.
Dans NED, un type ne peut s'étendre que (s'étendmot-clé) un élément du même type de composant :
un module simple peut étendre un module simple, un canal peut étendre un canal, une interface de
module peut étendre une interface de module, etc. Il y a cependant une irrégularité : un module
composé peut étendre un module simple (et hériter de sa classe C++), mais pas vice versa.
L'héritage unique est pris en charge pour les modules et les canaux, et l'héritage multiple est pris en charge pour
les interfaces de module et les interfaces de canal. Un réseau est un raccourci pour un module composé avec le @
estRéseau ensemble de propriétés, donc les mêmes règles s'appliquent à lui qu'aux modules composés.
Cependant, un type de module simple ou composé peut implémenter (Comme mot-clé) plusieurs interfaces de
module ; de même, un type de canal peut implémenter plusieurs interfaces de canal.
43
Manuel de simulation OMNeT++ – Le langage NED
IMPORTANT: Lorsque vous étendez un type de module simple à la fois dans NED et dans C++, vous
devez utiliser le @classerpour dire à NED d'utiliser la nouvelle classe C++ – sinon le nouveau type de
module hérite de la classe C++ de la base !
La succession peut :
• ajouter de nouvelles propriétés, paramètres, portes, types internes, sous-modules, connexions, tant que les
noms n'entrent pas en conflit avec les noms hérités
• modifier les propriétés héritées et les propriétés des paramètres et des portes hérités
• il ne peut pas modifier les sous-modules hérités, les connexions et les types internes
Pour des détails et des exemples, voir les sections correspondantes de ce chapitre (modules simples
3.3, modules composés 3.4, canaux 3.5, paramètres 3.6, portes 3.7, sous-modules 3.8, connexions 3.9,
interfaces de module et interfaces de canal 3.11.1).
3.14 Forfaits
Avoir tous les fichiers NED dans un seul répertoire convient aux petits projets de simulation. Cependant, lorsqu'un
projet grandit, il devient tôt ou tard nécessaire d'introduire une structure de répertoires et d'y trier les fichiers
NED. NED prend en charge nativement les arborescences de répertoires avec les fichiers NED et appelle les
répertoirespaquets. Les packages sont également utiles pour réduire les conflits de noms, car les noms peuvent
être qualifiés avec le nom du package.
REMARQUE: Les packages NED sont basés sur le concept de package Java, avec des améliorations mineures.
Si vous êtes familier avec Java, vous trouverez peu de surprise dans cette section.
3.14.1 Aperçu
Lorsqu'une simulation est exécutée, il faut indiquer au noyau de simulation le répertoire qui est la racine de
l'arborescence des packages ; appelons-leDossier source NED. Le noyau de simulation traversera toute l'arborescence
des répertoires et chargera tous les fichiers NED de chaque répertoire. On peut avoir plusieurs arborescences de
répertoires NED, et leurs racines (les dossiers source NED) doivent être données au noyau de simulation dans leChemin
NEDvariable. Le chemin NED peut être spécifié de plusieurs manières : en tant que variable d'environnement (NEDPATH),
comme option de configuration (chemin-ned), ou en tant qu'option de ligne de commande pour l'environnement
d'exécution de la simulation (-n). NEDPATHest décrit en détail au chapitre 11.
Les répertoires d'une arborescence source NED correspondent à des packages. Si les fichiers NED sont dans le <
racine>/a/b/c répertoire (où <racine>est répertorié dans le chemin NED), le nom du package estabcLe nom du
package doit également être explicitement déclaré en haut des fichiers NED, comme ceci :
paquetabc;
Le nom du package qui suit le nom du répertoire et le package déclaré doivent correspondre ; c'est
une erreur s'ils ne le font pas. (La seule exception est la racinepaquet.ned file, comme décrit ci-
dessous.)
Par convention, les noms de packages sont tous en minuscules et commencent soit par le nom du projet
(mon projet),ou le nom de domaine inversé plus le nom du projet (org.exemple.monprojet). Cette dernière
convention ferait commencer l'arborescence de répertoires par quelques niveaux de répertoires vides, mais
cela peut être éliminé avec un toplevelpackage.ned.
44
Manuel de simulation OMNeT++ – Le langage NED
Fichiers NED appeléspackage.ned ont un rôle particulier, car ils sont censés représenter l'ensemble du paquet.
Par exemple, les commentaires danspackage.ned sont traités comme la documentation du paquet. Aussi un @
espace de noms propriété dans un paquet.ned file affecte tous les fichiers NED dans ce répertoire et tous les
répertoires en dessous.
Le niveau supérieur paquet.ned file peut être utilisé pour désigner le package racine, ce qui est utile pour éliminer
quelques niveaux de répertoires vides résultant de la convention de nommage des packages. Par exemple, étant
donné un projet où tous les types NED sont sous leorg.acme.foosim package, on peut éliminer les niveaux de
répertoires vides org, acmé et imbécile en créant un package.ned fichier dans le répertoire racine source avec la
déclaration de package org.exemple.monprojet. Cela créera un répertoire fou sous la racine à interpréter comme
package org.exemple.monprojet.foo, et les fichiers NED qu'ils contiennent doivent contenir cela en tant que
déclaration de package. Seule la racinepackage.ned peut définir le package, paquet.ned files fichiers des sous-
répertoires doivent le suivre.
Prenons l'exemple du framework INET, qui contient des centaines de fichiers NED dans plusieurs dizaines
de packages. La structure du répertoire ressemble à ceci :
INET/
src/
base/
transport/
TCP/
udp/
...
couche réseau/
couche de liaison/
...
exemples/
ad hoc/
Ethernet/
...
lesrc et exemples les sous-répertoires sont désignés comme des dossiers source NED, donc NEDPATH est le
suivant (à condition qu'INET ait été décompressé dans /maison/joe):
/home/joe/INET/src;/home/joe/INET/exemples
Les deux src et exemples contenir paquet.ned fifichiers pour définir le package racine :
// INET/src/paquet.ned : paquet
inet;
// INET/exemples/paquet.ned : paquet
inet.exemples ;
Nous avons déjà mentionné que les packages peuvent être utilisés pour distinguer les types NED portant des
noms similaires. Le nom qui inclut le nom du package (abcQueuepour unFile d'attentemodules dans leabc
45
Manuel de simulation OMNeT++ – Le langage NED
paquet) s'appellenom complet; sans le nom du package (File d'attente)on l'appellenom simple.
Les noms simples seuls ne suffisent pas à identifier sans ambiguïté un type. Voici comment on peut se
référer à un type existant :
1. Par nom pleinement qualifié. C'est souvent fastidieux, car les noms ont tendance à être trop longs ;
3. Si le type se trouve dans le même package, il n'est pas nécessaire de l'importer ; il peut être désigné
par un simple nom
Les types peuvent être importés avec leimportermot-clé soit par un nom complet, soit par un modèle générique.
Dans les modèles génériques, un astérisque ("*") signifie "toute séquence de caractères ne contenant pas de
point", et deux astérisques ("**") signifient "toute séquence de caractères pouvant contenir un point".
Ainsi, n'importe laquelle des lignes suivantes peut être utilisée pour importer un type appelé
inet.protocols.networklayer.ip.RoutingTable :
importer inet.protocols.networklayer.ip.RoutingTable ;
importer inet.protocols.networklayer.ip.* ;
importer inet.protocols.networklayer.ip.Ro*Ta* ;
importer inet.protocols.*.ip.*;
importer inet.**.RoutingTable ;
Si une importation nomme explicitement un type avec son nom complet exact, alors ce type doit exister, sinon il
s'agit d'une erreur. Les importations contenant des caractères génériques sont plus permissives, il leur est permis
de ne correspondre à aucun type NED existant (bien que cela puisse générer un avertissement.)
Les types internes ne peuvent pas être référencés en dehors de leurs types englobants, ils ne peuvent donc pas non plus être importés.
La situation est un peu différente pour les spécifications de sous-module et de voie de connexion utilisant le
Comme mot-clé, lorsque le nom du type provient d'une expression de valeur de chaîne (voir la section 3.11.1 sur
les types de sous-module et de voie en tant que paramètres). Les importations ne sont pas très utiles ici : au
moment de l'écriture du fichier NED, on ne sait pas encore quels types de NED pourront être « branchés » là-bas,
ils ne peuvent donc pas être importés à l'avance.
Il n'y a pas de problème avec les noms pleinement qualifiés, mais les noms simples doivent être
résolus différemment. Voici ce que fait NED : il détermine quelle interface le type de module ou de
canal doit implémenter (c'est-à-dire ...comme INode),puis collecte les types qui ont le nom simple
donné ET implémentent l'interface donnée. Il doit y avoir exactement un type de ce type, qui est
ensuite utilisé. S'il n'y en a pas ou s'il y en a plusieurs, cela sera signalé comme une erreur.
Voyons l'exemple suivant :
moduleMobileHost
{
paramètres:
chaîne de caractèrestype de mobilité ;
sous-modules:
mobilité : <mobilityType> Comme Mobilité ;
46
Manuel de simulation OMNeT++ – Le langage NED
...
}
Il n'est pas obligatoire d'utiliser des packages : si tous les fichiers NED se trouvent dans un seul répertoire répertorié sur
le NEDPATH, les déclarations de packages (et les importations) peuvent être omises. On dit que ces fichiers se trouvent
dans lepaquet par défaut.
47
Manuel de simulation OMNeT++ – Le langage NED
48
Manuel de simulation OMNeT++ – Modules simples
Chapitre 4
Modules simples
Modules simples sont les composants actifs du modèle. Des modules simples sont programmés en C++, en utilisant la
bibliothèque de classes OMNeT++. Les sections suivantes contiennent une brève introduction à la simulation
d'événements discrets en général, expliquent comment ses concepts sont implémentés dans OMNeT++, et donnent un
aperçu et des conseils pratiques sur la façon de concevoir et de coder des modules simples.
Cette section contient une très brève introduction sur le fonctionnement de la simulation à événements discrets (DES),
afin d'introduire les termes que nous utiliserons pour expliquer les concepts et l'implémentation d'OMNeT++.
UNEsystème à événements discretsest un système où les changements d'état (événements) se produisent à des instances
discrètes dans le temps, et les événements ne prennent aucun temps pour se produire. On suppose qu'il ne se passe rien (c'est-
à-dire rien d'intéressant) entre deux événements consécutifs, c'est-à-dire qu'aucun changement d'état n'a lieu dans le système
entre les événements. Ceci est en contraste aveccontinusystèmes où les changements d'état sont continus. Les systèmes qui
peuvent être considérés comme des systèmes à événements discrets peuvent être modélisés à l'aide de la simulation à
événements discrets, DES.
Par exemple, les réseaux informatiques sont généralement considérés comme des systèmes à événements discrets. Certains des
événements sont:
Cela implique qu'entre deux événements tels que début d'une transmission de paquets et fin d'une
transmission de paquets, il ne se passe rien d'intéressant. Autrement dit, l'état du paquet resteêtre
transmis. Notez que la définition d'événements et d'états « intéressants » dépend toujours de l'intention et
des objectifs du modélisateur. Si nous étions intéressés par la transmission de bits individuels, nous aurions
inclus quelque chose commedébut de la transmission des bits et fin de transmission des bits parmi nos
événements.
49
Manuel de simulation OMNeT++ – Modules simples
Le moment où les événements se produisent est souvent appelé horodatage de l'événement; avec OMNeT++ nous
utilisons le terme heure d'arrivée (car dans la bibliothèque de classes, le mot « horodatage » est réservé à un attribut
configurable par l'utilisateur dans la classe d'événements). Le temps dans le modèle est souvent appelétemps de
simulation, temps de modèle ou temps virtuel par opposition au temps réel ou au temps CPU qui font référence à la
durée d'exécution du programme de simulation et au temps CPU qu'il a consommé.
La simulation d'événements discrets maintient l'ensemble des événements futurs dans une structure de données
souvent appelée FES (Future Event Set) ou FEL (Future Event List). Ces simulateurs fonctionnent généralement
selon le pseudocode suivant :
L'étape d'initialisation construit généralement les structures de données représentant le modèle de simulation, appelle
tout code d'initialisation défini par l'utilisateur et insère des événements initiaux dans le FES pour s'assurer que la
simulation peut démarrer. Les stratégies d'initialisation peuvent différer considérablement d'un simulateur à l'autre.
La boucle suivante consomme les événements du FES et les traite. Les événements sont traités dans un
ordre d'horodatage strict pour maintenir la causalité, c'est-à-dire pour garantir qu'aucun événement en
cours n'ait d'effet sur les événements antérieurs.
Le traitement d'un événement implique des appels au code fourni par l'utilisateur. Par exemple, en utilisant l'exemple de
simulation de réseau informatique, le traitement d'un événement "timeout expiré" peut consister à renvoyer une copie
du paquet réseau, à mettre à jour le nombre de tentatives, à programmer un autre événement "timeout", etc. Le code
utilisateur peut également supprimer des événements du FES, par exemple lors de l'annulation de temporisations.
La simulation s'arrête lorsqu'il n'y a plus d'événements (cela arrive rarement en pratique), ou lorsqu'il n'est pas
nécessaire de poursuivre la simulation parce que le temps modèle ou le temps CPU a atteint une limite donnée,
ou parce que les statistiques ont atteint la précision souhaitée. À ce stade, avant la fermeture du programme,
l'utilisateur souhaitera généralement enregistrer des statistiques dans des fichiers de sortie.
OMNeT++ utilise des messages pour représenter des événements.1 Les messages sont représentés par des
instances de cMessage classe et ses sous-classes. Les messages sont envoyés d'un module à l'autre - cela
signifie que l'endroit où "l'événement se produira" est lemodule de destination du message, et
1À toutes fins pratiques. Notez qu'il existe une classe appeléecÉvénement cette cMessage sous-classes de, mais il n'est utilisé
qu'en interne au noyau de simulation.
50
Manuel de simulation OMNeT++ – Modules simples
l'heure du modèle à laquelle l'événement se produit est la heure d'arrivée du message. Des événements tels que «
timeout expiré » sont implémentés par le module qui s'envoie un message à lui-même.
Les événements sont consommés à partir de la FES dans l'ordre des heures d'arrivée, pour maintenir la causalité. Plus précisément, étant
donné deux messages, les règles suivantes s'appliquent :
1. Le message avec le heure d'arrivée plus tôt est exécuté en premier. Si les heures d'arrivée sont égales,
2. celui avec le priorité de planification plus élevée (petite valeur numérique) est exécuté en premier. Si les
priorités sont les mêmes,
Le temps de simulation actuel peut être obtenu avec lesimHeure() une fonction.
Le temps de simulation dans OMNeT++ est représenté par le type C++simtime_t,qui est par défaut un
typedef pour leSimTimeclasser.SimTimeLa classe stocke le temps de simulation dans un entier 64 bits, en
utilisant une représentation décimale en virgule fixe. La résolution est contrôlée par leexposant d'échelle
variable de configuration globale ; C'est,SimTimeinstances ont la même résolution. L'exposant peut être
choisi entre -18 (résolution attoseconde) et 0 (secondes). Certains exposants avec les plages qu'ils
fournissent sont indiqués dans le tableau suivant.
Notez que bien que le temps de simulation ne puisse pas être négatif, il est toujours utile de pouvoir représenter
des nombres négatifs, car ils surviennent souvent lors de l'évaluation d'expressions arithmétiques.
Il n'y a pas de conversion implicite deSimTimeàdouble,principalement parce que cela entrerait en
conflit avec des opérations arithmétiques surchargées deSimTime ;Utilisez ledbl()méthode de
SimTimeou laSIM-TIME_DBL()macro à convertir. Pour réduire le besoin dedb(),plusieurs fonctions et
méthodes ont des variantes surchargées qui acceptent directementSimTime,par examplefabs(),
fmod(), div(), ceil(), floor(), uniform(), exponentiel(),et Ordinaire().
D'autres méthodes utiles deSimTimeinclurechaîne(),qui renvoie la valeur sous forme de chaîne ;
analyser (), qui convertit une chaîne enSimTime ; cru(),qui renvoie l'entier 64 bits sous-jacent ;
getScaleExp(),qui renvoie l'exposant de l'échelle globale ; est zéro(), qui teste si le temps de simulation
est 0 ; etgetMaxTime(), qui renvoie le temps de simulation maximal pouvant être représenté à
l'exposant de l'échelle actuelle. Le zéro et le temps maximum de simulation sont également
accessibles via leSIMTIME_ZERO et SIMTIME_MAX macros.
// 340 microsecondes dans le futur, tronqué à la limite de la milliseconde simtime_t timeout =
(simTime() + SimTime(340, SIMTIME_US)).trunc(SIMTIME_MS);
51
Manuel de simulation OMNeT++ – Modules simples
La mise en œuvre du FES est un facteur crucial dans la performance d'un simulateur à événements discrets. Dans
OMNeT++, le FES est remplaçable et l'implémentation FES par défaut utilisetas binaire comme structure de données. Le
tas binaire est généralement considéré comme le meilleur algorithme FES pour la simulation d'événements discrets, car
il offre de bonnes performances équilibrées pour la plupart des charges de travail. (Des structures de données exotiques
commeliste de sauts peut être plus performant que le tas dans certains cas.)
Les modèles de simulation OMNeT++ sont composés de modules et de connexions. Les modules peuvent être des
modules simples (atomiques) ou des modules composés ; les modules simples sont les composants actifs d'un modèle et
leur comportement est défini par l'utilisateur en tant que code C++. Les connexions peuvent avoir des objets de canal
associés. Les objets de canal encapsulent le comportement du canal : modélisation des temps de propagation et de
transmission, modélisation des erreurs, et éventuellement d'autres. Les canaux sont également programmables en C++
par l'utilisateur.
Les modules et les canaux sont représentés par lecModuleet cCanal classes, respectivement. cModule
et cCanal sont tous deux issus de lacComposant classer.
L'utilisateur définit des types de modules simples en sous-classantcModuleSimple.Les modules composés
sont instanciés aveccModule,bien que l'utilisateur puisse le remplacer par @classerdans le fichier NED, et
peut même utiliser une simple classe de module C++ (c'est-à-dire dérivée decSimpleModule)pour un
module composé.
lecCanal'Les sous-classes incluent les trois types de canaux intégrés : cIdealChannel, cDelayChannel et
cDatarateChannel. L'utilisateur peut créer de nouveaux types de canaux en sous-classant cCanal ou
toute autre classe de canal.
Le diagramme d'héritage suivant illustre la relation entre les classes mentionnées ci-dessus.
Des modules et des canaux simples peuvent être programmés en redéfinissant certaines fonctions
membres et en y fournissant votre propre code. Certaines de ces fonctions membres sont déclarées sur
cComposant, la classe de base commune des canaux et des modules.
cComposant a les fonctions membres suivantes destinées à la redéfinition dans les sous-classes :
• initialiser(). Cette méthode est invoquée après qu'OMNeT++ ait configuré le réseau (c'est-à-dire créé
des modules et les ait connectés conformément aux définitions), et fournit un emplacement pour le
code d'initialisation ;
• terminer() est appelé lorsque la simulation s'est terminée avec succès, et son utilisation
recommandée est l'enregistrement de statistiques récapitulatives.
52
Manuel de simulation OMNeT++ – Modules simples
cObjet
...
cComposant
cModule cCanal
Dans OMNeT++, les événements se produisent à l'intérieur de modules simples. Des modules simples encapsulent du code C++
qui génère des événements et réagit aux événements, implémentant le comportement du module.
Pour définir le comportement dynamique d'un module simple, l'une des fonctions membres suivantes doit
être remplacée :
• handleMessage(cMessage *msg).Il est invoqué avec le message en paramètre chaque fois que
le module reçoit un message.handleMessage()devraittraiterle message, puis revenir. Le temps
de simulation ne s'écoule jamais à l'intérieurhandleMessage()appels, uniquement entre eux.
• activité()est lancé comme une coroutine2 au début de la simulation, et il s'exécute jusqu'à la fin de la
simulation (ou jusqu'à ce que la fonction revienne ou se termine autrement). Les messages sont
obtenus avecrecevoir()appels. Le temps de simulation s'écoule à l'intérieurrecevoir() appels.
REMARQUE: rafraîchirAffichage()a été ajouté dans OMNeT++ 5.0. Jusque-là, les tâches liées à la
visualisation étaient généralement mises en œuvre dans le cadre dehandleMessage().
rafraîchirAffichage()fournit une solution bien supérieure et plus efficace.
53
Manuel de simulation OMNeT++ – Modules simples
4.3.1 Aperçu
Comme mentionné précédemment, un module simple n'est rien de plus qu'une classe C++ qui doit être sous-
classée à partir decModuleSimple,avec une ou plusieurs fonctions membres virtuelles redéfinies pour définir son
comportement.
La classe doit être enregistrée auprès d'OMNeT++ via leDefine_Module() macro. leDefine_Module() la
ligne doit toujours être insérée dans .ccou .cpp fifichiers et non le fichier d'en-tête (.h), car le
compilateur génère du code à partir de celui-ci.
Ce qui suitBonjourModule est à peu près le module simple le plus simple que l'on puisse écrire. (Nous
aurions pu omettre leinitialiser()méthode pour le rendre encore plus petit, mais comment dirait-il bonjour
alors ?) Remarque cModuleSimple comme classe de base, et la Define_Module() ligne.
// fichier : HelloModule.cc
# inclure <omnetpp.h> utilisation de
l'espace de noms omnetpp ;
protégé:
vide virtuel initialiser();
vide virtuel handleMessage(cMessage *msg);
} ;
annuler HelloModule::initialize() {
Afin de pouvoir faire référence à ce type de module simple dans les fichiers NED, nous avons également besoin d'une déclaration
NED associée qui pourrait ressembler à ceci :
// fichier : HelloModule.ned
FacileBonjourModule
{
portes:
saisir dans;
}
54
Manuel de simulation OMNeT++ – Modules simples
4.3.2 Constructeur
Les modules simples ne sont jamais instanciés directement par l'utilisateur, mais plutôt par le noyau de
simulation. Cela implique qu'on ne peut pas écrire de constructeurs arbitraires : la signature doit être celle
attendue par le noyau de simulation. Heureusement, ce contrat est très simple : le constructeur doit être
public, et ne doit prendre aucun argument :
Publique:
BonjourModule();// le constructeur ne prend aucun argument
Il est également acceptable d'omettre complètement le constructeur, car celui généré par le compilateur convient également.
La définition de constructeur suivante sélectionne activité()à utiliser avec le module, avec 16K de pile
coroutine :
HelloModule::HelloModule() : cSimpleModule(16384) {...}
Utilisation de base
leinitialiser()et terminer() les méthodes sont déclarées dans le cadre de cComposant, et fournir à
l'utilisateur la possibilité d'exécuter du code au début et à la fin réussie de la simulation.
La raison initialiser()existe est que vous ne pouvez généralement pas mettre de code lié à la simulation dans le
constructeur de module simple, car le modèle de simulation est toujours en cours de configuration lorsque le
constructeur s'exécute et de nombreux objets requis ne sont pas encore disponibles. En revanche,initialiser() est
appelé juste avant le début de l'exécution de la simulation, alors que tout le reste a déjà été configuré.
terminer() sert à enregistrer des statistiques, et il n'est appelé que lorsque la simulation s'est terminée
normalement. Il n'est pas appelé lorsque les simulations s'arrêtent avec un message d'erreur. le
55
Manuel de simulation OMNeT++ – Modules simples
destructor est toujours appelé à la fin, quelle que soit la façon dont la simulation s'est arrêtée, mais à ce
moment-là, il est juste de supposer que le modèle de simulation a déjà été à moitié démoli.
Sur la base des considérations ci-dessus, les conventions d'utilisation suivantes existent pour ces quatre
méthodes :
Constructeur:
Définissez les membres de pointeur de la classe de module sur nullptr ; reporter toutes les autres tâches
d'initialisation à initialiser().
initialiser():
Effectuer toutes les tâches d'initialisation : lire les paramètres du module, initialiser les variables de classe, allouer des structures
de données dynamiques avec Nouveau; également allouer et initialiser des messages automatiques (minuteries) si nécessaire.
terminer():
Enregistrer les statistiques. Fairene pas effacer quoi que ce soit ou annuler les minuteries - tout le nettoyage doit être effectué
dans le destructeur.
Destructeur :
Supprimez tout ce qui a été alloué parNouveauet est toujours détenu par la classe de module. Avec les
messages automatiques (minuteries), utilisez lesannulerEtSupprimer(msg)une fonction! Il est presque
toujours faux de simplement supprimer un message personnel du destructeur, car il pourrait se trouver
dans la liste des événements programmés. leannulerEtSupprimer(msg)la fonction vérifie d'abord cela et
annule le message avant de le supprimer si nécessaire.
OMNeT++ imprime la liste des objets inédits à la fin de la simulation. Lorsqu'un modèle de simulation
vide"objet non disposé..."messages, cela indique que les destructeurs de modules correspondants
doivent être corrigés. De manière temporaire, ces messages peuvent être masqués en configurant
print-undisposed=falsedans la configuration.
Ordre d'invocation
leinitialiser()les fonctions des modules sont appelées avant de le premier événement est traité, mais après
les événements initiaux (messages de démarrage) ont été placés dans le FES par le noyau de simulation.
Les modules simples et composés ont initialiser()les fonctions. Un module composé initialiser()la
fonction s'exécute avant de celle de ses sous-modules.
leterminer() les fonctions sont appelées lorsque la boucle d'événements s'est terminée, et seulement si elle s'est
terminée normalement.
REMARQUE: terminer() n'est pas appelée si la simulation s'est terminée par une erreur d'exécution.
L'ordre d'appel pour terminer() est l'inverse de l'ordre de initialiser() : fipremiers sous-modules, puis le
module composé englobant. 3
3La façon dont vous pouvez fournir un initialiser()la fonction d'un module composé est de sous-classer cModule,et dites à
OMNeT++ d'utiliser la nouvelle classe pour le module composé. Ce dernier se fait en ajoutant le @classe(<nom de classe>)
propriété dans la déclaration NED.
56
Manuel de simulation OMNeT++ – Modules simples
callInitialize()
{
appel à la fonction initialize() définie par l'utilisateur si (le
module est composé)
pour (chaque sous-module)
faire callInitialize() sur le sous-module
}
appelFin()
{
si (le module est composé)
pour (chaque sous-module)
faire callFinish() sur l'appel du sous-
module à la fonction finish() définie par l'utilisateur
}
Garde en tête queterminer() n'est pas toujours appelé, ce n'est donc pas un bon endroit pour le code de
nettoyage qui devrait s'exécuter à chaque fois que le module est supprimé. terminer() n'est qu'un bon endroit
pour écrire des statistiques, le post-traitement des résultats et d'autres opérations qui ne sont censées s'exécuter
qu'en cas de réussite. Le code de nettoyage doit aller dans le destructeur.
Dans les modèles de simulation où l'initialisation en une étape fournie par initialiser()n'est pas suffisant, on
peut utiliser une initialisation en plusieurs étapes. Les modules ont deux fonctions qui peuvent être
redéfinies par l'utilisateur :
Au début de la simulation, initialiser(0) est appelé pour tout modules, puis initialiser(1), initialiser(2),
etc. Vous pouvez y penser comme si l'initialisation se déroulait en plusieurs "vagues". Pour chaque
module,numInitStages()doit être redéfini pour renvoyer le nombre d'étapes d'initialisation requises,
par exemple pour une initialisation en deux étapes,numInitStages()devrait retourner 2, etinitialiser
(étape int)doit être mis en place pour gérerstade=0et étape=1cas.4
lecallInitialize()La fonction effectue l'initialisation complète en plusieurs étapes pour ce module et tous
ses sous-modules.
4Noter laconstantedans lenumInitStages()déclaration. Si vous l'oubliez, selon les règles C++, vous créez undifférentfonction au
lieu de redéfinir celle existante dans la classe de base, ainsi celle existante restera en vigueur et renverra 1.
57
Manuel de simulation OMNeT++ – Modules simples
Si vous ne redéfinissez pas les fonctions d'initialisation en plusieurs étapes, le comportement par défaut est
l'initialisation en une seule étape : la valeur par défautnumInitStages()renvoie 1, et la valeur par défautinitialiser
(étape int)appelle simplementinitialiser().
La tâche determiner() est implémenté dans plusieurs autres simulateurs en introduisant un fin de
simulationun événement. Ce n'est pas une très bonne pratique car le programmeur de simulation doit
coder les modèles (souvent représentés par des FSM) afin qu'ils puissenttoujours répondre correctement
aux événements de fin de simulation, quel que soit leur état. Cela complique souvent inutilement le code
du programme. Pour cette raison, OMNeT++ n'utilise pas l'événement de fin de simulation.
Cela se voit également dans la conception du langage de simulation PARSEC (UCLA). Son prédécesseur Maisie
utilisait des événements de fin de simulation, mais - comme documenté dans le manuel PARSEC - cela a conduit à
une programmation maladroite dans de nombreux cas, donc pour PARSEC les événements de fin de simulation
ont été abandonnés au profit determiner() (appelé finaliser() en PARSEC).
4.4.1 handleMessage()
L'idée est qu'à chaque événement (arrivée de message) nous appelons simplement une fonction définie par
l'utilisateur. Cette fonction,handleMessage(cMessage *msg)est une fonction membre virtuelle decModuleSimple
qui ne fait rien par défaut - l'utilisateur doit le redéfinir dans des sous-classes et ajouter le code de traitement du
message.
lehandleMessage()La fonction sera appelée pour chaque message qui arrive au module. La fonction
doit traiter le message et revenir immédiatement après cela. Le temps de simulation est
potentiellement différent à chaque appel. Aucun temps de simulation ne s'écoule dans un appel à
handleMessage().
La boucle d'événements à l'intérieur du simulateur gère à la foisactivité()et handleMessage()modules
simples, et il correspond au pseudocode suivant :
58
Manuel de simulation OMNeT++ – Modules simples
modules avechandleMessage()ne sont PAS démarrés automatiquement : le noyau de simulation crée des
messages de démarrage uniquement pour les modules avecactivité().Cela signifie que vous devez programmer
des messages personnels à partir duinitialiser()fonction si vous voulez unhandleMessage()module simple pour
commencer à travailler "par lui-même", sans recevoir au préalable de message des autres modules.
Pour utiliser lehandleMessage()mécanisme dans un module simple, vous devez spécifiertaille de pile
nullepour le module. Ceci est important, car cela indique à OMNeT++ que vous souhaitez utiliser
handleMessage()et pasactivité().
Fonctions liées aux messages/événements que vous pouvez utiliser danshandleMessage() :
lerecevoir()et attendre()les fonctions ne peuvent pas être utilisées dans handleMessage(), car ils sont basés
sur la coroutine par nature, comme expliqué dans la section sur activité().
Vous devez ajouter des membres de données à la classe de module pour chaque élément d'information que vous
souhaitez conserver. Ces informations ne peuvent pas être stockées dans des variables locales de
handleMessage()car ils sont détruits au retour de la fonction. De plus, ils ne peuvent pas être stockés dans des
variables statiques de la fonction (ou de la classe), car ils seraient partagés entre toutes les instances de la classe.
Les membres de données à ajouter à la classe de module incluront généralement des éléments tels que :
• d'autres variables qui appartiennent à l'état du module : nombre de tentatives, files d'attente de paquets,
etc.
• valeurs récupérées/calculées une seule fois puis stockées : valeurs des paramètres du module, indices de
porte, informations de routage, etc.
• des pointeurs d'objets message créés une seule fois puis réutilisés pour les timers, timeouts, etc.
Ces variables sont souvent initialisées à partir duinitialiser(), car les informations nécessaires pour obtenir la
valeur initiale (par exemple, les paramètres du module) peuvent ne pas encore être disponibles au moment où le
constructeur du module s'exécute.
Une autre tâche à accomplir dansinitialiser()est de programmer le(s) événement(s) initial(aux) qui déclenche(nt) le(s)
premier(s) appel(s) àhandleMessage().Après le premier appel,handleMessage()doit prendre soin de programmer d'autres
événements pour lui-même afin que la "chaîne" ne soit pas rompue. La planification d'événements n'est pas nécessaire si
votre module ne doit réagir qu'aux messages provenant d'autres modules.
terminer() est normalement utilisé pour enregistrer les informations statistiques accumulées dans les données membres
de la classe à la fin de la simulation.
59
Manuel de simulation OMNeT++ – Modules simples
Champ d'application
1. Lorsque vous prévoyez d'utiliser le module dans de grandes simulations, impliquant plusieurs milliers
de modules. Dans de tels cas, les piles de modules requises paractivité()consommerait simplement
trop de mémoire.
2. Pour les modules qui conservent peu ou pas d'informations d'état, comme les récepteurs de paquets,
handleMessage()est plus pratique à programmer.
3. D'autres bons candidats sont les modules avec un grand espace d'états et de nombreuses possibilités de
transition d'états arbitraires (c'est-à-dire lorsqu'il existe de nombreux états ultérieurs possibles pour tout
état). De tels algorithmes sont difficiles à programmer avecactivité(),et mieux adapté pour handleMessage()
(voir règle générale ci-dessous). C'est le cas de la plupart des protocoles de communication.
Les modèles de couches de protocole dans un réseau de communication ont tendance à avoir une structure commune à
un niveau élevé car, fondamentalement, ils doivent tous réagir à trois types d'événements : aux messages provenant de
protocoles de couche supérieure (ou applications), aux messages provenant de protocoles de couche inférieure. (du
réseau) et à divers temporisateurs et délais d'attente (c'est-à-dire des messages automatiques).
protégé:
// variables d'état
// ...
// ...
si(msg->isSelfMessage())
processTimer(msg);
sinon si(msg->arrivedOn("fromNetw"))
processMsgFromLowerLayer(check_and_cast<FooPacket *>(msg));
autre
processMsgFromHigherLayer(msg);
}
60
Manuel de simulation OMNeT++ – Modules simples
Le code pour les générateurs de paquets simples et les puits programmés avechandleMessage()peut être
aussi simple que le pseudo-code suivant :
PacketGenerator::handleMessage(msg) {
PacketSink::handleMessage(msg) {
effacer msg ;
}
Le module simple suivant génère des paquets avec un temps inter-arrivée exponentiel. (Certains
détails dans la source n'ont pas encore été discutés, mais le code est probablement compréhensible
néanmoins.)
Publique:
Générateur() : cSimpleModule() {} protégé:
Define_Module(Générateur);
61
Manuel de simulation OMNeT++ – Modules simples
Un exemple un peu plus réaliste consiste à réécrire notre générateur pour créer des rafales de paquets, chacun
composé deburstLengthpaquets.
Nous ajoutons quelques membres de données à la classe :
• burstLengthstockera le paramètre qui spécifie le nombre de paquets qu'une rafale doit contenir,
Le code:
classerBurstyGenerator :Publique cModuleSimple {
protégé:
entierburstLength ;
entierburstCounter ;
Define_Module(BurstyGenerator);
annuler BurstyGenerator::initialize() {
}
autre {
// programmer le prochain envoi dans la rafale
scheduleAt(simTime()+exponentiel(1.0), msg);
}
}
62
Manuel de simulation OMNeT++ – Modules simples
Avantages:
• consomme moins de mémoire : aucune pile séparée n'est nécessaire pour les modules simples
• rapide : l'appel de fonction est plus rapide que la commutation entre les coroutines
Les inconvénients:
• les variables locales ne peuvent pas être utilisées pour stocker des informations d'état
Autres simulateurs
De nombreux packages de simulation utilisent une approche similaire, souvent surmontée de quelque chose comme une
machine à états (FSM) qui masque les appels de fonction sous-jacents. De tels systèmes sont :
4.4.2 activité()
Avecactivité(),un module simple peut être codé comme un processus ou un thread du système d'exploitation. On
peut attendre un message entrant (événement) à n'importe quel moment du code, suspendre l'exécution
pendant un certain temps (temps modèle !), etc. Lorsque leactivité()la fonction se termine, le module est terminé.
(La simulation peut continuer s'il y a d'autres modules qui peuvent fonctionner.)
Les fonctions les plus importantes qui peuvent être utilisées dans activité()sont (ils seront discutés en détail
plus tard):
leactivité()fonction contient normalement une boucle infinie, avec au moins un attendre()ou recevoir()
appeler dans son corps.
63
Manuel de simulation OMNeT++ – Modules simples
Champ d'application
activité()
{
tandis que (vrai)
{
ouvrir la connexion en envoyant la commande OPEN à la couche transport
recevoir la réponse de la couche transport
si (ouverture échouée) {
S'il est nécessaire de gérer plusieurs connexions simultanément, la création dynamique de modules simples pour
gérer chacune est une option. La création de modules dynamiques sera abordée plus tard.
Il y a des situations où vous avez certainementne veut pasutiliseractivité().Si laactivité() la fonction ne
contient pasattendre()et il n'en a qu'unrecevoir()en haut d'une messagerie
64
Manuel de simulation OMNeT++ – Modules simples
boucle, il ne sert à rien d'utiliseractivité(),et le code doit être écrit avechandleMessage().Le corps de la
boucle deviendrait alors le corps dehandleMessage(), variables d'état à l'intérieuractivité()
deviendraient des membres de données dans la classe de module, et ils seraient initialisés dans
initialiser().
Exemple:
annuler Couler :: activité ()
{
tandis que(vrai) {
msg = recevoir();
effacer msg ;
}
}
effacer msg ;
}
activité()est exécuté dans une coroutine. Les coroutines sont similaires aux threads, mais sont
planifiées de manière non préventive (ceci est également appelé multitâche coopératif). On peut
passer d'une coroutine à une autre coroutine par untransferTo(otherCoroutine) appel, provoquant la
suspension de la première coroutine et l'exécution de la seconde. Plus tard, lorsque la deuxième
coroutine effectue unetransferTo(firstCoroutine) appel à la première, l'exécution de la première
coroutine reprendra à partir du point de transferTo(otherCoroutine) appeler. L'état complet de la
coroutine, y compris les variables locales, est préservé pendant que le fil d'exécution se trouve dans
d'autres coroutines. Cela implique que chaque coroutine a sa propre pile CPU, etTransférer à()
implique un passage d'une pile CPU à une autre.
Les coroutines sont au cœur d'OMNeT++, et le programmeur de simulation n'a jamais besoin d'appeler Transférer à() ou
d'autres fonctions dans la bibliothèque de coroutines, et il n'a pas non plus besoin de se soucier de l'implémentation de
la bibliothèque de coroutines. Il est important de comprendre, cependant, comment la boucle d'événements trouvée
dans les simulateurs d'événements discrets fonctionne avec les coroutines.
65
Manuel de simulation OMNeT++ – Modules simples
recevoir()
{
transferTo(main)
récupérer l'événement en cours
renvoie l'événement // rappelez-vous : événements = messages
}
attendre()
{
créer un événement e
programmez-le à (heure sim. actuelle + intervalle d'attente)
transferTo(main)
récupérer l'événement en cours
si (l'événement en cours n'est pas e) {
Erreur
}
supprimer e // note : impl réel. réutilise les événements
retour
}
Ainsi, lerecevoir()et attendre()les appels sont des points spéciaux dans leactivité()fonction, parce qu'ils
sont là où
Messages de démarrage
Le programmeur de simulation doit définir la taille de la pile CPU pour les coroutines. Cela ne peut pas être
automatisé.
16 ou 32 Ko est généralement un bon choix, mais plus d'espace peut être nécessaire si le module utilise des fonctions
récursives ou a de nombreuses/grandes variables locales. OMNeT++ possède un mécanisme intégré qui détecte
généralement si la pile de modules est trop petite et déborde. OMNeT++ peut également signaler la quantité d'espace de
pile qu'un module utilise réellement au moment de l'exécution.
66
Manuel de simulation OMNeT++ – Modules simples
Parce que les variables locales deactivité()sont conservés à travers les événements, vous pouvez tout y
stocker (informations d'état, tampons de paquets, etc.). Les variables locales peuvent être initialisées en
haut duactivité()fonction, donc il n'y a pas beaucoup besoin d'utiliser initialiser().
Vous avez besoin terminer(),cependant, si vous voulez écrire des statistiques à la fin de la simulation. Parce
queterminer() ne peut pas accéder aux variables locales de activité(),vous devez mettre les variables et les
objets contenant les statistiques dans la classe du module. Tu n'as toujours pas besoin initialiser()parce que
les membres de la classe peuvent également être initialisés en haut de activité().
Ainsi, une configuration typique ressemble à ceci en pseudocode :
classe MonModuleSimple... {
...
variables pour l'activité de collecte de
statistiques();
terminer();
} ;
MonModuleSimple::activité() {
MonModuleSimple::finish() {
Avantages:
• initialiser()pas nécessaire, l'état peut être stocké dans des variables locales de activité()
• la description de style processus est un modèle de programmation naturel dans certains cas
Les inconvénients:
• évolutivité limitée : les piles de coroutines peuvent augmenter de manière inacceptable les besoins en
mémoire du programme de simulation s'il y en a beaucoup activité()-modules simples basés;
• surcharge d'exécution : la commutation entre les coroutines est plus lente qu'un simple appel de fonction
• n'encourage pas un bon style de programmation : à mesure que la complexité des modules augmente, activité()
tend à devenir une grande fonction monolythique.
Dans la plupart des cas, les inconvénients l'emportent sur les avantages et il est préférable d'utiliser handleMessage()plutôt.
67
Manuel de simulation OMNeT++ – Modules simples
Autres simulateurs
Les coroutines sont utilisées par un certain nombre d'autres packages de simulation :
• Tous les logiciels de simulation qui héritent de SIMULA (par exemple C++SIM) sont basés sur des
coroutines, bien que dans l'ensemble le modèle de programmation soit assez différent.
• Le langage de simulation/programmation parallèle Maisie et son successeur PARSEC (de UCLA) utilisent
également des coroutines (bien qu'implémentées avec des threads préemptifs « normaux »). La philosophie
est assez similaire à OMNeT++. PARSEC, étant "juste" un langage de programmation, il a une syntaxe plus
élégante mais beaucoup moins de fonctionnalités que OMNeT++.
• De nombreuses bibliothèques de simulation basées sur Java sont basées sur des threads Java.
Si possible, évitez d'utiliser des variables globales, y compris des membres de classe statiques. Ils sont susceptibles de
causer plusieurs problèmes. Premièrement, ils ne sont pas réinitialisés à leurs valeurs initiales (à zéro) lorsque vous
reconstruisez la simulation dans Tkenv/Qtenv, ou démarrez une autre exécution dans Cmdenv. Cela peut produire des
résultats surprenants. Deuxièmement, ils vous empêchent de paralléliser la simulation. Lors de l'utilisation de la
simulation parallèle, chaque partition du modèle s'exécute dans un processus séparé, ayant ses propres copies de
variables globales. Ce n'est généralement pas ce que vous voulez.
La solution consiste à encapsuler les variables dans des modules simples en tant que membres de données privés
ou protégés, et à les exposer via des méthodes publiques. D'autres modules peuvent ensuite appeler ces
méthodes publiques pour obtenir ou définir les valeurs. Les méthodes d'appel d'autres modules seront abordées
dans la section 4.12. Des exemples de tels modules sont lesTableau noirdans leCadre de mobilité, et Table
d'interface et Table de routage dans leCadre INET.
Le code des modules simples peut être réutilisé via le sous-classement et la redéfinition des fonctions membres
virtuelles. Un exemple:
protégé:
vide virtuel recalculateTimeout();
} ;
Define_Module(TransportProtocolExt);
annuler TransportProtocolExt::recalculateTimeout() {
//...
}
@class(TransportProtocolExt); // Important!
}
68
Manuel de simulation OMNeT++ – Modules simples
Les paramètres de module déclarés dans les fichiers NED sont représentés par le cPar classe au moment de
l'exécution, et accessible en appelant la par() fonction membre de cComposant :
cPar'La valeur de s peut être lue avec des méthodes correspondant au type NED du paramètre : boolValue(),
longValue(), doubleValue(), stringValue(), stdstringValue(), xmlValue(). Il existe également des opérateurs de
conversion de type surchargés pour les types correspondants (boo; types entiers, y compris entier, long, etc;
double; caractère const * ; cXMLElement *).
long numJobs = par("numJobs").longValue();
doubletraitementDelay = par("processingDelay"); // en utilisant l'opérateur double()
Noter quecPar a deux méthodes pour renvoyer une valeur de chaîne : valeur de chaîne(), qui revient
caractère const *, et stdstringValue(), qui revient std :: chaîne. Pour les paramètres volatils,
uniquement stdstringValue() peuvent être utilisés, mais sinon les deux sont interchangeables.
Si vous utilisez le par("foo") paramètre dans les expressions (telles que 4*par("foo")+2), le compilateur
C++ peut être incapable de décider entre les opérateurs surchargés et signaler l'ambiguïté. Ce
problème peut être résolu en ajoutant un cast explicite tel que (double)par("foo"), ou en utilisant le
doubleValeur() ou valeurlong() méthodes.
Un paramètre peut être déclaré volatildans le fichier NED. levolatilmodificateur indique qu'un paramètre est relu
chaque fois qu'une valeur est nécessaire pendant la simulation. Les paramètres volatils sont généralement
utilisés pour des choses comme l'intervalle de génération de paquets aléatoires, et se voient attribuer des valeurs
telles queexponentiel(1.0) (nombres tirés de la distribution exponentielle avec une moyenne de 1,0).
En revanche, les paramètres NED non volatils sont des constantes et la lecture de leurs valeurs plusieurs
fois garantit la même valeur. Lorsqu'un paramètre non volatil se voit attribuer une valeur aléatoire comme
exponentiel(1.0), il est évalué une fois au début de la simulation et remplacé par le résultat, de sorte que
toutes les lectures obtiendront la même valeur (générée de manière aléatoire).
L'utilisation typique des paramètres non volatils est de les lire dans le initialiser()de la classe du module et
stockez les valeurs dans des variables de classe pour un accès facile ultérieurement :
classerLa source : Publique cModuleSimple {
protégé:
long nombre d'emplois ;
vide virtuel initialiser(); . . .
} ;
69
Manuel de simulation OMNeT++ – Modules simples
nombreJobs =
par("nombreJobs"); . . .
}
volatilles paramètres doivent être relus chaque fois que la valeur est nécessaire. Par exemple, un
paramètre qui représente un intervalle de génération de paquet aléatoire peut être utilisé comme ceci :
annuler Source::handleMessage(cMessage *msg) {
...
scheduleAt(simTime() + par("interval").doubleValue(), timerMsg); . . .
Ce code recherche le paramètre par son nom à chaque fois. Cette recherche peut être évitée en stockant le
pointeur de l'objet paramètre dans une variable de classe, ce qui donne le code suivant :
classerLa source : Publique cModuleSimple {
protégé:
cPar *intervalle ;
vide virtuel initialiser();
vide virtuel handleMessage(cMessage *msg); . . .
} ;
intervallep = &par("intervalle"); . . .
...
scheduleAt(simTime() + intervalp->doubleValue(), timerMsg); . . .
Les valeurs des paramètres peuvent être modifiées à partir du programme, pendant l'exécution. Ceci est rarement nécessaire,
mais peut être utile pour certains scénarios.
REMARQUE: Le type du paramètre ne peut pas être modifié lors de l'exécution – il doit rester le type déclaré dans
le fichier NED. Il n'est pas non plus possible d'ajouter ou de supprimer des paramètres de module lors de
l'exécution.
Les méthodes pour définir la valeur du paramètre sont setBoolValue(), setLongValue(), setString-
Value(), setDoubleValue(), setXMLValue(). Il existe également des opérateurs d'affectation surchargés
pour différents types, notamment booléen, int, long, double, const char *, et cXMLElement
*.
70
Manuel de simulation OMNeT++ – Modules simples
Pour autoriser un module à être notifié des changements de paramètres, remplacez son handleParameter-
Change() méthode, voir 4.5.5.
Le nom et le type du paramètre sont retournés par le obtenirNom() et getType() méthodes. Ce dernier
renvoie une valeur à partir d'une énumération, qui peut être convertie en une chaîne lisible avec le
getTypeName() méthode statique. Les valeurs d'énumération sontBOOL, DOUBLE, LONG, CHAÎNE et XML ;
et puisque l'énumération est un type interne, ils doivent généralement être qualifiés avec cPar ::.
estvolatile() renvoie si le paramètre a été déclaré volatile dans le fichier NED. estNumérique() renvoie
true si le type de paramètre est double ou long.
lechaîne() La méthode renvoie la valeur du paramètre sous forme de chaîne. Si le paramètre contient une
expression, la représentation sous forme de chaîne de l'expression est renvoyée.
Un exemple d'utilisation des méthodes ci-dessus :
cPar& p = par(i);
EV << "paramètre : " << p.getName() << "\n" ; EV << "
type :" << cPar::getTypeName(p.getType()) << "\n" ; contient :" <<
EV << " p.str() << "\n" ;
}
Les propriétés NED d'un paramètre sont accessibles avec la getProperties() méthode qui renvoie un
pointeur vers cPropriétés objet qui stocke les propriétés de ce paramètre. Spécifiquement,getUnit()
renvoie l'unité de mesure associée au paramètre (@unité propriété dans NED).
Davantage cPar méthodes et classes associées comme cExpression et cDynamicExpressionsont utilisés par
l'infrastructure NED pour configurer et attribuer des paramètres. Ils sont documentés dans le Référence API,
mais ils présentent normalement peu d'intérêt pour les utilisateurs.
Depuis la version 4.2, OMNeT++ ne prend pas en charge les tableaux de paramètres, mais en pratique, ils peuvent
être émulés à l'aide de paramètres de chaîne. On peut assigner au paramètre une chaîne qui contient toutes les
valeurs sous forme textuelle (par exemple, "0 1,234 3,95 5,467"),puis analysez cette chaîne dans le module simple.
lecStringTokenizerclass peut être très utile à cette fin. Le constructeur accepte une chaîne, qu'il
considère comme une séquence de jetons (mots) séparés par des caractères délimiteurs (par défaut,
des espaces). Ensuite, vous pouvez soit énumérer les jetons et les traiter un par un (hasMoreTokens(),
nextToken()),ou utilisez l'un descStringTokenizerdes méthodes pratiques pour les convertir en un
vecteur de chaînes (asVector()),entiers (asIntVector()),ou doubles (asDoubleVector()).
et
71
Manuel de simulation OMNeT++ – Modules simples
4.5.5 handleParameterChange()
Il est possible que les modules soient avertis lorsque la valeur d'un paramètre change au moment de l'exécution,
éventuellement en raison d'un autre module qui le modifie dynamiquement. Une utilisation typique consiste à relire le
paramètre modifié et à mettre à jour l'état du module si nécessaire.
REMARQUE: Les notifications sont désactivées lors de l'initialisation du composant, car elles
rendraient très difficile l'écriture de composants qui fonctionnent de manière fiable dans toutes les
conditions. handleParameterChange() est généralement déclenché à partir d'un autre module (il n'est
pas très logique qu'un module modifie ses propres paramètres), donc l'ordre relatif de initialiser()et
handleParameterChange() serait effectivement déterminé par l'ordre d'initialisation des modules, sur
lequel on ne peut généralement pas compter. Une fois la dernière étape de l'initialisation du
composant terminée,handleParameterChange() est appelé par le noyau de simulation avec nullptr
comme nom de paramètre. Cela permet au composant de réagir aux changements de paramètres qui
se sont produits pendant la phase d'initialisation.
L'exemple suivant montre un module qui relit son temps de serviceparamètre lorsque sa valeur
change :
annuler File d'attente ::handleParameterChange(caractère constant*nom de famille) {
si(strcmp(parname, "serviceTime")==0)
serviceTime = par("serviceTime"); // actualiser le membre de données
}
72
Manuel de simulation OMNeT++ – Modules simples
Si votre code dépend fortement des notifications et que vous souhaitez également recevoir des
notifications lors de l'initialisation ou de la finalisation, une solution consiste à appeler explicitement
handleParameter-Change() du initialiser()ou terminer() une fonction:
pour(entierje = 0 ; je < getNumParams(); je++)
handleParameterChange(par(i).getName());
REMARQUE: Soyez extrêmement prudent lorsque vous modifiez les paramètres de l'intérieur
handleParameter-Change(), car il est facile de créer accidentellement une boucle de notification infinie.
Les portes des modules sont représentées par cGateobjets. Les objets porte savent à quelles autres
portes ils sont connectés et quels sont les objets canal associés aux liens.
lecModuleLa classe a un certain nombre de fonctions membres qui traitent des portes. Vous pouvez rechercher une
porte par son nom en utilisant leportail()méthode:
Cela fonctionne pour les portes d'entrée et de sortie. Cependant, lorsqu'une porte a été déclaréeinoutdans
NED, il est en fait représenté par le noyau de simulation avec deux portes, donc l'appel ci-dessus se
traduirait par un portail introuvable Erreur. leportail()La méthode doit savoir si l'entrée ou la sortie de la
moitié de la porte dont vous avez besoin. Cela peut être fait en ajoutant le "$je" ou "$o" au nom de la porte.
L'exemple suivant récupère les deux portes pour la porte inout "g":
cGate *gIn = gate("g$i"); cGate
*gOut = gate("g$o");
Une autre façon consiste à utiliser le gateHalf() fonction, qui prend le nom de la porte inout plus soit
cGate::INPUT ou cGate::SORTIE :
cGate *gIn = gateHalf("g", cGate::INPUT); cGate *gOut =
gateHalf("g", cGate::OUTPUT);
Ces méthodes génèrent une erreur si la porte n'existe pas, elles ne peuvent donc pas être utilisées
pour déterminer si le module a une porte particulière. A cet effet il existe unaPorte()méthode. Un
exemple:
si (hasGate("optOut"))
envoyer(NouveaucMessage(), "optOut");
Une porte peut également être identifiée et recherchée par un ID de porte numérique. Vous pouvez obtenir l'ID
de la porte elle-même (getId()méthode), ou depuis le module par nom de porte (findGate()méthode). le portail()La
méthode a également une variante surchargée qui renvoie la porte à partir de l'ID de porte.
Comme les identifiants de porte sont plus utiles avec les vecteurs de porte, nous les aborderons en détail dans une section ultérieure.
73
Manuel de simulation OMNeT++ – Modules simples
Vecteurs de porte
Les vecteurs de porte en possèdent uncGateobjet par élément. Pour accéder à des portes individuelles dans
le vecteur, vous devez appeler leportail()fonction avec un supplémentindiceparamètre. L'indice doit être
compris entre zéro etTaille-1. La taille du vecteur porte peut être lue avec lagateSize() méthode. L'exemple
suivant parcourt tous les éléments du vecteur de porte :
Un vecteur de porte ne peut pas avoir de "trous" ; C'est,portail()ne revient jamais nullptrou génère une
erreur si le vecteur de porte existe et que l'index est dans les limites.
Pour les portails d'entrée, gateSize() peut être appelé avec ou sans le "$je"/"$o" suffixe et renvoie le
même nombre.
leaPorte()peut être utilisée à la fois avec et sans index, et elles signifient deux choses différentes : sans
index, elle indique l'existence d'un vecteur de porte avec le nom donné, quelle que soit sa taille (elle
renvoie vrai pour un vecteur existant même si sa taille est actuellement nulle !); avec un index, il
examine également si l'index est dans les limites.
ID de porte
Une porte est également accessible par son ID. Une propriété très importante des identifiants de porte est qu'ils
sont contigu dans un vecteur de porte, c'est-à-dire l'ID d'une porte g[k] peut être calculé comme l'ID de g[0] plus k
. Cela vous permet d'accéder efficacement à n'importe quelle porte dans un vecteur de porte, car la récupération
d'une porte par ID est plus efficace que par nom et index. L'indice de la première porte peut être obtenu avec
gate("out",0)->getId(), mais il vaut mieux utiliser une méthode dédiée, gateBaseId(), car cela fonctionne également
lorsque la taille du vecteur de porte est nulle.
Deux autres propriétés importantes des identifiants de porte : ils sont stable et unique (dans le module). Par stable, nous
entendons que l'ID d'une porte ne change jamais ; et par unique, nous entendons non seulement qu'à un moment donné, deux
portes n'ont pas les mêmes identifiants, mais également que les identifiants des portes supprimées ne sont pas réutilisés
ultérieurement, de sorte que les identifiants de porte sont uniques dans la durée de vie d'une simulation.
REMARQUE: La version OMNeT++ antérieure à 4.0 n'avait pas ces garanties - le redimensionnement d'un vecteur
de porte pouvait entraîner le déplacement de sa plage d'ID, s'il avait chevauché la plage d'ID d'autres vecteurs de
porte. OMNeT++ 4.x résout le même problème en interprétant l'ID de porte comme un champ de bits, contenant
essentiellement des bits qui identifient le nom de la porte et d'autres bits qui contiennent l'index. Cela signifie
également que la limite supérieure théorique pour une taille de porte est maintenant plus petite, bien qu'elle soit
encore suffisamment grande pour qu'elle puisse être ignorée en toute sécurité à des fins pratiques.
74
Manuel de simulation OMNeT++ – Modules simples
Si vous devez passer par toutes les portes d'un module, il y a deux possibilités. L'un invoque le
getGateNames() méthode qui renvoie les noms de toutes les portes et vecteurs de porte du module ;
alors tu peux appelerisGateVector(nom) pour déterminer si des noms individuels identifient une porte
scalaire ou un vecteur de porte ; alors les vecteurs de porte peuvent être énumérés par index. Aussi,
pour les portes d'entréegetGateNames() renvoie le nom de base sans le "$je"/"$o" suffixe, les deux
directions doivent donc être traitées séparément. legateType(nom) peut être utilisée pour tester si
une porte est entrée, entrée ou sortie (elle renvoie cGate::INOUT, cGate::INPUT, ou cGate::OUTPUT).
De toute évidence, la solution ci-dessus peut être assez difficile. Une alternative consiste à utiliser leGateIterator
classe assurée par cModule. Ça va comme ça:
Où cette désigne le module dont les portes sont énumérées (il peut être remplacé par n'importe quel
cModule * variable).
REMARQUE: Dans les versions antérieures d'OMNeT++, les ID de porte étaient de petits nombres entiers, il
était donc logique d'itérer sur toutes les portes d'un module en énumérant tous les ID de zéro à un
maximum, en sautant les trous (nullptrs). Ce n'est plus le cas avec OMNeT++ 4.0 et les versions ultérieures.
De plus, leportail()la méthode génère maintenant une erreur lorsqu'elle est appelée avec un ID invalide, et
ne se contente pas de renvoyer nullptr.
Bien que rarement nécessaire, il est possible d'ajouter et de supprimer des portes pendant la simulation. Vous pouvez ajouter
des portes scalaires et des vecteurs de porte, modifier la taille des vecteurs de porte et supprimer des portes scalaires et des
vecteurs de porte entiers. Il n'est pas possible de supprimer des portes aléatoires individuelles d'un vecteur de porte, de
supprimer la moitié d'une porte inout (par exemple "porte$o"),ou pour définir différentes tailles de vecteur de porte sur les deux
moitiés d'un vecteur de porte inout.
REMARQUE: Lorsque l'efficacité de la mémoire est préoccupante, il est utile de savoir que dans OMNeT++ 4.0 et
versions ultérieures, un vecteur de porte consommera beaucoup moins de mémoire que le même nombre de
portes scalaires individuelles.
Méthodes cGate
leobtenirNom() méthode de cGaterenvoie le nom de la porte ou du vecteur de porte sans l'index. Si vous avez
besoin d'une chaîne contenant également l'index de la porte,obtenirNomComplet() est ce que vous voulez. Si vous
souhaitez également inclure le nom hiérarchique du module propriétaire, appelezgetFullPath().
legetType() méthode de cGaterenvoie le type de porte, soit cGate::INPUT ou cGate::SORTIE. (Il ne peut
pas revenir cGate::INOUT, parce qu'une porte inout est représentée par une paire de cGates.)
75
Manuel de simulation OMNeT++ – Modules simples
Si vous avez une porte qui représente la moitié d'une porte inout (c'est-à-dire, obtenirNom() renvoie
quelque chose comme "g$i" ou "g$o"), vous pouvez diviser le nom avec le getBaseName() et
getNameSuffix() méthodes. getBaseName() la méthode renvoie le nom sans le $je/$o suffixe; et
getName-Suffix() renvoie uniquement le suffixe (y compris le signe dollar). Pour les portes normales,
getBaseName() est le même que obtenirNom(), et getNameSuffix() renvoie la chaîne vide.
leestVector(), getIndex(), getVectorSize() parler pour eux-mêmes; Taille() est un alias pour
getVectorSize(). Pour les portes non vectorielles, obtenirIndex() renvoie 0 et getVectorSize() renvoie 1.
4.6.2 Connexions
Les portes de module simples ont normalement une connexion attachée. Les portes des modules composés,
cependant, doivent être connectées à la fois à l'intérieur et à l'extérieur du module pour être utiles. Une série de
connexions (jointes avec des portes de modules composés) est appelée unchemin de connexionou simplement
chemin. Un chemin est dirigé et commence normalement à une porte de sortie d'un module simple, se termine à
une porte d'entrée d'un module simple et passe par plusieurs portes de module composé.
TouscGateL'objet contient des pointeurs vers la porte précédente et la porte suivante dans le chemin
(renvoyé par legetPreviousGate()et getNextGate()méthodes), de sorte qu'un chemin peut être considéré
comme une liste à double lien.
L'utilisation de laporte précédenteet prochaine portepointeurs avec différents types de portes est illustré sur la
figure 4.2.
Les portes de début et de fin du chemin peuvent être trouvées avec legetPathStartGate()et
getPathEndGate()méthodes, qui suivent simplement lesporte précédenteet prochaine portepointeurs,
respectivement, jusqu'à ce qu'ils soientnullptr.
leisConnectedOutside()et isConnectedInside()Les méthodes renvoient si une porte est connectée à
l'extérieur ou à l'intérieur. Ils examinent soit leprécédentou lasuivant pointeur, selon le type de porte
(entrée ou sortie). Par exemple, une porte de sortie estconnecté à l'extérieursi lasuivantle pointeur
n'est pasnullptr ; la même fonction pour une porte d'entrée vérifie la
76
Manuel de simulation OMNeT++ – Modules simples
(une) (b)
(c) (ré)
Figure 4.2 : (a) porte de sortie du module simple, (b) porte de sortie du module composé, (c) porte d'entrée du
module simple, (d) porte d'entrée du module composé
précédentaiguille. Encore une fois, voir la figure 4.2 pour une illustration.
leest connecté()est un peu différente : elle renvoie true si la porte estpleinementconnecté, c'est-à-dire pour une
porte de module composé à la fois à l'intérieur et à l'extérieur, et pour une porte de module simple, à l'extérieur.
Le code suivant imprime le nom de la porte à laquelle une simple porte de module est connectée :
L'objet canal associé à une connexion est accessible par un pointeur stocké à la porte source de la
connexion. Le pointeur est renvoyé par legetChannel()méthode de la porte:
cChannel *channel = gate->getChannel();
Le résultat peut êtrenullptr,c'est-à-dire qu'une connexion peut ne pas avoir d'objet canal associé.
Si vous avez un pointeur de canal, vous pouvez récupérer sa porte source avec legetSourceGate()
méthode:
cGate *gate = canal->getSourceGate();
cCanal est juste une classe de base abstraite pour les canaux, donc pour accéder aux détails du canal, vous
devrez peut-être convertir le pointeur résultant dans une classe de canal spécifique, par exemplecDelay-
Canalou cDatarateChannel.
Un autre type de canal spécifique estcIdealChannel,qui ne fait fondamentalement rien : il agit comme
s'il n'y avait aucun objet de canal affecté à la connexion. OMNeT++ insère parfois de manière
transparente uncIdealChanneldans une connexion sans canal, par exemple pour contenir la chaîne
d'affichage associée à la connexion.
Souvent, vous n'êtes pas vraiment intéressé par le canal d'une connexion spécifique, mais plutôt par le canal de
transmission (voir 4.7.6) du chemin de connexion qui commence à une porte de sortie spécifique. le
77
Manuel de simulation OMNeT++ – Modules simples
canal de transmission peut être trouvé en suivant le chemin de connexion jusqu'à ce que vous
trouviez un canal dont isTransmissionChannel() la méthode renvoie vrai,mais cGatea une méthode
pratique pour cela, nommée getTransmissionChannel(). Un exemple d'utilisation :
cChannel *txChan = gate("ppp$o")->getTransmissionChannel();
Les deux méthodes génèrent une erreur si aucun canal de transmission n'est trouvé. Si cela ne
convient pas, utilisez le mêmefindTransmissionChannel()et findIncomingTransmissionChannel()
méthodes qui retournent simplementnullptrdans ce cas.
Les canaux sont traités plus en détail dans la section 4.8.
À un niveau abstrait, un modèle de simulation OMNeT++ est un ensemble de modules simples qui
communiquent entre eux via la transmission de messages. L'essence des modules simples est qu'ils créent,
envoient, reçoivent, stockent, modifient, planifient et détruisent des messages - le reste d'OMNeT++ existe
pour faciliter cette tâche et collecter des statistiques sur ce qui se passait.
Les messages dans OMNeT++ sont des instances decMessage classe ou l'une de ses sous-classes. Les
paquets réseau sont représentés parcPaquet,qui est également sous-classé decMessage.Les objets de
message sont créés à l'aide de C++Nouveauopérateur, et détruit à l'aide dueffacer l'opérateur lorsqu'ils ne
sont plus nécessaires.
Les messages sont décrits en détail au chapitre 5. À ce stade, tout ce que nous devons savoir à leur
sujet est qu'ils sont appeléscMessage *pointeurs. Dans les exemples ci-dessous, les messages seront
créés avecnouveau cMessage("foo")où "fou"est un nom de message descriptif, utilisé à des fins de
visualisation et de débogage.
Presque tous les modèles de simulation doivent planifier des événements futurs afin d'implémenter des temporisateurs, des
délais d'attente, des retards, etc. Quelques exemples typiques :
• Un module source qui crée et envoie périodiquement des messages doit programmer le prochain
envoi après chaque opération d'envoi ;
• Un serveur qui traite des travaux à partir d'une file d'attente doit démarrer un temporisateur chaque fois qu'il commence
à traiter un travail. Lorsque la minuterie expire, le travail terminé peut être envoyé et un nouveau travail peut commencer
à être traité ;
• Lorsqu'un paquet est envoyé par un protocole de communication qui utilise la retransmission, il doit
planifier un délai d'attente afin que le paquet puisse être retransmis si aucun accusé de réception
n'arrive dans un certain laps de temps.
Dans OMNeT++, vous résolvez de telles tâches en laissant le module simple s'envoyer un message à lui-
même ; le message serait délivré au module simple ultérieurement. Les messages utilisés de cette façon
sont appelésauto-messages, et la classe module a pour eux des méthodes spéciales qui permettent
d'implémenter des messages automatiques sans portes ni connexions.
78