3 NED語言

3.1 NED簡介

使用者使用NED語言來描述模擬模型的結構,NED字面上的意思就是網路描述(Network Description)。NED可以讓使用者宣告簡單模組,連結它們以及將它們組合成複合式模組。使用者可以將某些複合式模組標記成為網路(networks) — 一個完整的模擬模型。通道(channels)是另外一個元件型別,它的實例(instances)也可以被使用在複合式模組中。

NED語言有很多很棒的特點可以讓它很適合用在大型的專案中:

  • 階層式(Hierarchical)。對於處理大型複雜的系統,階層式架構是一種傳統的處理方式。在OMNeT++中,任何過於複雜的模組都可以被切分成為較小的模組加以實現,然後將這些較小的模組組合成複合式模組來實現原本複雜模組的功能。
  • 基於元件的(Component-Based)。簡單模組與複合式模組都是可以被繼承、被重複使用的。它不僅僅是減少程式碼複製而以,更重要的是它能夠實踐成為元件函式庫(component libraries)(像是INET Framwork, MiXiM, 和 Castalia等函式庫)
  • 介面(interface)。模組跟通道介面被用來作為模組與通道被使用時的一個代表(或稱為佔位符,placeholder),實際的模組型別需要去實作(implement)介面所定義的功能。舉例來說,如果有一個名為MobileHost的複合式模組,它包含一個IMobility型別的子模組,命名為mobility。這裡的IMobility為一個模組介面,因此實際上的mobility模組可以從實作IMobility介面的模組型別中選取一個來使用(例如:RandomWalkMobility模組型別, TurtleMobility模組型別等)(註*3-1)。
  • 繼承(inheritance)。模組與通道可以是子類別。衍生類別與通道可以加入新的參數、閘、以及新的子模組與連線(在複合式模組中)。衍生類別中可以將已有的參數給予固定的數值,設定閘的數目,這樣讓我們可以很容易的,舉例來說,將GenericTCPClientApp模組中的某些參數設定為定值衍生成為FTPClientApp模組,或將BaseHost複合式模組加上WebClientApp子模組並將WebClientAPP連線到BaseHost內部的TCP子模組,衍生出新的WebClientHost複合式模組。
  • 套件(Packages)。NED語言使用類似Java程式語言的套件結構(package)以減少不同模組間名稱衝突的問題。NEDPATH(類似於Java中的CLASSPATH)被使用來指定不同模組間的相依性(dependencies)。
  • 內部型別(inner type)。如果通道型別以及模組型別只是被一個複合式模組所使用,則可以將其定義在此複合式模組中以減少名稱空間汙染(namespace pollution)。
  • 後設資料註解(Metadata annotations)。藉由增加屬性(properties)提供模組或是通道型別、參數、閘、子模組註解,後設資料不會直接的被模擬核心使用,但它可以攜帶額外的資料給不同的工具、模擬執行環境(runtime environment)使用,甚至是給模型中其他模組使用。舉例來說,模組的圖形化介面表示(圖示等)或是參數的提示字串與測量單位(milliwatt毫瓦特等)都已經視為是後設資料註解。

(譯註*3-1:參考物件導向程式設計中關於介面的介紹,這是一樣的概念)。

NOTE
NED語言在4.0版有一個很大個改版,繼承、介面、套件、內部型別、後設資料註解、輸入輸出閘等,以及一些其他的功能都在4.0 release版中加入。因為這最基本的語法都已經改變,舊的NED檔案需要被轉換成為新的語法才能使用。不過這有自動工具可以使用,手動轉換僅僅在需要使用到4.0版的新功能時使用。

NED語言具有同義的樹表示法,因此可以被序列化成為XML語言。意思是說NED可以被轉換成XML,也可以由XML轉換回來並不會產生任何資料損失,包含註解。這降低了使用NED語言的障礙,例如,由其他像是SQL資料庫的系統抽取資訊、重構與轉換NED、產生NED等。

NOTE
本章將會逐步地使用範例來介紹NED的使用,如果你想要查看正規、簡明的說明請看附錄20。

3.2 NED 快速學習

在這一節我們藉由一個完整、合理且實際的通訊網路的例子來介紹NED語言。

我們假設的網路遊節點(nodes)所組成,每一個節點都有一個應用程式在執行且以隨機的時間間隔產生封包。節點同時也扮演路由器(router)的腳色。我們假設這些應用程式使用datagram-based的通訊模式,所以我們可以在此先暫時的省略掉傳輸層(transport layer)的模擬。

3.2.1 網路 (The Network)

首先我們定義網路,然後在下一節我們將定義網路節點。

假設網路的拓樸如下圖

omnet_ned-routing-network

圖:網路

這個網路的NED描述檔內容看起來應該像是:

//
// 網路
//
network Network
{
    submodules:
        node1: Node;
        node2: Node;
        node3: Node;
        ...
    connections:
        node1.port++ <--> {datarate=100Mbps;} <--> node2.port++;
        node2.port++ <--> {datarate=100Mbps;} <--> node4.port++;
        node4.port++ <--> {datarate=100Mbps;} <--> node6.port++;
        ...
}

上面的程式碼定義了一個網路型別,命名為Network。注意,NED語言使用常見的大括號({  })語法,且 “//” 表示註解。

NOTE
在NDE中的註解不僅僅讓原始碼更加容易閱讀,同時在OMNeT++ IDE中他會出現在很多不同的位置(工具提示、內容協助等)。註解也會變成NED檔案輸出文件的一部分。NED檔案文件系統和JavaDoc以及Doxygen有所不同,會在第十四章說明。

上圖的網路由許多不同的節點所組成,這些節點以node1、node2等等來命名,且定義自NED模組型別中的Node模組型別。此型別將在下一節說明。

這份描述檔的下半部分宣告這些節點是如何的彼此連結。雙向箭頭表示這是一條雙向連線;連結到模組的端點就稱為閘(gate),port++語法表示增加一個新閘到模組的port[] gate向量中。閘和連線將會在3.7節和3.9節有比較詳細的討論。另外,節點間使用一條資料速率為100Mbps的通道彼此連接。

NOTE
在其他系統中,類似於OMNeT++閘(gate)的元件稱為埠(port)。我們保留閘這個名稱去避免與其他頻繁使用埠這個字的系統衝突,像是TCP port,I/O port等等。

上面的程式碼將會被儲存到一個名為Net6.ned的檔案中。我們習慣將每一個NED定義儲存成一個獨立的檔案並且適當的命名,但它並不是被要求一定要這樣。

在一個NED檔案中可以包含無數個網路的定義,但是針對每一個模擬程式使用者必須指定清楚哪一個網路將會被使用。一般的指定網路的方法是將network選項放在組態定義檔中(預設是omnetpp.int)。

[General]
network = Network

3.2.2 通道的介紹

這其實有一點繁瑣對於需要針對每一個通道都設定其資料速率(data rate),還好NED提供一個方便的解決方法。我們可以建立一個新的通道型別,將資料速率封裝(encapsulate)在其中。此通道型別便可以被使用在網路的定義中而不會讓全域名稱空間變得凌亂。

這經過改良的網路看起來像是這樣:

//
// 網路
//
network Network
{
    types:
        channel C extends ned.DatarateChannel {
            datarate = 100Mbps;
        }
    submodules:
        node1: Node;
        node2: Node;
        node3: Node;
        ...
    connections:
        node1.port++ <--> C <--> node2.port++;
        node2.port++ <--> C <--> node4.port++;
        node4.port++ <--> C <--> node6.port++;
        ...
}

後面的章節將會詳細說明這裡所用到的概念(內部型別、通道、DatarateChannel內件型別、繼承)。

3.2.3 簡單模組:App、Routing、以及Queue

簡單模組(Simple Module)是作為其他(複合式)模組的基本單元,以關鍵字simple命名。所有模型中活動的行為都會封裝在simple模組中。行為由C++類別所定義;NED檔案僅僅是宣告模組的外部的可視介面(閘、參數等)。

在我們的範例中,我們的確可以定義Node作為一個簡單模組,然而它所需要的功能卻太過於複雜(流量(traffic)產生、路由等等)。所以比較好的方式是將其以數個簡單模組的方式實現,然後再組合成為一個複合式模組。我們會有一個間單模組負責資料流量的產生 (App),一個路由模組(Routing),以及一個可以暫存封包並將它送出的佇列模組(Queue)。為了簡明說明,我們省略掉後面兩個模組內部的程式碼,如下:

simple App
{
    parameters:
        int destAddress;
        ...
        @display("i=block/browser");
    gates:
        input in;
        output out;
}

simple Routing
{
    ...
}

simple Queue
{
    ...
}

依照慣例,上面所提到的簡單模組被儲存成App.ned,Routing.ned,以及Queue.ned檔案

NOTE
注意這些模組型別的名稱(App, Routing, Queue)都是大寫字母開頭,參數名稱、閘名稱都是用小寫字母命名,這是建議的命名方式,同時NED語言是區分大小寫的。

讓我們觀察第一個模組型別的宣告。App有一個參數稱為destAddress (其他的參數在此處被省略了),以及兩個閘分別命名為out和in,各自負責傳送與接收應用程式封包。

引數@display()被稱為是一個顯示字串,它定義了模組在圖形介面的外觀,”i=…”定義預設圖示。

一般來說,像是@display這樣的@-字組被稱為NED的屬性(properties),被用來以後設資料(metadata)來標註不同物件。屬性可以附加在檔案、模組、參數、閘、連線、以及其他物件上,而其參數值可以有很彈性的語法。

3.2.4 複合式模組:Node

接下來我們將App、Routing、以及Queue模組組合起來變成一個複合式模組。一個複合式模組可以被想像成為是一個”硬紙板箱(cardboard box)”,可以將其他模組放在一起變成一個較大的模組單元,同時也可以在以後變成其他模組的建構單元;network由許多Node所組成也算是一種複合式模組。

omnentpp_ned-routing-node

圖:Node複合式模組

module Node
{
    parameters:
        int address;  // 型別  變數名稱
        @display("i=misc/node_vs,gold");
    gates:
        inout port[];  // 型別  變數名稱
    submodules:
        app: App;  // 模組名稱: 模組型別
        routing: Routing;
        queue[sizeof(port)]: Queue;
    connections:
        routing.localOut --> app.in;
        routing.localIn <-- app.out;
        for i=0..sizeof(port)-1 {
            routing.out[i] --> queue[i].in;
            routing.in[i] <-- queue[i].out;
            queue[i].line <--> port[i]; 
        }
}

 

如同簡單模組,複合式模組也有它自己的參數和閘。範例中的Node模組包含一個address參數和一個未指定數量的閘向量,命名為port。這是一個隱含式宣告,實際上閘的數量將會在建立network時由連線的Node的數量決定。port的型別是inout,它是傳送和輸入的雙向連線。

組成這個複合式模組的子模組被條列在submodules標籤下。Node複合式模組包含有一個app和一個routing子模組,以及一個queue[]子模組向量其針對每一個port分配一個Queue模組,所以向量的大小被設定為[sizeof(port)]。(指定[sizeof(port)]是合法的語法,因為這個network是以由上而下的順序被編譯(註*3-1),而當要進行這個Node子模組編譯時node已經在network層級被建立以及設置連線,所以連線的數量在此時已經是已知的)。

(譯註*3-1:原文使用build(建立),因為翻譯成中文後容易在閱讀時腦補,所以改用compile(編譯)這個字。它不會失去原文的意義也容易在閱讀時注意到這裡的機制)

connections區塊,子模組彼此互相連結,同時也連結到其父模組。單箭頭被使用來連結輸出到輸入閘,雙箭頭用來連結inout閘。for迴圈則被用來連結routing模組到每一個queue模組,同時用來連結此模組中相對應的queue和port的輸出/輸入連線(line閘)。(註*3-2)(註*3-3)

(譯註*3-2:因為還沒有細讀每個子模組的內容,也沒有了解每個子模組的內部功能,此處的閱讀僅需要能夠由NED檔案的描述中發現與上圖的關聯即可,譬如能夠指出routing.localOut –> app.in在上圖是哪一條連線即可,至於何謂localOut可以留到以後再說)

(譯註*3-3:上圖中,兩條單向連線疊在一起看起來就像是一條雙向連線,所以圖片只是輔助,連線的細節還是要由NED檔中的描述決定)

3.2.5 整合在一起

我們已經幫這個範例創造好NED定義檔案,但是我們將如何在OMNeT++中使用這些檔案?當模擬程式開始時,它將這些NED檔案載入。程式中應該已經存在有使用C++類別實作好的App、Routing、以及Queue模組;這些C++程式碼是放在可執行檔中或是以分享式函式庫的方式存在。模擬程式同時載入組態檔(omnetpp.ini),由組態檔中得知將會執行這個Network網路模型,然後開始執行模擬程式。

模擬模型是以由上而下預設(top-down preorder)形式來進行編譯與建立程序。也就是說模型先由空系統模型開始建立,所以的子模組先被建立,其參數和閘向量也先被分配,然後這些子模組間的連線也建立好,最後才是子模組內部功能的建立。

***

本章接下來的內容將會進入到NED語言元件內部的細節

3.3 簡單模組

簡單模組是在模型中的一個活動元件,由simple關鍵字所定義。一個簡單模組的範例如下:

simple Queue     // simple 模組名稱
{
    parameters:
        int capacity;
        @display("i=block/queue");
    gates:
        input in;
        output out;
}

parametersgates區段都是非必要的,有需要的時候再使用,當沒有參數或是閘需要定義的時侯就可以省略。此外,parameters關鍵字本身也是非必要的,甚至當有參數或屬性存在的時候它都可以省略。

注意,NED檔案本身並沒有包含任何程式碼去定義簡單模組的動作,動作本身是由C++語言所撰寫。根據預設,OMNeT++將C++類別名稱與NED型別名稱定義為相同名稱(此處,Queue即是)。

我們也可以明確地用@class屬性指出所使用的C++類別,類別與名稱空間修飾子(namespace qualifiers)都可以被指定,如下圖中使用mylib::Queue類別

simple Queue
{
    parameters:
        int capacity;
        @class(mylib::Queue);    // 指定Queue的名稱空間
        @display("i=block/queue");
    gates:
        input in;
        output out;
}

如果你有許多模組都在同一個名稱空間中,則最好將@class屬性以@namespace屬性取代。以@namespace所指定的C++名稱空間會前綴在其他類別名稱之前,如下面的範例:這些C++類別將會是mylib::App,mylib::Router,以及mylib::Queue。

@namespace(mylib);     //指定mylib為名稱空間

simple App {
   ...
}

simple Router {
   ...
}

simple Queue {
   ...
}

如同你所看到的,@namespace的設定是屬於檔案層級(file level),此外,當@namesapce被放在一個名為package.ned的檔案中,則此名稱空間將會使用於整個目錄以及他所有的子目錄中。

簡單模組的C++類別實作必須繼承於cSimpleModule函式庫模組;第四章將說明如何撰寫C++類別。

簡單模組可以藉由衍生去拓展成為另一個子模組,拓展成為子模組的動機可以是將某些參數或閘的數量設定為固定數值(see[3.6] and [3.7]),或是去將C++類別以其他類別取代。根據預設值,衍生的NED模組型別將會繼承基礎型別的C++類別,所以請記得如果你想要使用新的C++類別你需要明確的使用@class指定。

下面的範例展示如何指定特定的參數給一個模組(但是原本的C++類別沒有改變)

simple Queue
{
   int capacity;
   ...
}

// BoundedQueue簡單模組繼承自Queue簡單模組
simple BoundedQueue extends Queue  
{
   capacity = 10;
}

下一個範例,作者寫了一個PriorityQueue的C++類別,並且想用此類別應用在同名的NED型別中。由Queue型別衍生,然而它並不正確

simple PriorityQueue extends Queue // wrong! still uses the Queue C++ class
{
}

正確的寫法需要使用@class屬性來覆寫(override)原本繼承來的C++類別:

simple PriorityQueue extends Queue
{
   @class(PriorityQueue);
}

繼承(Inheritance)將會在3.13節中討論

3.4 複合式模組 (Compound Modules)

複合式模組是將其他幾個模組集合在一起形成一個較大的模擬單元。一個複合式模組可以有像簡單模組一般的閘和參數,但是沒有屬於自己的行為描述在其中。

[雖然在複合式模組中我們還是可以使用@class屬性去指定C++類別,但是這個功能最好永遠不要去用它。關於行為的描述應該撰寫在簡單模組之中,然後將此簡單模組作為複合式模組的子模組之一]

NOTE
當你覺得應該在複合式模組中增加一些程式碼的時候,請將這些程式碼增加到簡單模組中,然後加入複合式模組。

一個複合式模組的宣告包含很多區段(section),全部都是可選擇的(optional):

module Host
{
   types:
       ...
   parameters:
       ...
   gates:
       ...
   submodules:
       ...
   connections:
       ...
}

模組如果被包含在一個複合式模組中,被稱為子模組(submodule),需要被宣告在submodules區段中。我們可以建立一個子模組陣列(i.e. submodule vectors),且這些子模組的型別可以由參數定義之。

連線被宣告在connections區段中。我們可以使用簡單的程式結構(迴圈、條件敘述)來建立連線,連線的行為由所關聯的通道來定義,而通道的型別同樣可以由參數所指定。

如果模組和通道型別僅僅使用在本地,則可以定義在types區段使其成為內部型別,這樣就不會污染名稱空間。

複合式模組可以由繼承而來,在父模組的基礎上增加新的子模組和新的連線。除了閘和參數以外,子模組和型別都是可以繼承的。唯一不能做的是縮小繼承範圍或是修改子模組與連線內容。

 

下面的範例中我們將展示如何將常用的協定(protocols)組合到一個無線主機的端點(“stub”)並藉由繼承增加使用者介面。

[使用在這個範例中的模組型別、閘的名稱等等都是虛構的,和實際的OMNeT++模組架構中的不同]

module WirelessHostBase
{
   gates:
       input radioIn;
   submodules:
       tcp: TCP;
       ip: IP;
       wlan: Ieee80211;
   connections:
       tcp.ipOut --> ip.tcpIn;
       tcp.ipIn <-- ip.tcpOut;
       ip.nicOut++ --> wlan.ipIn;
       ip.nicIn++ <-- wlan.ipOut;
       wlan.radioIn <-- radioIn;
}

module WirelessHost extends WirelessHostBase
{
   submodules:
       webAgent: WebAgent;
   connections:
       webAgent.tcpOut --> tcp.appIn++;
       webAgent.tcpIn <-- tcp.appOut++;
}

這個WirelessHost複合式模組可以再被擴展,舉例來說,可以加上一個Ethernet埠:

module DesktopHost extends WirelessHost
{
   gates:
       inout ethg;
   submodules:
       eth: EthernetNic;
   connections:
       ip.nicOut++ --> eth.ipIn;
       ip.nicIn++ <-- eth.ipOut;
       eth.phy <--> ethg;
}

3.5 通道 (Channels)

通道(channels)封裝關聯於連線的參數和行為成為一個單元。通道從程式的觀點來看,它像是簡單模組一般由相關的C++類別構成。所以指定C++類別給NED通道型別的方式和簡單模組是一樣的:預設的類別名稱就是NED型別名稱,除非用@class屬性另外指定(@namespace也是同樣方式指定),同時當通道是子類別時,C++類別也會被繼承。

因此,下面的通道型別將期待會有一個特定的CustomChannel C++類別存在:

channel CustomChannel  // requires a CustomChannel C++ class
{
}

和模組實際上的差別是,實際上你極少需要去撰寫你自己的C++通道類別因為有許多預設的通道型別可以繼承使用,並繼承他們的C++程式碼。這些預設的型別有:ned.IdealChannel、ned.DelayChannelned.DatarateChannel。(“ned” 是套件名稱,你可以省略掉它如果你使用import ned* 或是類似的指令去指定整個型別。套件以及imports將在3.14節說明)

IdealChannel沒有任何參數,而且它可以讓所有的訊息以沒有任何延遲與副作用的情況下通過。一個連線沒有任何的通道物件與一個連線使用IdealChannel的效果是一模一樣的。此外,IdealChannel有他自己的用法。舉例來說,當我們需要一個通道物件幫忙傳送一個新的屬性或是參數給模擬模型的其他部分讀取的時候可使用IdealChannel來完成這件工作。

DelayChannel有兩個參數:

  • delay是一個double型別的參數,他用來表示訊息的傳輸延遲。指定其數值時需要加上時間的單位(s, ms, us,  等)。
  • disabled是一個布林變數,預設值是false。當它被設定為true時,這個通道物件將會拋棄所有的訊息(譯注:此路不通)。

DatarateChannel比DelayChannel多了幾個參數:

  • datarate是一個double型別的參數表示這條連線的資料速率,以位元每秒(bits per second)或是他的整數倍為單位(bps, kbps, Mbps, Gbps, etc.)。零表示一個特別的數字,它表示零傳輸時間,亦即無限大頻寬。零同時也是通道資料速率的預設值。資料速率被用來計算封包的傳輸時間(transmission duration)。
  • ber和per表示位元錯誤率(Bit Error Rate)以及封包錯誤率(Packet Error Rate)可以用來作為基本的錯誤模擬。他們使用一個範圍在[0, 1]之間的double數值,以隨機的方式決定在一個封包傳遞的過程中是否有錯誤發生。若有,則設定一個錯誤旗標(error flag)在這個封包物件中。 接收端模組被期望去檢查這個旗標,並且當發現此旗標被設定時丟棄這個封包。ber和per的預設值為零。

NOTE
There is no channel parameter that specifies whether the channel delivers the message object to the destination module at the end or at the start of the reception; that is decided by the C++ code of the target simple module. See the setDeliverOnReceptionStart() method of cGate (譯註:目前不是很清楚這句的意思,所以先不翻譯)

下面的範例展示如何使用DatarateChannel建立一個新的通道型別:

channel Ethernet100 extends ned.DatarateChannel
{
    datarate = 100Mbps;
    delay = 100us;
    ber = 1e-10;
}

NOTE
這三種內建的通道型別也被使用在那些沒有明確指定通道型別的連線中。

你可以使用繼承的方式增加參數或是屬性到通道上,也可以修改現有的通道。下面的範例說明以距離為基礎的傳播延遲計算。

channel DatarateChannel2 extends ned.DatarateChannel
{
    double distance @unit(m);
    delay = this.distance / 200000km * 1s;  //傳播速度=2 x 10^8 m/s
}

參數主要被用來作為底層C++類別的輸入,但是如果你重用(reuse)這些內建通道型別的底層C++類別,參數可能被讀取與使用在這個模型的其他部分。例如:增加一個成本參數(cost parameter)(或是@cost屬性)可以被路由協定所觀察或是用來計算路由決策。下面的範例說明一個成本參數以及使用一個屬性(@backbone)來註解。

channel Backbone extends ned.DatarateChannel
{
    @backbone;
    double cost = default(1);
}

3.6 參數(Parameters)

參數是屬於模組的變數(variables),參數可以被用來建立拓樸(節點的數目等),與作為實作簡單模組或是通道的C++程式碼的輸入引數。

參數的型別可以是doubleintboolstring、或是xml型別,它也可以被宣告成為volatile(註*3-4)。對於數值型別的參數,在建立數值參數時必須附上該數值的單位(@unit屬性)以增加安全性。

(譯註*3-4:一個變數被宣告成為volatile就表示這個變數是有可能隨時會被其他執行緒修改的,即便與該變數相關的上下文沒有任何對其進行修改的語句)

參數可以由NED檔案或是組態設定檔(omnetpp.int)取得它們的數值。當沒有任何參數設定來源時,預設值可以由default(…)設定。

下面的範例展示一個簡單模組與他的五個參數,其中三個有預設值。

simple App
{
    parameters:
        string protocol;       // protocol to use: "UDP" / "IP" / "ICMP" / ...
        int destAddress;       // destination address
        volatile double sendInterval @unit(s) = default(exponential(1s)); 
                               // time between generating packets
                               // 單位為秒,預設值是以平均值為1秒的指數型隨機變數
        volatile int packetLength @unit(byte) = default(100B);
                               // length of one packet
                               // 單位是byte,預設值100bytes
        volatile int timeToLive = default(32);
                               // maximum number of network hops to survive
                               // 單位是"次",所以不算是數值參數,不用指定單位
    gates:
        input in;
        output out;
}
指定數值

參數可以有好幾種方式取得它們的數值:1.由NED程式碼;2.由組態設定檔(omnetpp.ini);3.或甚至由與使用者的互動中取得。NED檔案有好幾的地方可以讓你設定參數值:1.藉由繼承在子類別中設定;2.在子模組或連線的定義中且此NED型別已經被宣告;3.在網路或複合式模組中,直接或間接地包含相關的子模組或連線。

舉例,我們可以指定上面範例中的App模組型別藉由繼承取得下面的定義:

simple PingApp extends App
{
    parameters:
        protocol = "ICMP/ECHO"    // 繼承後指定protocol數值
        sendInterval = default(1s);   // 繼承後變更預設值
        packetLength = default(64byte);  // 繼承後變更預設值
}

這個繼承定義了protocol參數的數值為固定的協定(“ICMP/ECHO”),變更sendInterval和packetLength參數的預設值。此時protocol參數被鎖定,因為在未來繼承PingApp的子類別就不能夠再修改protocol參數的內容。sendInterval和packetLength參數則未被鎖定,因此在未來的繼承中還是可以修改其預設值。

現在讓我們再看一個例子,這個例子中繼承PingApp:

module Host
{
    submodules:
        ping : PingApp {
            packetLength = 128B; // always ping with 128-byte packets
        }
        ...
}

這個定義檔將packetLength設為固定值,它寫死packetLength為128位元組,此數值將不能再被NED或其他組態檔更改。

並不是只有使用繼承子模組的方式來設定參數,可以用在模組樹中較高層次的模組來設定。如果你有一個網路其中包含幾個Host模組,它也可以被定義如下:

network Network
{
    submodules:
        host[100]: Host {
            ping.timeToLive = default(3);
            ping.destAddress = default(0);
        }
        ...
}

參數也可以在網路模組的papameters區塊中指定其數值,它提供額外的彈性。在下面的範例中一半的hosts被設定他們的ping值是50,剩下的一半為0

network Network
{
    parameters:
        host[*].ping.timeToLive = default(3);
        host[0..49].ping.destAddress = default(50);
        host[50..].ping.destAddress = default(0);

    submodules:
        host[100]: Host;
        ...
}

節點使用星號(*)表示適用於所有索引號碼,’..’表示索引號碼的範圍。

如果不是使用子模組陣列而是逐一的列出獨立的host子模組,這這個網路的定義可能看起來像是這樣:

network Network
{
    parameters:
        host*.ping.timeToLive = default(3);
        host{0..49}.ping.destAddress = default(50);
        host{50..}.ping.destAddress = default(0);

    submodules:
        host0: Host;
        host1: Host;
        host2: Host;
        ...
        host99: Host;
}

這裡星號(*)符合任何不包含’.’的子字串,且’..’在一對大括號中時可以嵌入一個自然數到子字串中。

在上面看到的大部分的參數指定中,在等號的左邊包含一個’.’且常常有一個萬用字元(星號*或是數值範圍’..’);我們稱這樣的指定為樣式指定(pattern assignments)或是深指定(deep assignments)。

在樣式指定中,我們可以用一個以上的萬用字元,也就是兩個星號(**)。他表示任何字元序列,包含’.’,所以它可以用來表示多重路徑元素,例如:

network Network
{
    parameters:
        **.timeToLive = default(3);
        **.destAddress = default(0);
    submodules:
        host0: Host;
        host1: Host;
        ...
}

注意在上面的某些設定中改變了參數的預設值,但有一些參數被設定了固定的數值。那些在NED檔案中沒有被設定為固定數值的參數可以在組態設定檔(omnetpp.ini)中設定其數值。

重要
一個在NED檔案中已經被設定為固定數值的參數不能夠在之後的NED檔或是.ini檔中變更其內容。當考慮使用.ini或是NED檔案使用它時,它變成一個”寫死的”參數。相對的,僅設定預設值的參數值是有可能被覆寫的。

參數可以在組態設定檔中使用類似NED檔案中的樣式指定的語法來指定數值(實際上以歷史的演進精確地說,反過來講,NED樣式指定的語法類似於組態設定檔所使用的語法)。

Network.host[*].ping.sendInterval = 500ms  # for the host[100] example
Network.host*.ping.sendInterval = 500ms    # for the host0,host1,... example
**.sendInterval = 500ms

我們常使用兩個星號(**)省去指定型別(或節省打字輸入的字元數目),通常寫成

**.ping.sendInterval = 500ms

或者,如果你確定你不會突然地設定其他的sendInterval參數的數值,你可以這樣寫

**.sendInterval = 500ms

在組態設定檔中的參數設定將在9.3節中說明。

你也可以在NED檔案中或是.ini檔案中寫出包含隨機變數的參數表示式,例如,你可以加上顫動(jitter)到你送出的ping封包:

**.sendInterval = 1s + normal(0s, 0.001s)  # or just: normal(1s, 0.001s)

如果在NED檔案或是.ini檔案中沒有指定參數數值,那預設值(使用default(…)在NED檔案中指定)將會被使用。如果沒有預設值,那使用者將會被要求輸入該數值,模擬程式會允許我們做這樣的設定,如果沒有明確的指定數值,則那將會有錯誤發生。(互動模式(interactive mode)在批次執行的時候預設是被關閉的,因為利大於弊)

有時候會明確的說明該參數就是使用預設值(某些時候這樣會很有用):

**.sendInterval = default

最後,我們也可以明確的指出需要在模擬程式執行時藉由使用者介面詢問該參數的數值(嗯,提供互動模式是被允許的,否則它將產生錯誤)。

**.sendInterval = ask

NOTE
如何決定在NED檔案中指定參數數值或是在.ini檔案中指定參數數值?在.ini檔案中指定數值的好處是這樣可以有一個較為清楚的模型與實驗的分界。NED檔案(和相關的C++程式碼)通常被視為是模型的一部分,且或多或少是固定不變的。另一方面,.ini檔案是針對特定的實驗設定,通常執行一個模擬好幾次但使用不同的參數來觀察實驗的結果。因次,參數如果是在不同的實驗中會被改變的應該放在.ini檔案中。

運算式 (Expressions)

參數可以透過數學運算式指定。NED語言的數學運算式和C語言除了部分運算子符號不同外,其餘使用相同的語法。對於位元與邏輯互斥或(binary and logical XOR)運算元使用#以及##來表示;^符號用來表示”次方”;+號在字串上被用來做串接運算(concatenation)。運算式可以使用各種不同的數值、字串、統計與其他函數(fabs(), toUpper(), uniform(), erlang_k(), 等等)。

NOTE
NED函數集可以在附錄[22]中查到。使用者也可以擴充NED撰寫為新的函數。

運算式可以被用在模組參數、閘向量與模組向量大小(使用sizeof運算子)的設定,以及作為目前的模組在子模組向量中的索引值。(譯註:原文使用模組向量(module vector),讀者若改為模組陣列(module array)應該會比較容易理解)

運算式可以被用在複合式模組的參數定義,用在目前的子模組的參數定義(使用this.字首)或是修改已定義的子模組的參數,使用submodule.parametername語法(或是submodule[index].parametername)。

volatile

volatile修飾子促使參數值運算式在每一次此參數被讀取時必須重新的運算求值。這一點在當運算式非常數值時變得非常重要。舉例來說,當此運算式包含一個隨機變數產生器時。反過來說,非volatile參數僅僅會被求值一次。(在實作上的意義是,這些參數都會在模擬程式開始運行前先將其數值算出,然後以常數的方式使用在模擬程式中)

想要更加了解volatile參數,讓我們假設一個包含有一個volatele double參數的Queue簡單模組,此參數名為serviceTime。

simple Queue
{
    parameters:
        volatile double serviceTime;
}

因為這個volatile修飾子,這個queue模組的C++實作部分會被期望在每一次系統去讀取serviceTime的時候重算一次,也就是每一筆工作的服務時間。因此,如果serviceTime被指定為像是uniform(0.5s, 1.5s)這樣的運算式,每一筆工作都會有不同的、隨機的服務時間。再強調一次這個效果,在這裡你可以設計一個時變(time-varying)的參數,藉由使用NED的simTime()函式回傳系統目前的模擬時間(simulation time):

**.serviceTime = simTime()<1000s ? 1s : 2s  # queue that slows down after 1000s

在實務上,volatle參數典型的被使用來當作模組中的可調隨機變數源。

NOTE
這並不表示非volatile參數不能夠被指定為隨機數值,像是uniform(0.5s, 1.5s)。非volatile也可以這樣做,不過會有完全不一樣的效果:非volatile參數只會在模擬開始前被計算一次,所以模擬程式可能會使用一個固定的serviceTime,例如1.2975367s,在整個模擬執行的過程中。

單位(Units)

使用者可以使用@unit屬性來宣告參數的單位,範例如下:

simple App
{
    parameters:
        volatile double sendInterval @unit(s) = default(exponential(350ms));
        volatile int packetLength @unit(byte) = default(4KiB);
    ...
}

@unit(s)和@unit(byte)宣告指定參數的量測單位,被指定給這些參數的數值必須符合或是相容於這些單位。@unit(s)接受毫秒(milliseconds)、奈秒(nanoseconds)、分(minutes)、小時(hours)等等,@unit(byte)接受千位元組(kilobytes)、百萬位元組(megabytes)等等單位。

NOTE
OMNeT++可接受的單位表列在附錄[19.5.6]中。未知的單位(bogomips等)也可以被使用,但是系統並不會去轉換它,也就是系統不會辨識在這些單位前面的十進制前綴(kilo, mega, giga等等)

OMNeT++運行核心會執行一個完整且嚴格的單位檢查以確保模組的”單位安全”,任何常數都必須要包含量測單位。

一個參數的@unit屬性不能夠在子類別或是子模組宣告中被增加或覆寫。

XML參數

有些時候模組需要輸入型別具有較為複雜的資料結構,而不容易使用簡單的模組參數型別來完成。一個解決的方法是將這些輸入的資料寫入一個使用者自訂型別的組態檔案,將此檔案的名稱以一個字串參數的形式提供給模組。模組會讀入這個檔案並解析它。

通常將此組態檔案寫成XML語法的形式在使用上會較為簡單,因為OMNeTT++內建支援XML檔案格式。使用XML解析器(LibXML2 或 Expat),OMNeT++讀入該檔案並驗證DTD格式(如果此XML文件包含DOCTYPE),儲存此檔案在記憶體中(所以如果有很多模組都參考到此檔案,僅需要被讀取與解析一次),允許使用XPath-subset表示法選取文件部分的內容,也能夠呈現DOM-like物件樹的內容。

可以透過NED語言的xml參數型別與xmldoc()函式來啟動這項能力。你能夠將xml型別模組參數藉由xmldoc()函式指向一個特定的xml檔案(或是XML檔案裡面的一個元素),你可以由NED檔案或是omnetpp.ini中設定xml參數。

下面是一個範例,宣告xml參數並將一個XML檔案指定給它。這個檔案的名稱與相對於工作目錄的路徑必須很清楚的被指定。

simple TrafGen {
    parameters:
        xml profile;
    gates:
        output out;
}

module Node {
    submodules:
        trafGen1 : TrafGen {
            profile = xmldoc("data.xml");
        }
        ...
}

也可以指定XML檔案裏面的元素給NED參數,如果你想要將很多模組裡面的參數整合在一個XML檔案裡面時會很有用。下面範例的XML檔案中包含兩個profile分別具有ID gen1和gen2。

<?xml>
<root>
    <profile id="gen1">
          <param>3</param>
          <param>5</param>
    </profile>
    <profile id="gen2">
          <param>9</param>
    </profile>
</root>

然後你可以使用XPath-like表示式指定任一個profile給相關的子模組

module Node {
    submodules:
        trafGen1 : TrafGen {
            profile = xmldoc("all.xml", "/root/profile[@id='gen1']");
        }
        trafGen2 : TrafGen {
            profile = xmldoc("all.xml", "/root/profile[@id='gen2']");
        }
}

也可以使用xml()函式,以字串的方式在NED檔案中建立XML文件。這在建立一個xml參數的預設值的時候特別好用。如下:

simple TrafGen {
    parameters:
        xml profile = xml("<root/>"); // empty document as default
        ...
}

xml()函式和xmlfun()函式一樣都支援次要的XPath參數來選擇子樹(subtree)。

3.7 閘(Gates)

“閘”是模組的連接點,在OMNeT++中有三種閘的形式:輸入(input)、輸入(output)以及輸入輸出(inout),這最後一項本質上就是一個輸入閘加上一個輸出閘黏合在一起。

一個閘,不管是輸入閘或是輸出閘,都只能連接到另外一個閘。(對於一個複合模組來說,它的意思就是一個”外部(outside)”的連線和一個”內部(inside)”的連線)。它可能,一般來說並不推薦,分別去連結一個inout閘的輸入端和輸出端到不同的模組。(see section[3.9])

我們可以建立單一個閘或是建立一個閘向量,閘向量的大小在宣告變數時使用中括號中的數字指定,但是它也可以使用一個空的中括號[]來指定。

當使用空的中括號時,向量的大小可以稍後在指定。像是當繼承這個模組時,或是使用這個模組當作是另一個複合式模組的子模組時。向量大小可以不需要被指定,因為我們還可以使用gate++運算子在建立連線的時候自動增加閘向量的數目。

閘的大小可以由不同的NED運算式使用sizeof()運算子得到。

NED在正常的情況下要求所有的閘都必須是有連結的。稍微放鬆一點這項需求,你可以使用@loose註譯符號標註在某些閘上頭,它將會關掉這個閘的連結檢查。同時,獨立存在的輸入閘可以接收來自於sendDirect()的訊息(看[4.7.5]),但它需要使用@directIn標註。OMNeT++也可以關掉一個複合式模組中所有連線的閘檢查,藉由標註allowunconnected關鍵字在模組的connections區段。

讓我們看一些範例。

在下面的範例中,Classifier模組有一個輸入閘來接收工作,然後它將送給一個輸出閘。輸出閘的數目由模組參數決定。

simple Classifier {
    parameters:
        int numCategories;
    gates:
        input in;
        output out[numCategories];
}

下面這個Sink模組有它自己的in[]閘向量,所以它可以連結到好幾個模組。

simple Sink {
    gates:
        input in[];
}

下面這幾行定義一個節點來建立方格,在這個方格周邊的閘被期望為保持為未連接,因此使用@loose標記

simple GridNode {
    gates:
        inout neighbour[4] @loose;
}

下面的無線節點被期望直接由無線傳輸的方式收到訊息,所以它的radioIn閘被標示為@directIn。

simple WirelessNode {
    gates:
        input radioIn @directIn;
}

在下面的範例,我們定義TreeNode具有可以連結到任意數量子節點的閘,而它的子類別BinaryTreeNode則將閘的數量設定為2:

simple TreeNode {
    gates:
        inout parent;
        inout children[];
}

simple BinaryTreeNode extends TreeNode {
    gates:
        children[2];
}

一個將在子模組中設定閘向量數目的範例,使用上例中的TreeNode模組型別。

module BinaryTree {
    submodules:
        nodes[31]: TreeNode {
            gates:
                children[2];
        }
    connections:
        ...
}

3.8 子模組(Submodules)

 

3.11 參數型子模組與連線型別 (Parametric Submodule and Connection Types)

3.11.1 參數型子模組型別

子模組型別可以用型別為字串的模組參數所指定,或者一般來說可以用字串型別的表示式來指定。該語法使用like關鍵字完成。

讓我們使用一個例子解釋:

network Net6
{
    parameters:
        string nodeType;
    submodules:
        node[6]: <nodeType> like INode {
            address = index;
        }
    connections:
        ...
}

這個例子建立的一個子模組陣列,他的模組型別是noteType參數(註*)。如果noteType被設定為”SensorNode”,那這個子模組陣列將由sensor node所組成,具備該模組的型別與特性。而這裡的INode必須是一個存在的模組介面(module interface),並且SensorNode模組必須實作該介面。

(譯註* 3-6節提到過,參數可以被用來建立拓樸(節點的數目等),與作為實作簡單模組或是通道的C++程式碼的輸入引數)

在前面也曾提到過,我們可以在尖括號中間撰寫運算式,這個運算式可以使用其父模組或是以定義好的子模組的參數,最後產生一個字串結果作為引入參數。舉例來說,下面的程式碼是有效的:

network Net6
{
    parameters:
        string nodeTypePrefix;
        int variant;
    submodules:
        node[6]: <nodeTypePrefix + "Node" + string(variant)> like INode {
           ...
}

其他相對應的NED宣告:

moduleinterface INode
{
    parameters:
        int address;
    gates:
        inout port[];
}

module SensorNode like INode
{
    parameters:
        int address;
        ...
    gates:
        inout port[];
        ...
}

這個  “<nodeType> like INode” 語法當使用在子模組陣列的時候有一點需要注意:他不允許在陣列中指定不同的型別給個別的元素。下面的語法適用於宣告子模組陣列:(譯註:文件中沒有寫任何東西,應該是有遺漏)

在尖括號中間的運算式可以完全空白,僅留下一對空的尖括號,<>:

module Node
{
    submodules:
        nic: <> like INic;  // type name expression left unspecified
        ...
}

所以現在子模組的型別被期待由型別名稱指定方式所定義,型別名稱指定方式看起來像是指定子模組參數的型別,但僅僅在參數名稱的地方由typename關鍵字所取代。型別名稱指定也可以寫在組態檔案中。在一個網路中,使用上面範例中的Node NED型別作為型別名稱指定的範例看起來像是:

network Network
{
    parameters:
        node[*].nic.typename = "Ieee80211g";
    submodules:
        node: Node[100];
}

在Node模組中我們同樣可以設定參數的預設值,在尖括號中。這種用法使用在當沒有明確的指定模組型別名稱的時候使用:

module Node
{
    submodules:
        nic: <default("Ieee80211b")> like INic;
        ...
}

這種情況下必須有一的模組型別是以Ieee80211b命名的,且需要實作模組介面INic,否則你會得到一個錯誤的訊息。(將Node的NED檔案import到網路NED檔案對於型別的解析沒有幫助)。如果有兩個以上的同名型別,你可以藉由指定完整的模組型別名稱來避免這種模糊:

module Node
{
    submodules:
        nic: <default("acme.wireless.Ieee80211b")> like INic; // made-up name
        ...
}

3.11.2 參數型連線型別 (Parametric Connection Types)

參數型連結型別的工作方式和參數型模組型別類似,同時語法也是類似的。一個基本的例子使用父模組的參數:

a.g++ <--> <channelType> like IMyChannel <--> b.g++;
a.g++ <--> <channelType> like IMyChannel {@display("ls=red");} <--> b.g++;

這運算表示式可以使用迴圈變數,父模組參數同時也是子模組的參數(例如:host[2].channelType)。

這型別運算表示式可以省略不寫,此時型別將會被期望使用型別名稱指定來定義:

a.g++ <--> <> like IMyChannel <--> b.g++;
a.g++ <--> <> like IMyChannel {@display("ls=red");} <--> b.g++;

預設值也可以設定:

a.g++ <--> <default("Ethernet100")> like IMyChannel <--> b.g++;
a.g++ <--> <default(channelType)> like IMyChannel <--> b.g++;

相對應的型別名稱指定:

a.g$o[0].channel.typename = "Ethernet1000";  // A -> B channel
b.g$o[0].channel.typename = "Ethernet1000";  // B -> A channel

3.12 後設資料註解(屬性) (Metadata Annotations (Properties))

 

 

 


 

3 The NED Language

3.1 NED Overview

The user describes the structure of a simulation model in the NED language. NED stands for Network Description. NED lets the user declare simple modules, and connect and assemble them into compound modules. The user can label some compound modules as networks; that is, self-contained simulation models. Channels are another component type, whose instances can also be used in compound modules.

The NED language has several features which let it scale well to large projects:

  • Hierarchical. The traditional way to deal with complexity is by introducing hierarchies. In OMNeT++, any module which would be too complex as a single entity can be broken down into smaller modules, and used as a compound module.
  • Component-Based. Simple modules and compound modules are inherently reusable, which not only reduces code copying, but more importantly, allows component libraries (like the INET Framework, MiXiM, Castalia, etc.) to exist.
  • Interfaces. Module and channel interfaces can be used as a placeholder where normally a module or channel type would be used, and the concrete module or channel type is determined at network setup time by a parameter. Concrete module types have to “implement” the interface they can substitute. For example, given a compound module type named MobileHost contains a mobility submodule of the type IMobility (whereIMobility is a module interface), the actual type of mobility may be chosen from the module types that implemented IMobility (RandomWalkMobility, TurtleMobility, etc.)
  • Inheritance. Modules and channels can be subclassed. Derived modules and channels may add new parameters, gates, and (in the case of compound modules) new submodules and connections. They may set existing parameters to a specific value, and also set the gate size of a gate vector. This makes it possible, for example, to take aGenericTCPClientApp module and derive an FTPClientApp from it by setting certain parameters to a fixed value; or to derive a WebClientHost compound module from aBaseHost compound module by adding a WebClientApp submodule and connecting it to the inherited TCP submodule.
  • Packages. The NED language features a Java-like package structure, to reduce the risk of name clashes between different models. NEDPATH (similar to Java’s CLASSPATH) has also been introduced to make it easier to specify dependencies among simulation models.
  • Inner types. Channel types and module types used locally by a compound module can be defined within the compound module, in order to reduce namespace pollution.
  • Metadata annotations. It is possible to annotate module or channel types, parameters, gates and submodules by adding properties. Metadata are not used by the simulation kernel directly, but they can carry extra information for various tools, the runtime environment, or even for other modules in the model. For example, a module’s graphical representation (icon, etc) or the prompt string and measurement unit (milliwatt, etc) of a parameter are already specified as metadata annotations.

NOTE

    The NED language has changed significantly in the 4.0 version. Inheritance, interfaces, packages, inner types, metadata annotations, inout gates were all added in the 4.0 release, together with many other features. Since the basic syntax has changed as well, old NED files need to be converted to the new syntax. There are automated tools for this purpose, so manual editing is only needed to take advantage of new NED features.

The NED language has an equivalent tree representation which can be serialized to XML; that is, NED files can be converted to XML and back without loss of data, including comments. This lowers the barrier for programmatic manipulation of NED files; for example extracting information, refactoring and transforming NED, generating NED from information stored in other systems like SQL databases, and so on.

NOTE
This chapter is going to explain the NED language gradually, via examples. If you are looking for a more formal and concise treatment, see Appendix [20]

    .

3.2 NED Quickstart

In this section we introduce the NED language via a complete and reasonably real-life example: a communication network.

Our hypothetical network consists of nodes. On each node there is an application running which generates packets at random intervals. The nodes are routers themselves as well. We assume that the application uses datagram-based communication, so that we can leave out the transport layer from the model.

3.2.1 The Network

First we’ll define the network, then in the next sections we’ll continue to define the network nodes.

Let the network topology be as in Figure below.

Figure: The network

The corresponding NED description would look like this:

//
// A network
//
network Network
{
    submodules:
        node1: Node;
        node2: Node;
        node3: Node;
        ...
    connections:
        node1.port++ <--> {datarate=100Mbps;} <--> node2.port++;
        node2.port++ <--> {datarate=100Mbps;} <--> node4.port++;
        node4.port++ <--> {datarate=100Mbps;} <--> node6.port++;
        ...
}

The above code defines a network type named Network. Note that the NED language uses the familiar curly brace syntax, and “//” to denote comments.

NOTE

      Comments in NED not only make the source code more readable, but in the OMNeT++ IDE they also are displayed at various places (tooltips, content assist, etc), and become part of the documentation extracted from the NED files. The NED documentation system, not unlike

JavaDoc

      or

Doxygen

      , will be described in Chapter

[14]

    .

The network contains several nodes, named node1, node2, etc. from the NED module type Node. We’ll define Node in the next sections.

The second half of the declaration defines how the nodes are to be connected. The double arrow means bidirectional connection. The connection points of modules are called gates, and the port++ notation adds a new gate to the port[] gate vector. Gates and connections will be covered in more detail in sections [3.7] and [3.9]. Nodes are connected with a channel that has a data rate of 100Mbps.

NOTE

      In many other systems, the equivalent of OMNeT++ gates are called

ports

      . We have retained the term

gate

      to reduce collisions with other uses of the otherwise overloaded word

port

    : router port, TCP port, I/O port, etc.

The above code would be placed into a file named Net6.ned. It is a convention to put every NED definition into its own file and to name the file accordingly, but it is not mandatory to do so.

One can define any number of networks in the NED files, and for every simulation the user has to specify which network to set up. The usual way of specifying the network is to put the network option into the configuration (by default the omnetpp.ini file):

[General]
network = Network

3.2.2 Introducing a Channel

It is cumbersome to have to repeat the data rate for every connection. Luckily, NED provides a convenient solution: one can create a new channel type that encapsulates the data rate setting, and this channel type can be defined inside the network so that it does not litter the global namespace.

The improved network will look like this:

//
// A Network
//
network Network
{
    types:
        channel C extends ned.DatarateChannel {
            datarate = 100Mbps;
        }
    submodules:
        node1: Node;
        node2: Node;
        node3: Node;
        ...
    connections:
        node1.port++ <--> C <--> node2.port++;
        node2.port++ <--> C <--> node4.port++;
        node4.port++ <--> C <--> node6.port++;
        ...
}

Later sections will cover the concepts used (inner types, channels, theDatarateChannel built-in type, inheritance) in detail.

3.2.3 The App, Routing, and Queue Simple Modules

Simple modules are the basic building blocks for other (compound) modules, denoted by the simple keyword. All active behavior in the model is encapsulated in simplemodules. Behavior is defined with a C++ class; NED files only declare the externally visible interface of the module (gates, parameters).

In our example, we could define Node as a simple module. However, its functionality is quite complex (traffic generation, routing, etc), so it is better to implement it with several smaller simple module types which we are going to assemble into a compound module. We’ll have one simple module for traffic generation (App), one for routing (Routing), and one for queueing up packets to be sent out (Queue). For brevity, we omit the bodies of the latter two in the code below.

simple App
{
    parameters:
        int destAddress;
        ...
        @display("i=block/browser");
    gates:
        input in;
        output out;
}

simple Routing
{
    ...
}

simple Queue
{
    ...
}

By convention, the above simple module declarations go into the App.ned, Routing.nedand Queue.ned files.

NOTE

      Note that module type names (

App

      ,

Routing

      ,

Queue

    ) begin with a capital letter, and parameter and gate names begin with lowercase — this is the recommended naming convention. Capitalization matters because the language is case sensitive.

Let us look at the first simple module type declaration. App has a parameter calleddestAddress (others have been omitted for now), and two gates named out and in for sending and receiving application packets.

The argument of @display() is called a display string, and it defines the rendering of the module in graphical environments; "i=..." defines the default icon.

Generally, @-words like @display are called properties in NED, and they are used to annotate various objects with metadata. Properties can be attached to files, modules, parameters, gates, connections, and other objects, and parameter values have a very flexible syntax.

3.2.4 The Node Compound Module

Now we can assemble App, Routing and Queue into the compound module Node. A compound module can be thought of as a “cardboard box” that groups other modules into a larger unit, which can further be used as a building block for other modules; networks are also a kind of compound module.


Figure: The Node compound module

module Node
{
    parameters:
        int address;
        @display("i=misc/node_vs,gold");
    gates:
        inout port[];
    submodules:
        app: App;
        routing: Routing;
        queue[sizeof(port)]: Queue;
    connections:
        routing.localOut --> app.in;
        routing.localIn <-- app.out;
        for i=0..sizeof(port)-1 {
            routing.out[i] --> queue[i].in;
            routing.in[i] <-- queue[i].out;
            queue[i].line <--> port[i];
        }
}

Compound modules, like simple modules, may have parameters and gates. Our Nodemodule contains an address parameter, plus a gate vector of unspecified size, namedport. The actual gate vector size will be determined implicitly by the number of neighbours when we create a network from nodes of this type. The type of port[] isinout, which allows bidirectional connections.

The modules that make up the compound module are listed under submodules. OurNode compound module type has an app and a routing submodule, plus a queue[]submodule vector that contains one Queue module for each port, as specified by[sizeof(port)]. (It is legal to refer to [sizeof(port)] because the network is built in top-down order, and the node is already created and connected at network level when its submodule structure is built out.)

In the connections section, the submodules are connected to each other and to the parent module. Single arrows are used to connect input and output gates, and double arrows connect inout gates, and a for loop is utilized to connect the routing module to each queue module, and to connect the outgoing/incoming link (line gate) of each queue to the corresponding port of the enclosing module.

3.2.5 Putting It Together

We have created the NED definitions for this example, but how are they used by OMNeT++? When the simulation program is started, it loads the NED files. The program should already contain the C++ classes that implement the needed simple modules, App,Routing and Queue; their C++ code is either part of the executable or is loaded from a shared library. The simulation program also loads the configuration (omnetpp.ini), and determines from it that the simulation model to be run is the Network network. Then the network is instantiated for simulation.

The simulation model is built in a top-down preorder fashion. This means that starting from an empty system module, all submodules are created, their parameters and gate vector sizes are assigned, and they are fully connected before the submodule internals are built.

 

* * *

In the following sections we’ll go through the elements of the NED language and look at them in more detail.

3.3 Simple Modules

Simple modules are the active components in the model. Simple modules are defined with the simple keyword.

An example simple module:

simple Queue
{
    parameters:
        int capacity;
        @display("i=block/queue");
    gates:
        input in;
        output out;
}

Both the parameters and gates sections are optional, that is, they can be left out if there is no parameter or gate. In addition, the parameters keyword itself is optional too; it can be left out even if there are parameters or properties.

Note that the NED definition doesn’t contain any code to define the operation of the module: that part is expressed in C++. By default, OMNeT++ looks for C++ classes of the same name as the NED type (so here, Queue).

One can explicitly specify the C++ class with the @class property. Classes with namespace qualifiers are also accepted, as shown in the following example that uses themylib::Queue class:

simple Queue
{
    parameters:
        int capacity;
        @class(mylib::Queue);
        @display("i=block/queue");
    gates:
        input in;
        output out;
}

If you have several modules that are all in a common namespace, then a better alternative to @class is the @namespace property. The C++ namespace given with@namespace will be prepended to the normal class name. In the following example, the C++ classes will be mylib::App, mylib::Router and mylib::Queue:

@namespace(mylib);

simple App {
   ...
}

simple Router {
   ...
}

simple Queue {
   ...
}

As you’ve seen, @namespace can be specified at the file level. Moreover, when placed in a file called package.ned, the namespace will apply to all files in the same directory and all directories below.

The implementation C++ classes need to be subclassed from the cSimpleModule library class; chapter [4] of this manual describes in detail how to write them.

Simple modules can be extended (or specialized) via subclassing. The motivation for subclassing can be to set some open parameters or gate sizes to a fixed value (see [3.6]and [3.7]), or to replace the C++ class with a different one. Now, by default, the derived NED module type will inherit the C++ class from its base, so it is important to remember that you need to write out @class if you want it to use the new class.

The following example shows how to specialize a module by setting a parameter to a fixed value (and leaving the C++ class unchanged):

simple Queue
{
   int capacity;
   ...
}

simple BoundedQueue extends Queue
{
   capacity = 10;
}

In the next example, the author wrote a PriorityQueue C++ class, and wants to have a corresponding NED type, derived from Queue. However, it does not work as expected:

simple PriorityQueue extends Queue // wrong! still uses the Queue C++ class
{
}

The correct solution is to add a @class property to override the inherited C++ class:

simple PriorityQueue extends Queue
{
   @class(PriorityQueue);
}

Inheritance in general will be discussed in section [3.13].

3.4 Compound Modules

A compound module groups other modules into a larger unit. A compound module may have gates and parameters like a simple module, but no active behavior is associated with it.

[Although the C++ class for a compound module can be overridden with the @classproperty, this is a feature that should probably never be used. Encapsulate the code into a simple module, and add it as a submodule.]NOTE

    When there is a temptation to add code to a compound module, then encapsulate the code into a simple module, and add it as a submodule.

A compound module declaration may contain several sections, all of them optional:

module Host
{
   types:
       ...
   parameters:
       ...
   gates:
       ...
   submodules:
       ...
   connections:
       ...
}

Modules contained in a compound module are called submodules, and they are listed in the submodules section. One can create arrays of submodules (i.e. submodule vectors), and the submodule type may come from a parameter.

Connections are listed under the connections section of the declaration. One can create connections using simple programming constructs (loop, conditional). Connection behaviour can be defined by associating a channel with the connection; the channel type may also come from a parameter.

Module and channel types only used locally can be defined in the types section as inner types, so that they do not pollute the namespace.

Compound modules may be extended via subclassing. Inheritance may add new submodules and new connections as well, not only parameters and gates. Also, one may refer to inherited submodules, to inherited types etc. What is not possible is to “de-inherit” or modify submodules or connections.

In the following example, we show how to assemble common protocols into a “stub” for wireless hosts, and add user agents via subclassing.

[Module types, gate names, etc. used in the example are fictional, not based on an actual OMNeT++-based model framework]

module WirelessHostBase
{
   gates:
       input radioIn;
   submodules:
       tcp: TCP;
       ip: IP;
       wlan: Ieee80211;
   connections:
       tcp.ipOut --> ip.tcpIn;
       tcp.ipIn <-- ip.tcpOut;
       ip.nicOut++ --> wlan.ipIn;
       ip.nicIn++ <-- wlan.ipOut;
       wlan.radioIn <-- radioIn;
}

module WirelessHost extends WirelessHostBase
{
   submodules:
       webAgent: WebAgent;
   connections:
       webAgent.tcpOut --> tcp.appIn++;
       webAgent.tcpIn <-- tcp.appOut++;
}

The WirelessHost compound module can further be extended, for example with an Ethernet port:

module DesktopHost extends WirelessHost
{
   gates:
       inout ethg;
   submodules:
       eth: EthernetNic;
   connections:
       ip.nicOut++ --> eth.ipIn;
       ip.nicIn++ <-- eth.ipOut;
       eth.phy <--> ethg;
}

3.5 Channels

Channels encapsulate parameters and behaviour associated with connections. Channels are like simple modules, in the sense that there are C++ classes behind them. The rules for finding the C++ class for a NED channel type is the same as with simple modules: the default class name is the NED type name unless there is a @class property (@namespace is also recognized), and the C++ class is inherited when the channel is subclassed.

Thus, the following channel type would expect a CustomChannel C++ class to be present:

channel CustomChannel  // requires a CustomChannel C++ class
{
}

The practical difference compared to modules is that you rarely need to write you own channel C++ class because there are predefined channel types that you can subclass from, inheriting their C++ code. The predefined types are: ned.IdealChannel,ned.DelayChannel and ned.DatarateChannel. (“ned” is the package name; you can get rid of it if you import the types with the import ned.* or similar directive. Packages and imports are described in section [3.14].)

IdealChannel has no parameters, and lets through all messages without delay or any side effect. A connection without a channel object and a connection with anIdealChannel behave in the same way. Still, IdealChannel has its uses, for example when a channel object is required so that it can carry a new property or parameter that is going to be read by other parts of the simulation model.

DelayChannel has two parameters:

  • delay is a double parameter which represents the propagation delay of the message. Values need to be specified together with a time unit (s, ms, us, etc.)
  • disabled is a boolean parameter that defaults to false; when set to true, the channel object will drop all messages.

DatarateChannel has a few additional parameters compared to DelayChannel:

  • datarate is a double parameter that represents the data rate of the channel. Values need to be specified in bits per second or its multiples as unit (bps, kbps,Mbps, Gbps, etc.) Zero is treated specially and results in zero transmission duration, i.e. it stands for infinite bandwidth. Zero is also the default. Data rate is used for calculating the transmission duration of packets.
  • ber and per stand for Bit Error Rate and Packet Error Rate, and allow basic error modelling. They expect a double in the [0,1] range. When the channel decides (based on random numbers) that an error occurred during transmission of a packet, it sets an error flag in the packet object. The receiver module is expected to check the flag, and discard the packet as corrupted if it is set. The default ber andper are zero.

NOTE

      There is no channel parameter that specifies whether the channel delivers the message object to the destination module at the end or at the start of the reception; that is decided by the C++ code of the target simple module. See the

setDeliverOnReceptionStart()

      method of

cGate

    .

The following example shows how to create a new channel type by specializingDatarateChannel:

channel Ethernet100 extends ned.DatarateChannel
{
    datarate = 100Mbps;
    delay = 100us;
    ber = 1e-10;
}

NOTE

    The three built-in channel types are also used for connections where the channel type is not explicitly specified.

You may add parameters and properties to channels via subclassing, and may modify existing ones. In the following example, we introduce distance-based calculation of the propagation delay:

channel DatarateChannel2 extends ned.DatarateChannel
{
    double distance @unit(m);
    delay = this.distance / 200000km * 1s;
}

Parameters are primarily useful as input to the underlying C++ class, but even if you reuse the underlying C++ class of built-in channel types, they may be read and used by other parts of the model. For example, adding a cost parameter (or @cost property) may be observed by the routing algorithm and used for routing decisions. The following example shows a cost parameter, and annotation using a property (@backbone).

channel Backbone extends ned.DatarateChannel
{
    @backbone;
    double cost = default(1);
}

3.6 Parameters

Parameters are variables that belong to a module. Parameters can be used in building the topology (number of nodes, etc), and to supply input to C++ code that implements simple modules and channels.

Parameters can be of type double, int, bool, string and xml; they can also be declared volatile. For the numeric types, a unit of measurement can also be specified (@unit property), to increase type safety.

Parameters can get their value from NED files or from the configuration (omnetpp.ini). A default value can also be given (default()), which is used if the parameter is not assigned otherwise.

The following example shows a simple module that has five parameters, three of which have default values:

simple App
{
    parameters:
        string protocol;       // protocol to use: "UDP" / "IP" / "ICMP" / ...
        int destAddress;       // destination address
        volatile double sendInterval @unit(s) = default(exponential(1s));
                               // time between generating packets
        volatile int packetLength @unit(byte) = default(100B);
                               // length of one packet
        volatile int timeToLive = default(32);
                               // maximum number of network hops to survive
    gates:
        input in;
        output out;
}

Assigning a Value

Parameters may get their values in several ways: from NED code, from the configuration (omnetpp.ini), or even, interactively from the user. NED lets you assign parameters at several places: in subclasses via inheritance; in submodule and connection definitions where the NED type is instantiated; and in networks and compound modules that directly or indirectly contain the corresponding submodule or connection.

For instance, one could specialize the above App module type via inheritance with the following definition:

simple PingApp extends App
{
    parameters:
        protocol = "ICMP/ECHO"
        sendInterval = default(1s);
        packetLength = default(64byte);
}

This definition sets the protocol parameter to a fixed value ("ICMP/ECHO"), and changes the default values of the sendInterval and packetLength parameters.protocol is now locked down in PingApp, its value cannot be modified via further subclassing or other ways. sendInterval and packetLength are still unassigned here, only their default values have been overwritten.

Now, let us see the definition of a Host compound module that uses PingApp as submodule:

module Host
{
    submodules:
        ping : PingApp {
            packetLength = 128B; // always ping with 128-byte packets
        }
        ...
}

This definition sets the packetLength parameter to a fixed value. It is now hardcoded that Hosts send 128-byte ping packets; this setting cannot be changed from NED or the configuration.

It is not only possible to set a parameter from the compound module that contains the submodule, but also from modules higher up in the module tree. If you had a network that employed several Host modules, it could be defined like this:

network Network
{
    submodules:
        host[100]: Host {
            ping.timeToLive = default(3);
            ping.destAddress = default(0);
        }
        ...
}

Parameter assignment can also be placed into the parameters block of the parent compound module, which provides additional flexibility. The following definition sets up the hosts so that half of them pings host #50, and the other half pings host #0:

network Network
{
    parameters:
        host[*].ping.timeToLive = default(3);
        host[0..49].ping.destAddress = default(50);
        host[50..].ping.destAddress = default(0);

    submodules:
        host[100]: Host;
        ...
}

Note the use of asterisk to match any index, and `..‘ to match index ranges.

If you had a number of individual hosts instead of a submodule vector, the network definition could look like this:

network Network
{
    parameters:
        host*.ping.timeToLive = default(3);
        host{0..49}.ping.destAddress = default(50);
        host{50..}.ping.destAddress = default(0);

    submodules:
        host0: Host;
        host1: Host;
        host2: Host;
        ...
        host99: Host;
}

An asterisk matches any substring not containing a dot, and a `..‘ within a pair of curly braces matches a natural number embedded in a string.

In most assigments we have seen above, the left hand side of the equal sign contained a dot and often a wildcard as well (asterisk or numeric range); we call these assignmentspattern assignments or deep assignments.

There is one more wildcard that can be used in pattern assignments, and this is the double asterisk; it matches any sequence of characters including dots, so it can match multiple path elements. An example:

network Network
{
    parameters:
        **.timeToLive = default(3);
        **.destAddress = default(0);
    submodules:
        host0: Host;
        host1: Host;
        ...
}

Note that some assignments in the above examples changed default values, while others set parameters to fixed values. Parameters that received no fixed value in the NED files can be assigned from the configuration (omnetpp.ini).

IMPORTANT

    A non-default value assigned from NED cannot be overwritten later in NED or from ini files; it becomes “hardcoded” as far as ini files and NED usage are concerned. In contrast, default values are possible to overwrite.

A parameter can be assigned in the configuration using a similar syntax as NED pattern assignments (actually, it would be more historically accurate to say it the other way round, that NED pattern assignments use a similar syntax to ini files):

Network.host[*].ping.sendInterval = 500ms  # for the host[100] example
Network.host*.ping.sendInterval = 500ms    # for the host0,host1,... example
**.sendInterval = 500ms

One often uses the double asterisk to save typing. You can write

**.ping.sendInterval = 500ms

Or if you are sure that you don’t accidentally assign some other sendIntervalparameter, you can just write

**.sendInterval = 500ms

Parameter assignments in the configuration are described in section [9.3].

One can also write expressions, including stochastic expressions, in NED files and in ini files as well. For example, here’s how you can add jitter to the sending of ping packets:

**.sendInterval = 1s + normal(0s, 0.001s)  # or just: normal(1s, 0.001s)

If there is no assignment for a parameter in NED or in the ini file, the default value (given with =default(...) in NED) will be applied implicitly. If there is no default value, the user will be asked, provided the simulation program is allowed to do that; otherwise there will be an error. (Interactive mode is typically disabled for batch executions where it would do more harm than good.)

It is also possible to explicitly apply the default (this can sometimes be useful):

**.sendInterval = default

Finally, one can explicitly ask the simulator to prompt the user interactively for the value (again, provided that interactivity is enabled; otherwise this will result in an error):

**.sendInterval = ask

NOTE

      How do you decide whether to assign a parameter from NED or from an ini file? The advantage of ini files is that they allow a cleaner separation of the

model

      and

experiments

    . NED files (together with C++ code) are considered to be part of the model, and to be more or less constant. Ini files, on the other hand, are for experimenting with the model by running it several times with different parameters. Thus, parameters that are expected to change (or make sense to be changed) during experimentation should be put into ini files.

Expressions

Parameter values may be given with expressions. NED language expressions have a C-like syntax, with some variations on operator names: binary and logical XOR are # and##, while ^ has been reassigned to power-of instead. The + operator does string concatenation as well as numeric addition. Expressions can use various numeric, string, stochastic and other functions (fabs(), toUpper(), uniform(), erlang_k(), etc.).

NOTE

      The list of NED functions can be found in Appendix

[22]

    . The user can also extend NED with new functions.

Expressions may refer to module parameters, gate vector and module vector sizes (using the sizeof operator) and the index of the current module in a submodule vector (index).

Expressions may refer to parameters of the compound module being defined, of the current module (with the this. prefix), and to parameters of already defined submodules, with the syntax submodule.parametername (orsubmodule[index].parametername).

volatile

The volatile modifier causes the parameter’s value expression to be evaluated every time the parameter is read. This has significance if the expression is not constant, for example it involves numbers drawn from a random number generator. In contrast, non-volatile parameters are evaluated only once. (This practically means that they are evaluated and replaced with the resulting constant at the start of the simulation.)

To better understand volatile, let’s suppose we have a Queue simple module that has a volatile double parameter named serviceTime.

simple Queue
{
    parameters:
        volatile double serviceTime;
}

Because of the volatile modifier, the queue module’s C++ implementation is expected to re-read the serviceTime parameter whenever a value is needed; that is, for every job serviced. Thus, if serviceTime is assigned an expression like uniform(0.5s, 1.5s), every job will have a different, random service time. To highlight this effect, here’s how you can have a time-varying parameter by exploiting the simTime() NED function that returns the current simulation time:

**.serviceTime = simTime()<1000s ? 1s : 2s  # queue that slows down after 1000s

In practice, a volatile parameters are typically used as a configurable source of random numbers for modules.

NOTE

      This does not mean that a non-volatile parameter could not be assigned a random value like

uniform(0.5s, 1.5s)

      . It can, but that would have a totally different effect: the simulation would use a constant service time, say

1.2975367s

    , chosen randomly at the beginning of the simulation.

Units

One can declare a parameter to have an associated unit of measurement, by adding the@unit property. An example:

simple App
{
    parameters:
        volatile double sendInterval @unit(s) = default(exponential(350ms));
        volatile int packetLength @unit(byte) = default(4KiB);
    ...
}

The @unit(s) and @unit(byte) declarations specify the measurement unit for the parameter. Values assigned to parameters must have the same or compatible unit, i.e.@unit(s) accepts milliseconds, nanoseconds, minutes, hours, etc., and @unit(byte)accepts kilobytes, megabytes, etc. as well.

NOTE

      The list of units accepted by OMNeT++ is listed in the Appendix, see

[19.5.6]

      . Unknown units (

bogomips

    , etc.) can also be used, but there are no conversions for them, i.e. decimal prefixes will not be recognized.

The OMNeT++ runtime does a full and rigorous unit check on parameters to ensure “unit safety” of models. Constants should always include the measurement unit.

The @unit property of a parameter cannot be added or overridden in subclasses or in submodule declarations.

XML Parameters

Sometimes modules need complex data structures as input, which is something that cannot be done well with module parameters. One solution is to place the input data into a custom configuration file, pass the file name to the module in a string parameter, and let the module read and parse the file.

It is somewhat easier if the configuration uses XML syntax, because OMNeT++ contains built-in support for XML files. Using an XML parser (LibXML2 or Expat), OMNeT++ reads and DTD-validates the file (if the XML document contains a DOCTYPE), caches the file (so that references to it from several modules will result in the file being loaded only once), allows selection of parts of the document using an XPath-subset notation, and presents the contents in a DOM-like object tree.

This capability can be accessed via the NED parameter type xml, and the xmldoc()function. You can point xml-type module parameters to a specific XML file (or to an element inside an XML file) via the xmldoc() function. You can assign xml parameters both from NED and from omnetpp.ini.

The following example declares an xml parameter, and assigns an XML file to it. The file name is understood as being relative to the working directory.

simple TrafGen {
    parameters:
        xml profile;
    gates:
        output out;
}

module Node {
    submodules:
        trafGen1 : TrafGen {
            profile = xmldoc("data.xml");
        }
        ...
}

It is also possible to assign an XML element within a file to the parameter, which is useful if you want to group the input of several modules into a single XML file. For example, the following XML file contains two profiles with the IDs gen1 and gen2:

<?xml>
<root>
    <profile id="gen1">
          <param>3</param>
          <param>5</param>
    </profile>
    <profile id="gen2">
          <param>9</param>
    </profile>
</root>

And you can assign each profile to a corresponding submodule using an XPath-like expression:

module Node {
    submodules:
        trafGen1 : TrafGen {
            profile = xmldoc("all.xml", "/root/profile[@id='gen1']");
        }
        trafGen2 : TrafGen {
            profile = xmldoc("all.xml", "/root/profile[@id='gen2']");
        }
}

It is also possible to create an XML document from a string constant, using the xml()function. This is especially useful for creating a default value for xml parameters. An example:

simple TrafGen {
    parameters:
        xml profile = xml("<root/>"); // empty document as default
        ...
}

The xml() function, like xmldoc(), also supports an optional second XPath parameter for selecting a subtree.

3.7 Gates

Gates are the connection points of modules. OMNeT++ has three types of gates: input,output and inout, the latter being essentially an input and an output gate glued together.

A gate, whether input or output, can only be connected to one other gate. (For compound module gates, this means one connection “outside” and one “inside”.) It is possible, though generally not recommended, to connect the input and output sides of an inout gate separately (see section [3.9]).

One can create single gates and gate vectors. The size of a gate vector can be given inside square brackets in the declaration, but it is also possible to leave it open by just writing a pair of empty brackets (“[]”).

When the gate vector size is left open, one can still specify it later, when subclassing the module, or when using the module for a submodule in a compound module. However, it does not need to be specified because one can create connections with the gate++operator that automatically expands the gate vector.

The gate size can be queried from various NED expressions with the sizeof()operator.

NED normally requires that all gates be connected. To relax this requirement, you can annotate selected gates with the @loose property, which turns off the connectivity check for that gate. Also, input gates that solely exist so that the module can receive messages via sendDirect() (see [4.7.5]) should be annotated with @directIn. It is also possible to turn off the connectivity check for all gates within a compound module by specifying theallowunconnected keyword in the module’s connections section.

Let us see some examples.

In the following example, the Classifier module has one input for receiving jobs, which it will send to one of the outputs. The number of outputs is determined by a module parameter:

simple Classifier {
    parameters:
        int numCategories;
    gates:
        input in;
        output out[numCategories];
}

The following Sink module also has its in[] gate defined as a vector, so that it can be connected to several modules:

simple Sink {
    gates:
        input in[];
}

The following lines define a node for building a square grid. Gates around the edges of the grid are expected to remain unconnected, hence the @loose annotation:

simple GridNode {
    gates:
        inout neighbour[4] @loose;
}

WirelessNode below is expected to receive messages (radio transmissions) via direct sending, so its radioIn gate is marked with @directIn.

simple WirelessNode {
    gates:
        input radioIn @directIn;
}

In the following example, we define TreeNode as having gates to connect any number of children, then subclass it to get a BinaryTreeNode to set the gate size to two:

simple TreeNode {
    gates:
        inout parent;
        inout children[];
}

simple BinaryTreeNode extends TreeNode {
    gates:
        children[2];
}

An example for setting the gate vector size in a submodule, using the same TreeNodemodule type as above:

module BinaryTree {
    submodules:
        nodes[31]: TreeNode {
            gates:
                children[2];
        }
    connections:
        ...
}

3.8 Submodules

Modules that a compound module is composed of are called its submodules. A submodule has a name, and it is an instance of a compound or simple module type. In the NED definition of a submodule, this module type is usually given statically, but it is also possible to specify the type with a string expression. (The latter feature, parametric submodule types, will be discussed in section [3.11.1].)

NED supports submodule arrays (vectors) and conditional submodules as well. Submodule vector size, unlike gate vector size, must always be specified and cannot be left open as with gates.

It is possible to add new submodules to an existing compound module via subclassing; this has been described in the section [3.4].

The basic syntax of submodules is shown below:

module Node
{
    submodules:
        routing: Routing;   // a submodule
        queue[sizeof(port)]: Queue;  // submodule vector
        ...
}

As already seen in previous code examples, a submodule may also have a curly brace block as body, where one can assign parameters, set the size of gate vectors, and add/modify properties like the display string (@display). It is not possible to add new parameters and gates.

Display strings specified here will be merged with the display string from the type to get the effective display string. The merge algorithm is described in chapter [11].

module Node
{
    gates:
        inout port[];
    submodules:
        routing: Routing {
            parameters:   // this keyword is optional
                routingTable = "routingtable.txt"; // assign parameter
            gates:
                in[sizeof(port)];  // set gate vector size
                out[sizeof(port)];
        }
        queue[sizeof(port)]: Queue {
            @display("t=queue id $id"); // modify display string
            id = 1000+index;  // use submodule index to generate different IDs
        }
    connections:
        ...
}

An empty body may be omitted, that is,

      queue: Queue;

is the same as

      queue: Queue {
      }

A submodule or submodule vector can be conditional. The if keyword and the condition itself goes after the submodule type, like in the example below:

module Host
{
    parameters:
        bool withTCP = default(true);
    submodules:
        tcp : TCP if withTCP;
        ...
}

The condition is less useful with submodule vectors, as one could also use a zero vector size.

3.9 Connections

Connections are defined in the connections section of compound modules. Connections cannot span across hierarchy levels; one can connect two submodule gates, a submodule gate and the “inside” of the parent (compound) module’s gates, or two gates of the parent module (though this is rarely useful), but it is not possible to connect to any gate outside the parent module, or inside compound submodules.

Input and output gates are connected with a normal arrow, and inout gates with a double-headed arrow “<-->”. To connect the two gates with a channel, use two arrows and put the channel specification in between. The same syntax is used to add properties such as @display to the connection.

Some examples have already been shown in the NED Quickstart section ([3.2]); let’s see some more.

It has been mentioned that an inout gate is basically an input and an output gate glued together. These sub-gates can also be addressed (and connected) individually if needed, as port$i and port$o (or for vector gates, as port$i[$k$] and port$o[k]).

Gates are specified as modulespec.gatespec (to connect a submodule), or as gatespec(to connect the compound module). modulespec is either a submodule name (for scalar submodules), or a submodule name plus an index in square brackets (for submodule vectors). For scalar gates, gatespec is the gate name; for gate vectors it is either the gate name plus an index in square brackets, or gatename++.

The gatename++ notation causes the first unconnected gate index to be used. If all gates of the given gate vector are connected, the behavior is different for submodules and for the enclosing compound module. For submodules, the gate vector expands by one. For a compound module, after the last gate is connected, ++ will stop with an error.

NOTE

    Why is it not possible to expand a gate vector of the compound module? The model structure is built in top-down order, so new gates would be left unconnected on the outside, as there is no way in NED to “go back” and connect them afterwards.

When the ++ operator is used with $i or $o (e.g. g$i++ or g$o++, see later), it will actually add a gate pair (input+output) to maintain equal gate sizes for the two directions.

Channel Specification

Channel specifications (-->channelspec--> inside a connection) are similar to submodules in many respect. Let’s see some examples!

The following connections use two user-defined channel types, Ethernet100 andBackbone. The code shows the syntax for assigning parameters (cost and length) and specifying a display string (and NED properties in general):

a.g++ <--> Ethernet100 <--> b.g++;
a.g++ <--> Backbone {cost=100; length=52km; ber=1e-8;} <--> b.g++;
a.g++ <--> Backbone {@display("ls=green,2");} <--> b.g++;

When using built-in channel types, the type name can be omitted; it will be inferred from the parameters you assign.

a.g++ <--> {delay=10ms;} <--> b.g++;
a.g++ <--> {delay=10ms; ber=1e-8;} <--> b.g++;
a.g++ <--> {@display("ls=red");} <--> b.g++;

If datarate, ber or per is assigned, ned.DatarateChannel will be chosen. Otherwise, if delay or disabled is present, it will be ned.DelayChannel; otherwise it isned.IdealChannel. Naturally, if other parameter names are assigned in a connection without an explicit channel type, it will be an error (with “ned.DelayChannel has no such parameter” or similar message).

Connection parameters, similarly to submodule parameters, can also be assigned using pattern assignments, albeit the channel names to be matched with patterns are a little more complicated and less convenient to use. A channel can be identified with the name of its source gate plus the channel name; the channel name is currently always channel. It is illustrated by the following example:

module Queueing
{
    parameters:
        source.out.channel.delay = 10ms;
        queue.out.channel.delay = 20ms;
    submodules:
        source: Source;
        queue: Queue;
        sink: Sink;
    connections:
        source.out --> ned.DelayChannel --> queue.in;
        queue.out --> ned.DelayChannel <--> sink.in;

Using bidirectional connections is a bit trickier, because both directions must be covered separately:

network Network
{
    parameters:
        hostA.g$o[0].channel.datarate = 100Mbps; // the A -> B connection
        hostB.g$o[0].channel.datarate = 100Mbps; // the B -> A connection
        hostA.g$o[1].channel.datarate = 1Gbps;   // the A -> C connection
        hostC.g$o[0].channel.datarate = 1Gbps;   // the C -> A connection
    submodules:
        hostA: Host;
        hostB: Host;
        hostC: Host;
    connections:
        hostA.g++ <--> ned.DatarateChannel <--> hostB.g++;
        hostA.g++ <--> ned.DatarateChannel <--> hostC.g++;

Also, it is not always easy to figure out which gate indices map to the connections you want to configure. If connection objects could be given names to override the default name “channel”, that would make it easier to identify connections in patterns. This feature is planned for future OMNeT++ releases.

Channel Names

The default name given to channel objects is "channel". Since OMNeT++ 4.3 it is possible to specify the name explicitly, and also to override the default name per channel type. The purpose of custom channel names is to make addressing easier when channel parameters are assigned from ini files.

The syntax for naming a channel in a connection is similar to submodule syntax: name: type. Since both name and type are optional, the colon must be there after name even iftype is missing, in order to remove the ambiguity.

Examples:

r1.pppg++ <--> eth1: EthernetChannel <--> r2.pppg++;
a.out --> foo: {delay=1ms;} --> b.in;
a.out --> bar: --> b.in;

In the absence of an explicit name, the channel name comes from the @defaultnameproperty of the channel type if that exists.

channel Eth10G extends ned.DatarateChannel like IEth {
    @defaultname(eth10G);
}

There’s a catch with @defaultname though: if the channel type is specified with a**.channelname.liketype= line in an ini file, then the channel type’s @defaultnamecannot be used as channelname in that configuration line, because the channel type would only be known as a result of using that very configuration line. To illustrate the problem, consider the above Eth10G channel, and a compound module containing the following connection:

r1.pppg++ <--> <> like IEth <--> r2.pppg++;

Then consider the following inifile:

**.eth10G.typename = "Eth10G"   # Won't match! The eth10G name would come from
                                #   the Eth10G type - catch-22!
**.channel.typename = "Eth10G"  # OK, as lookup assumes the name "channel"
**.eth10G.datarate = 10.01Gbps  # OK, channel already exists with name "eth10G"

The anomaly can be avoided by using an explicit channel name in the connection, not using @defaultname, or by specifying the type via a module parameter (e.g. writing<param> like ... instead of <> like ...).

3.10 Multiple Connections

Simple programming constructs (loop, conditional) allow creating multiple connections easily.

This will be shown in the following examples.

Chain

One can create a chain of modules like this:

module Chain
    parameters:
        int count;
    submodules:
        node[count] : Node {
            gates:
                port[2];
        }
    connections allowunconnected:
        for i = 0..count-2 {
            node[i].port[1] <--> node[i+1].port[0];
        }
}

Binary Tree

One can build a binary tree in the following way:

simple BinaryTreeNode {
    gates:
        inout left;
        inout right;
        inout parent;
}

module BinaryTree {
    parameters:
        int height;
    submodules:
        node[2^height-1]: BinaryTreeNode;
    connections allowunconnected:
        for i=0..2^(height-1)-2 {
            node[i].left <--> node[2*i+1].parent;
            node[i].right <--> node[2*i+2].parent;
        }
}

Note that not every gate of the modules will be connected. By default, an unconnected gate produces a run-time error message when the simulation is started, but this error message is turned off here with the allowunconnected modifier. Consequently, it is the simple modules’ responsibility not to send on an unconnected gate.

Random Graph

Conditional connections can be used to generate random topologies , for example. The following code generates a random subgraph of a full graph:

module RandomGraph {
    parameters:
        int count;
        double connectedness; // 0.0<x<1.0
    submodules:
        node[count]: Node {
            gates:
                in[count];
                out[count];
        }
    connections allowunconnected:
        for i=0..count-1, for j=0..count-1 {
            node[i].out[j] --> node[j].in[i]
                if i!=j && uniform(0,1)<connectedness;
        }
}

Note the use of the allowunconnected modifier here too, to turn off error messages produced by the network setup code for unconnected gates.

3.10.1 Connection Patterns

Several approaches can be used when you want to create complex topologies which have a regular structure; three of them are described below.

“Subgraph of a Full Graph”

This pattern takes a subset of the connections of a full graph. A condition is used to “carve out” the necessary interconnection from the full graph:

for i=0..N-1, for j=0..N-1 {
    node[i].out[...] --> node[j].in[...] if condition(i,j);
}

The RandomGraph compound module (presented earlier) is an example of this pattern, but the pattern can generate any graph where an appropriate condition(i,j) can be formulated. For example, when generating a tree structure, the condition would return whether node j is a child of node i or vice versa.

Though this pattern is very general, its usage can be prohibitive if the number of nodes Nis high and the graph is sparse (it has much less than N2 connections). The following two patterns do not suffer from this drawback.

“Connections of Each Node”

The pattern loops through all nodes and creates the necessary connections for each one. It can be generalized like this:

for i=0..Nnodes, for j=0..Nconns(i)-1 {
    node[i].out[j] --> node[rightNodeIndex(i,j)].in[j];
}

The Hypercube compound module (to be presented later) is a clear example of this approach. BinaryTree can also be regarded as an example of this pattern where the inner j loop is unrolled.

The applicability of this pattern depends on how easily the rightNodeIndex(i,j) function can be formulated.

“Enumerate All Connections”

A third pattern is to list all connections within a loop:

for i=0..Nconnections-1 {
    node[leftNodeIndex(i)].out[...] --> node[rightNodeIndex(i)].in[...];
}

This pattern can be used if leftNodeIndex(i) and rightNodeIndex(i) mapping functions can be sufficiently formulated.

The Chain module is an example of this approach where the mapping functions are extremely simple: leftNodeIndex(i)=i and rightNodeIndex(i) = i+1. The pattern can also be used to create a random subset of a full graph with a fixed number of connections.

In the case of irregular structures where none of the above patterns can be employed, you can resort to listing all connections, like you would do it in most existing simulators.

3.11 Parametric Submodule and Connection Types

3.11.1 Parametric Submodule Types

A submodule type may be specified with a module parameter of the type string, or in general, with any string-typed expression. The syntax uses the like keyword.

Let us begin with an example:

network Net6
{
    parameters:
        string nodeType;
    submodules:
        node[6]: <nodeType> like INode {
            address = index;
        }
    connections:
        ...
}

It creates a submodule vector whose module type will come from the nodeTypeparameter. For example, if nodeType is set to "SensorNode", then the module vector will consist of sensor nodes, provided such module type exists and it qualifies. What this means is that the INode must be an existing module interface, which the SensorNodemodule type must implement (more about this later).

As already mentioned, one can write an expression between the angle brackets. The expression may use the parameters of the parent module and of previously defined submodules, and has to yield a string value. For example, the following code is also valid:

network Net6
{
    parameters:
        string nodeTypePrefix;
        int variant;
    submodules:
        node[6]: <nodeTypePrefix + "Node" + string(variant)> like INode {
           ...
}

The corresponding NED declarations:

moduleinterface INode
{
    parameters:
        int address;
    gates:
        inout port[];
}

module SensorNode like INode
{
    parameters:
        int address;
        ...
    gates:
        inout port[];
        ...
}

The “<nodeType> like INode” syntax has an issue when used with submodule vectors: does not allow you to specify different types for different indices. The following syntax is better suited for submodule vectors:

The expression between the angle brackets may be left out altogether, leaving you with a pair of empty angle brackets, <>:

module Node
{
    submodules:
        nic: <> like INic;  // type name expression left unspecified
        ...
}

Now the submodule type name is expected to be defined via typename pattern assignments. Typename pattern assignments look like pattern assignments for the submodule’s parameters, only the parameter name is replaced by the typenamekeyword. Typename pattern assignments may also be written in the configuration file. In a network that uses the above Node NED type, typename pattern assignments would look like this:

network Network
{
    parameters:
        node[*].nic.typename = "Ieee80211g";
    submodules:
        node: Node[100];
}

A default value may also be specified between the angle brackets; it will be used if there is no typename assignment for the module:

module Node
{
    submodules:
        nic: <default("Ieee80211b")> like INic;
        ...
}

3.11.2 Parametric Connection Types

Parametric connection types work similarly to parametric submodule types, and the syntax is similar as well. A basic example that uses a parameter of the parent module:

a.g++ <--> <channelType> like IMyChannel <--> b.g++;
a.g++ <--> <channelType> like IMyChannel {@display("ls=red");} <--> b.g++;

The expression may use loop variables, parameters of the parent module and also parameters of submodules (e.g. host[2].channelType).

The type expression may also be absent, and then the type is expected to be specified using typename pattern assignments:

a.g++ <--> <> like IMyChannel <--> b.g++;
a.g++ <--> <> like IMyChannel {@display("ls=red");} <--> b.g++;

A default value may also be given:

a.g++ <--> <default("Ethernet100")> like IMyChannel <--> b.g++;
a.g++ <--> <default(channelType)> like IMyChannel <--> b.g++;

The corresponding type pattern assignments:

a.g$o[0].channel.typename = "Ethernet1000";  // A -> B channel
b.g$o[0].channel.typename = "Ethernet1000";  // B -> A channel

3.12 Metadata Annotations (Properties)

NED properties are metadata annotations that can be added to modules, parameters, gates, connections, NED files, packages, and virtually anything in NED. @display,@class, @namespace, @unit, @prompt, @loose, @directIn are all properties that have been mentioned in previous sections, but those examples only scratch the surface of what properties are used for.

Using properties, one can attach extra information to NED elements. Some properties are interpreted by NED, by the simulation kernel; other properties may be read and used from within the simulation model, or provide hints for NED editing tools.

Properties are attached to the type, so you cannot have different properties defined per-instance. All instances of modules, connections, parameters, etc. created from any particular location in the NED files have identical properties.

The following example shows the syntax for annotating various NED elements:

@namespace(foo);  // file property

module Example
{
    parameters:
       @node;   // module property
       @display("i=device/pc");   // module property
       int a @unit(s) = default(1); // parameter property
    gates:
       output out @loose @labels(pk);  // gate properties
    submodules:
       src: Source {
           parameters:
              @display("p=150,100");  // submodule property
              count @prompt("Enter count:"); // adding a property to a parameter
           gates:
              out[] @loose;  // adding a property to a gate
       }
       ...
    connections:
       src.out++ --> { @display("ls=green,2"); } --> sink1.in; // connection prop.
       src.out++ --> Channel { @display("ls=green,2"); } --> sink2.in;
}

Property Indices

Sometimes it is useful to have multiple properties with the same name, for example for declaring multiple statistics produced by a simple module. Property indices make this possible.

A property index is an identifier or a number in square brackets after the property name, such as eed and jitter in the following example:

simple App {
    @statistic[eed](title="end-to-end delay of received packets";unit=s);
    @statistic[jitter](title="jitter of received packets");
}

This example declares two statistics as @statistic properties, @statistic[eed] and@statistic[jitter]. Property values within the parentheses are used to supply additional info, like a more descriptive name (title="..." or a unit (unit=s). Property indices can be conveniently accessed from the C++ API as well; for example it is possible to ask what indices exist for the "statistic" property, and it will return a list containing"eed" and "jitter").

In the @statistic example the index was textual and meaningful, but neither is actually required. The following dummy example shows the use of numeric indices which may be ignored altogether by the code that interprets the properties:

simple Dummy {
    @foo[1](what="apples";amount=2);
    @foo[2](what="oranges";amount=5);
}

Note that without the index, the lines would actually define the same @foo property, and would overwrite each other’s values.

Indices also make it possible to override entries via inheritance:

simple DummyExt extends Dummy {
    @foo[2](what="grapefruits"); // 5 grapefruits instead of 5 oranges
}

Data Model

Properties may contain data, given in parentheses; the data model is quite flexible. To begin with, properties may contain no value or a single value:

@node;
@node(); // same as @node
@class(FtpApp2);

Properties may contain lists:

@foo(Sneezy,Sleepy,Dopey,Doc,Happy,Bashful,Grumpy);

They may contain key-value pairs, separated by semicolons:

@foo(x=10.31; y=30.2; unit=km);

In key-value pairs, each value can be a (comma-separated) list:

@foo(coords=47.549,19.034;labels=vehicle,router,critical);

The above examples are special cases of the general data model. According to the data model, properties contain key-valuelist pairs, separated by semicolons. Items in valuelistare separated by commas. Wherever key is missing, values go on the valuelist of thedefault key, the empty string.

Value items may contain words, numbers, string constants and some other characters, but not arbitrary strings. Whenever the syntax does not permit some value, it should be enclosed in quotes. This quoting does not affect the value because the parser automatically drops one layer of quotes; thus, @class(TCP) and @class("TCP") are exactly the same. If you want the quotes to be part of the value, use escaped quotes:@foo("\"some string\"").

There are also some conventions. One can use properties to tag NED elements; for example, a @host property could be used to mark all module types that represent various hosts. This property could be recognized e.g. by editing tools, by topology discovery code inside the simulation model, etc.

The convention for such a “marker” property is that any extra data in it (i.e. within parens) is ignored, except a single word false, which has the special meaning of “turning off” the property. Thus, any simulation model or tool that interprets properties should handle all the following forms as equivalent to @host: @host(), @host(true), @host(anything-but-false), @host(a=1;b=2); and @host(false) should be interpreted as the lack of the @host tag.

Overriding and Extending Property Values

When you subclass a NED type, use a module type as submodule or use a channel type for a connection, you may add new properties to the module or channel, or to its parameters and gates, and you can also modify existing properties.

When modifying a property, the new property is merged with the old one, with a few simple rules. New keys simply get added. If a key already exists in the old property, items in its valuelist overwrite items on the same position in the old property. A single hyphen ($-$) as valuelist item serves as “antivalue”, it removes the item at the corresponding position.

Some examples:

base @prop
new @prop(a)
result @prop(a)
base @prop(a,b,c)
new @prop(,-)
result @prop(a,,c)
base @prop(foo=a,b)
new @prop(foo=A,,c;bar=1,2)
result @prop(foo=A,b,c;bar=1,2)

NOTE

      The above merge rules are part of NED, but the code that interprets properties may have special rules for certain properties. For example, the

@unit

      property of parameters is not allowed to be overridden, and

@display

      is merged with special although similar rules (see Chapter

[11]

    ).

3.13 Inheritance

Inheritance support in the NED language is only described briefly here, because several details and examples have been already presented in previous sections.

In NED, a type may only extend (extends keyword) an element of the same component type: a simple module may only extend a simple module, compound module may only extend a compound module, and so on. Single inheritance is supported for modules and channels, and multiple inheritance is supported for module interfaces and channel interfaces. A network is a shorthand for a compound module with the @isNetworkproperty set, so the same rules apply to it as to compound modules.

However, a simple or compound module type may implement (like keyword) several module interfaces; likewise, a channel type may implement several channel interfaces.

IMPORTANT

      When you extend a simple module type both in NED and in C++, you must use the

@class

    property to tell NED to use the new C++ class — otherwise your new module type inherits the C++ class of the base!

Inheritance may:

  • add new properties, parameters, gates, inner types, submodules, connections, as long as names do not conflict with inherited names
  • modify inherited properties, and properties of inherited parameters and gates
  • it may not modify inherited submodules, connections and inner types

For details and examples, see the corresponding sections of this chapter (simple modules[3.3], compound modules [3.4], channels [3.5], parameters [3.6], gates [3.7], submodules[3.8], connections [3.9], module interfaces and channel interfaces [3.11.1]).

3.14 Packages

Having all NED files in a single directory is fine for small simulation projects. When a project grows, however, it sooner or later becomes necessary to introduce a directory structure, and sort the NED files into them. NED natively supports directory trees with NED files, and calls directories packages. Packages are also useful for reducing name conflicts, because names can be qualified with the package name.

NOTE

    NED packages are based on the Java package concept, with minor enhancements. If you are familiar with Java, you’ll find little surprise in this section.

Overview

When a simulation is run, you must tell the simulation kernel the directory which is the root of your package tree; let’s call it NED source folder. The simulation kernel will traverse the whole directory tree, and load all NED files from every directory. You can have several NED directory trees, and their roots (the NED source folders) should be given to the simulation kernel in the NEDPATH variable. NEDPATH can be specified in several ways: as an environment variable (NEDPATH), as a configuration option (ned-path), or as a command-line option to the simulation runtime (-n). NEDPATH is described in detail in chapter [10].

Directories in a NED source tree correspond to packages. If you have NED files in a<root>/a/b/c directory (where <root> gets listed in NEDPATH), then the package name is a.b.c. The package name has to be explicitly declared at the top of the NED files as well, like this:

package a.b.c;

The package name that follows from the directory name and the declared package must match; it is an error if they don’t. (The only exception is the root package.ned file, as described below.)

By convention, package names are all lowercase, and begin with either the project name (myproject), or the reversed domain name plus the project name (org.example.myproject). The latter convention would cause the directory tree to begin with a few levels of empty directories, but this can be eliminated with a toplevelpackage.ned.

NED files called package.ned have a special role, as they are meant to represent the whole package. For example, comments in package.ned are treated as documentation of the package. Also, a @namespace property in a package.ned file affects all NED files in that directory and all directories below.

The toplevel package.ned file can be used to designate the root package, which is useful for eliminating a few levels of empty directories resulting from the package naming convention. For example, if you have a project where you want to have all NED types under the org.example.myproject package but don’t want to have the directories named org, example and myproject in the source tree, then you can put apackage.ned file in the source root directory with the package declarationorg.example.myproject. This will cause a directory foo under the root to be interpreted as package org.example.myproject.foo, and NED files in them must contain that as package declaration. Only the root package.ned can define the package,package.ned files in subdirectories must follow it.

Let’s look at the INET Framework as example, which contains hundreds of NED files in several dozen packages. The directory structure looks like this:

INET/
    src/
        base/
        transport/
            tcp/
            udp/
            ...
        networklayer/
        linklayer/
        ...
    examples/
        adhoc/
        ethernet/
        ...

The src and examples subdirectories are denoted as NED source folders, so NEDPATH is the following (provided INET was unpacked in /home/joe):

/home/joe/INET/src;/home/joe/INET/examples

Both src and examples contain package.ned files to define the root package:

// INET/src/package.ned:
package inet;
// INET/examples/package.ned:
package inet.examples;

And other NED files follow the package defined in package.ned:

// INET/src/transport/tcp/TCP.ned:
package inet.transport.tcp;

Name Resolution, Imports

We already mentioned that packages can be used to distinguish similarly named NED types. The name that includes the package name (a.b.c.Queue for a Queue module in the a.b.c package) is called fully qualified name; without the package name (Queue) it is called simple name.

Simple names alone are not enough to unambiguously identify a type. Here is how you can refer to an existing type:

  1. By fully qualified name. This is often cumbersome though, as names tend to be too long;
  2. Import the type, then the simple name will be enough;
  3. If the type is in the same package, then it doesn’t need to be imported; it can be referred to by simple name

Types can be imported with the import keyword by either fully qualified name, or by a wildcard pattern. In wildcard patterns, one asterisk (“*“) stands for “any character sequence not containing period”, and two asterisks (“**“) mean “any character sequence which may contain period”.

So, any of the following lines can be used to import a type calledinet.protocols.networklayer.ip.RoutingTable:

import inet.protocols.networklayer.ip.RoutingTable;
import inet.protocols.networklayer.ip.*;
import inet.protocols.networklayer.ip.Ro*Ta*;
import inet.protocols.*.ip.*;
import inet.**.RoutingTable;

If an import explicitly names a type with its exact fully qualified name, then that type must exist, otherwise it is an error. Imports containing wildcards are more permissive, it is allowed for them not to match any existing NED type (although that might generate a warning.)

Inner types may not be referred to outside their enclosing types, so they cannot be imported either.

Name Resolution With “like”

The situation is a little different for submodule and connection channel specifications using the like keyword, when the type name comes from a string-valued expression (see section [3.11.1] about submodule and channel types as parameters). Imports are not much use here: at the time of writing the NED file it is not yet known what NED types will be suitable for being “plugged in” there, so they cannot be imported in advance.

There is no problem with fully qualified names, but simple names need to be resolved differently. What NED does is this: it determines which interface the module or channel type must implement (i.e. ... like INode), and then collects the types that have the given simple name AND implement the given interface. There must be exactly one such type, which is then used. If there is none or there are more than one, it will be reported as an error.

Let us see the following example:

module MobileHost
{
    parameters:
        string mobilityType;
    submodules:
        mobility: <mobilityType> like IMobility;
        ...
}

and suppose that the following modules implement the IMobility module interface:inet.mobility.RandomWalk, inet.adhoc.RandomWalk,inet.mobility.MassMobility. Also suppose that there is a type calledinet.examples.adhoc.MassMobility but it does not implement the interface.

So if mobilityType="MassMobility", then inet.mobility.MassMobility will be selected; the other MassMobility doesn’t interfere. However, ifmobilityType="RandomWalk", then it is an error because there are two matchingRandomWalk types. Both RandomWalk‘s can still be used, but one must explicitly choose one of them by providing a package name:mobilityType="inet.adhoc.RandomWalk".

The Default Package

It is not mandatory to make use of packages: if all NED files are in a single directory listed on the NEDPATH, then package declarations (and imports) can be omitted. Those files are said to be in the default package.