omnet++:官方文檔翻譯總結(三)


翻譯總結自:Turning it Into a Real Network - OMNeT++ Technical Articles

官方文檔翻譯總結(二),本節主要是真實網絡的搭建

Part 4 - Turning it Into a Real Network

①多於兩個節點的網絡:Tictoc10

現在我們要邁出一大步了:創造多個tic module並把它們連入網絡。

現在,我們構建一個簡單的多節點網絡:其中一個節點產生消息發往一個隨機方向,該節點繼續隨機發送,……,剩下的節點執行同樣的行為,直到它到達一個預先確定好的目的節點。

NED文件需要一些改變:

Txc module需要有多個input、output gates

simple Txc10
{
    parameters:
        @display("i=block/routing");
    gates:
        input in[];//定義in[]和out[]標注一系列的進出口
        output out[];
}

[ ]把單個gate變成了gate數組。數組大小(數組中gate數量)決定了網絡中輸入輸出端口的數量:

network Tictoc10
{
    submodules:
        tic[6]:Txc10;
    connections:
        tic[0].out++ --> {delay=100ms;} --> tic[1].in++;
        tic[0].in++ <-- {delay=100ms;} <-- tic[1].out++;
        
        tic[1].out++ --> {  delay = 100ms; } --> tic[2].in++;
        tic[1].in++ <-- {  delay = 100ms; } <-- tic[2].out++;

        tic[1].out++ --> {  delay = 100ms; } --> tic[4].in++;
        tic[1].in++ <-- {  delay = 100ms; } <-- tic[4].out++;

        tic[3].out++ --> {  delay = 100ms; } --> tic[4].in++;
        tic[3].in++ <-- {  delay = 100ms; } <-- tic[4].out++;

        tic[4].out++ --> {  delay = 100ms; } --> tic[5].in++;
        tic[4].in++ <-- {  delay = 100ms; } <-- tic[5].out++;
}

上段NED代碼中我們構建了6個module作為一個module vector,並將它們相連接,結果拓撲如下:

 

 其中tic[0]產生消息。這一步是在initialize()中實現的,實現過程中需要借助函數getIndex()——這個函數返回module在vector中的下標。

代碼的核心是forwardMessage()函數,當一個消息到達時,我們在處理消息的handleMessage()中調用這個函數。這個方法中產生了一個隨機數,並將消息從這個隨機數代表的gate中發送出去:

void Txc10::forwardMessage(cMessage * msg){
    //在本例中,我們選擇一個隨機gate將消息發送出去
    //這個隨機數的取值范圍為0~size(out[])-1
    int n=gateSize("out");
    int k=intuniform(0,n-1);
    
    EV<<"Forward message "<<msg<<" on port out["<<k<<"]\n";
    send(msg,"out",k);
}

當消息到達tic[3]時,它的handleMessage()將會刪除該消息(即目標節點是tic[3]

補充:使用過程中,你可能會發現這個簡單的路由算法並不是十分有效的——包會經常在兩個節點間循環反彈一會兒再發送到別的節點。我們可以改進這個算法——通過某些中間節點后不從輸入端口發送出去。提示:cMessage::getArrivalGate(),cGate::getIndex()。另外,如果某個消息不經過端口發送出去,也就是說這個消息是一個self-message,那么getArrivalGate()將返回null

總結:tictoc10

  1. 當一個simple module有多個輸入輸出端口時,在NED文件中,定義simple module文件時,gates關鍵字中的端口,不能定義為類似 input : in 這樣的一般單個變量,而應該定義成 input : in[ ] 這樣的vector變量,表明一個simple module有多個in端口。
    另外,這種形式布置的端口,在networkconnections中進行連接時,就不能用之前的例子中所寫的諸如 xxx.out --> { ... } --> xxx.in;而應該是xxx.out++ --> { ... } --> xxx.in++
  2. network的構建中,如果想快速定義多個同類型simple module節點,可以使用vector變量(也就是數組變量):
        submodules:
            tic[6] : Txc10;

    只是這樣定義的話,我們無法在Design模式下設置每一個節點的位置,而只能讓IDE運行時自行布置。

  3. 如果我們有一個module vector(比如上文的tic[6]),需要根據module號來決定消息處理方式,那么可以在handleMessage中,用以下語句加以判斷:
    if(getIndex()==0){
        //0號module
        ...
    }

     

  4. 如果采用總結1中那種vector型多端口,發送消息時應該指定從哪個端口發出去:send( msg , "out" , k )
  5. 使用gateSize("out")可以知道這個module有多少個out gate
  6. 在ned文件中通過vector一次定義了多個simple moduletic[6] : Txc10,這些節點無法在運行時手動Design模式下設置它們的位置;只能在運行時讓IDE自行布局;如果對布局不滿意的話,可以通過“Re-layout”按鈕進行重布局,不過樣式有限,多次重布局后就會回到最初的布局結構了。

     

     

 ②通道channel和內部類型定義:tictoc11

我們的網絡定義已經變得非常復雜和龐大了,特別是在connections這一節。我們可以對其嘗試優化:首先,我們注意到connections中總是用到了delay parameter。我們可以為connections創造相關types(這里是所謂channels,就像我們給simple modules添加para那樣。我們可以創造一個channel類型指定delay,之后我們就可以使用它來構建網絡中的connections

network Tictoc11
{
    types:
        channel Channel extends ned.DelayChannel{
                delay=100ms;
        }
    submodules:

我們在network中添加了types關鍵字,並在其中定義了新的channeltypes關鍵字只能用在network中。它是一種局部的、內部的type。如果我們想要的話,我們可以使用simple modules作為內部type。

之后connections中的代碼就變成了:

connections:
    tic[0].out++ --> Channel --> tic[1].in++;
        tic[0].in++ <-- Channel <-- tic[1].out++;

        ...

        tic[4].out++ --> Channel --> tic[5].in++;
        tic[4].in++ <-- Channel <-- tic[5].out++;
}

我們在connections中通過channel名指定了這個channel標記的delay,這樣,我們就可以在隨后為整個網絡輕松修改所有delay了。

總結:tictoc11

  1. 本例中,我們用channel代替之前寫的delay=100ms;
  2. channel定義在ned文件下network中的types關鍵字中,用以實現信道時延的channel都是繼承自ned.DelayChannel,定義方式如下:
    network Tictoc11
    {
        types:
            channel Channel extends ned.DelayChannel{
                delay=100ms;
            }
  3. channel的使用,用在network下的connections關鍵字中,用來對端口與端口間的信道進行某些規定:不是xxx.out、xxx.in而是xxx.in++xxx.out++
    connections:
        tic[0].out++ --> Channel --> tic[1].in++;
        tic[0].in++ <-- Channel <-- tic[1].out++;
        ...
  4. 在ned文件中對network的channel進行修改,可以實現同時對整個鏈路修改的目的

③雙向連接:tictoc12

你可能發現了,connections中每個節點對都有兩個連接,每個代表一個方向。OMNET++支持雙向連接,所以我們可以用以下方法使用它。

我們通過inout gate定義雙向連接,而不是用inputoutput gate這種我們之前使用的形式:

simple Txc12
{
    parameters:
        @display("i=block/routing");
    gates:
        inout gate[];
}

修改后的connections就將像下邊這樣:

connections:
    tic[0].gate++ <--> Channel <--> tic[1].gate++;
    tic[1].gate++ <--> Channel <--> tic[2].gate++;
    ...
    tic[4].gate++ <--> Channel <--> tic[5].gate++;
}

由於我們修改了gate名,所以我們需要在C++中進行修改:

void Txc12::forwardMessage(cMessage * msg)
{
    int n = gateSize("gate");
    int k = intuniform(0,n-1);
    
    EV<<"Forwarding message " <<msg<<" on gate["<<k<<"]\n";
    //$o與$i后綴用以區分一個雙向gate的output/input端口
    send(msg,"gate$o",k);
}

總結:tictoc12

  1. inout gate,相當於某個gate即是input又是output,用起來比單個input和output方便多了;
  2. 如果某個節點有多個inout gate,可以定義一個vector類型的inout gate,實現起來像下邊這樣:
    simple Txc12
    {
        ...
        gates:
            inout gate[];
    }

    這種vector,就像我們在tictoc11的總結3中所說,在使用時也要用到++符號,就像gate++這樣;

  3. 與之前的單向收發的節點相比,使用時的信道連接方式,也是雙向的,即<-->這樣,而不是<---->這樣:
    connections:
        tic[0].gate++ <--> Channel <-->tic[1].gate++;

     

  4. 使用send發送消息時,需要指明通過后綴$i$o指明發送端口
    send(msg,"gate$o",k)
  5. 如果要想知道有多少個雙向端口,也是用gateSize,就像我們在tictoc10總結5中所說:
    int n = gateSize("gate");

     

④消息類(message class):tictoc13

在本節中,目的節點不再是固定的tic[3]——我們用一個隨機的目的地,我們把目的地址添加到message中。

最好的方法是繼承cMessage得到新的message子類,並將目的地指定為成員屬性。手寫全部代碼通常不太現實,因為它包含了太多的樣版代碼,所以我們可以用OMNET++來為我們生成class。本例中我們在tictoc13.msg中指定message class:

message TicTocMsg13
{
    int source;
    int destination;
    int hopCount=0;
}

生成文件tictoc13.msg建立后,message編譯器就會自動生成tictoc13_m.htictoc13_m.cc(從文件名而不是message class名中創建)。這兩個文件中將自動生成一個繼承自cMessage的子類TicTocMsg13。該class將對每個字段生成gettersetter方法。

我們在寫C++代碼的cc文件中,需要引入tictoc13_m.h,這樣我們就可以使用TicTocMsg13這個message class了。

#include <tictoc13_m.h>

例如,我們可以在generateMessage()中通過如下代碼生成message,並填充它的各個字段:

TicTocMsg * msg = new TicTocMsg13(msgname);
msg->setSource(src);
msg->setDestination(dest);
return msg;

之后的handleMessage()的開始幾行代碼就可以寫成如下的形式:

void Txc13::handleMessage(cMessage * msg){
    TicTocMsg13 * ttmsg = check_and_cast <TicTocMsg13 *>(msg);
    if( ttmsg->getDestination()==getIndex()){

handleMessage中,我們接受一個消息作為參數,其類型是cMessage *指針。只是,我們當我們將普通的cMessage轉化為TicTocMsg*后,就只能訪問TicTocMsg13中定義的那些字段。我們經常使用的那種消息類型轉化方式,如(TicTocMsg13 *) msg並不安全,因為如果隨后的程序中得到的msg並不是TicTocMsg13類型,就會報錯。

C++用dynamic_cast機制來解決這種問題。本例中我們使用check_and_cast<>(),該方法嘗試通過dynamic_cast的方式傳遞指針,如果方法失敗,它就會終止仿真並彈出錯誤消息,類似下邊這樣:

 下一行中,我們檢查目的地址是否和節點地址相同。為了使model執行的更長遠,在一個消息到達目的地時,目的節點將生成另一條包含着隨機目的地址的消息,發送出去……

當我們運行model,它看起來像下邊這樣:

 我們可以點擊消息(就是圖中的小紅點)在左下角的窗口中查看它的內容。

在本model中,在任意指定的時候只有一個正在運行着的消息:當另一個消息到達時,節點只生成一個消息。我們之所以這樣做,是為了使仿真更簡單。如果想讓消息的產生存在間隔,我們可以修改module以達成這一目的。消息間隔應該是一個module parameter,返回指數分布的隨機數。

總結:tictoc13

  1. 在msg文件中指定message class,每個message中有一些信息字節:
    message TicTocMsg13
    {
        int source;
        int destination;
        int hopCount=0;
    }

    msg文件名為xxx.msg格式;message class定義時用message關鍵字;

  2. xxx.msg文件建立后,編譯器自動生成xxx_m.hxxx_m.cc(與msg文件名而不是message名相對應)。這兩個文件中會自動生成一個繼承自cMessage消息類,這個消息類就是我們在msg文件中用關鍵字message建立的那個消息。此外,這個消息類中,對每個字段都實現了gettersetter方法。
  3. 在負責整個網絡邏輯的cc文件中,通過#include<xxx_m.h>引入之前創建的message,在其中訪問和設置字段值,通常,在生成message的代碼之后,通過msg->setXXX()設置值,在handleMessage()中,通過msg->getXXX()獲取這些值。

    通常,我們可以單獨寫一個產生消息的函數generateMessage()函數,在其中實現創建新消息、設置字段值、返回創建的新消息的功能:

    xxxMsg *  Txc13 :: generateMessage()
    {
        ...
        xxxMsg * msg = new xxxMsg( msgname );
        msg->setSource(src);
        msg->setDestination(dest);
        return msg;
    }

    上文中的xxxMsg就是我們在msg文件中指定的message類。

  4. handleMessage()中,用xxxMsg處理收到的普通message的代碼為:
    void handleMessage(cMessage * msg){
        xxxMsg *  xmsg = check_and_cast <xxxMsg *>(msg);
        if( xmsg->getXXX()==getIndex() )

    check_and_cast < xxxMsg *> (msg)可以安全地把一個普通的cMessage類型,變為我們需要的那種xxxMsg。轉換完成后,就可以用getter方法提取我們之前定義的和設置了值的字段

 

由於tictoc13這個例子很有代表性,現對其代碼逐句加以分析解釋。

NED文件:tictoc13.ned

simple Txc13
{
    parameters:
        @display("i=block/routing");
    gates:
        inout gate[];
}

network Tictoc13
{
    types:
        channel Channel extends ned.DelayChannel{
            delay = 100ms;
        }
    submodules:
        tic[6] : Txc13;
    connections:
        tic[0].gate++ <--> Channel <--> tic[1].gate++;
        tic[1].gate++ <--> Channel <--> tic[2].gate++;
        tic[1].gate++ <--> Channel <--> tic[4].gate++;
        tic[3].gate++ <--> Channel <--> tic[4].gate++;
        tic[4].gate++ <--> Channel <--> tic[5].gate++;
}

ned文件比較簡單,沒什么需要多說的,需要注意的地方都在上個代碼中給標紅了。

msg文件:tictoc13.msg

message TicTocMsg13
{
    int source;
    int destination;
    int hopCount = 0;
}

message定義了每個節點發送、接收的消息的格式。

本例中,每個接收、發送、在信道中傳輸的消息中都有三個字段:source、destination、hopCount;分別標識源地址(創建新消息的節點地址)目的地址當前跳數。由於每個新消息的hopCount都是0,所以可以在此處直接將hopCount在定義時初始化為0。而source、destination都需要在消息傳遞過程中動態確定,所以此處並不初始化,而是在cc文件中建立消息時,通過setter方法設置。在cc文件中訪問這些字段時,通過getter方法設置。

cc文件:txc13.cc

#include<stdio.h>
#include<string.h>
#include<omnetpp.h>
using namespace omnetpp;
#include<tictoc13_m.h> //①

class Txc13 : public cSimpleModule
{
    protected:
        virtual TicTocMsg13 * generateMessage();  //②
        virtual void forwardMessage(TicTocMsg13 * msg);
        virtual void initialize() override;
        virtual void handleMessage(cMessage * msg) override;
};
Define_Module(Txc13);

void Txc13::initialize()  //③
{
    if(getIndex() == 0){  
        TicTocMsg13 * msg = generateMessage();
        scheduleAt(0.0 , msg);
    }
}

void Txc13::handleMessage(cMessage * msg){   //④
    TicTocMsg13 * ttmsg = check_and_cast <TicTocMsg13 *>(msg); 
    
    if(ttmsg->getDesination()==getIndex()){
        EV<<"Message "<<ttmsg<<" arrived after "<<ttmsg->getHopCount()<<" hops.\n";
        bubble("ARRIVED, starting new one!");
        delete ttmsg;

        EV<<"Generating another message: ";
        TicTocMsg * newmsg = generateMessage();
        EV<<newmsg <<endl;
        forwardMessage(newmsg);
    }
    else{
        forwardMessage(ttmsg);
    }
}

TicTocMsg13 * Txc13::generateMessage()  //⑤
{
    int src = getIndex();
    int n = getVectorSize();
    int dest = intuniform(0,n-2);
    if(dest >= src)
        dest++;

    char msgname[20];
    sprintf(msgname,"tic-%d-to-%d",src,dest);

    TicTocMsg13 * msg = new TicTocMsg13(msgname);
    msg->setSource(src);
    msg->setDestination(dest);
    return msg;
}

void Txc13 :: forwardMessage(TicTocMsg13 * msg)  //⑥
{
    msg->setHopCount(msg->getHopCount()+1);
    
    int n = gateSize("gate");
    int k = intuniform(0,n-1);
    EV<<"Forwarding message "<<msg<<" on gate["<<k<<"]\n";
    send(msg,"gate$o",k);
}

 ①引入之前message所在的文件

#include<tictoc13_m.h>

在我們完成xxx.msg之后,IDE就會自動生成一個xxx_m.hxxx_m.cc,在其中自動實現了我們自己寫的message,使用時需要用#include引入,之后才能使用。

    virtual TicTocMsg13 *generateMessage();
    virtual void forwardMessage(TicTocMsg13 *msg);
    virtual void initialize() override;
    virtual void handleMessage(cMessage *msg) override;

除了我們最常用、也是最常見的initialize()handleMessage()方法之外,我們又加入了兩個方法generateMessage()forwardMessage().。這兩個函數的作用分別是創建新消息轉發消息

initialize()

void Txc13::initialize()
{
    if (getIndex() == 0) {
        TicTocMsg13 *msg = generateMessage();
        scheduleAt(0.0, msg);
    }
}

在初始化函數中,我們指定了消息的起點——節點號為0的點,這個點是開啟整個仿真的地方。

if(getIndex()==0)

對每一個節點初始化時,都會檢查它的節點號,如果是0,就進行如下操作:

TicTocMsg13 * msg = generateMessage();
scheduleAt(0.0,msg);

第一句是,該節點創建了新消息,這個消息也是整個網絡的起始消息,由它激活整個網絡。

第二句話是這個消息被創建后直接發給自己,是一個self-message。這樣,我們就不用在初始化函數中指定這個消息從哪里發出去,而是采用handleMessage()方法中跟普通消息一樣的轉發方式。省卻了很多代碼。

④我們把handleMessage()方法放在最后說,先說另外兩個方法。

generateMessage()

TicTocMsg13 * Txc13::generateMessage()
{
    int src = getIndex();  // our module index
    int n = getVectorSize();  // module vector size
    int dest = intuniform(0, n-2);
    if (dest >= src)
        dest++;

    char msgname[20];
    sprintf(msgname, "tic-%d-to-%d", src, dest);

    TicTocMsg13 *msg = new TicTocMsg13(msgname);
    msg->setSource(src);
    msg->setDestination(dest);
    return msg;
}

generateMessage()中我們創建並返回了一個新消息,由於需要返回新消息,所以函數類型就是TicTocMsg13 *,而不同於另外三個方法的void

1)

    int src = getIndex();  
    int n = getVectorSize();  
    int dest = intuniform(0, n-2);
    if (dest >= src)
        dest++;

第一部分,我們指定了源地址目的地址,源地址也就是創建消息的節點的地址(其實就是節點號),通過getIndex()直接獲取到,其實也就是該節點的節點號。目的地址是除了該節點以外的任意其他節點(通過隨機數函數intuniform()來確定),至於dest >= src的判斷,個人認為應該是用 ==

2)

char msgname[20];
sprintf(msgname,"tic-%d-to-%d",src,dest);
TicTocMsg13 * msg = new TicTocMsg13(msgname);

第二部分,我們根據源地址目的地址的不同,創造了不同的消息,其中用sprintf創建消息名的語句,我們會經常用到。

3)

msg->setSource(src);
msg->setDestination(dest);
return msg;

第三部分,我們為消息的部分字段進行賦值,通過setter方法。

消息創建完了,消息內的各字段也有了,就完成的新消息的創建,將它return。

forwardMessage()

void Txc13::forwardMessage(TicTocMsg13 *msg)
{
    msg->setHopCount(msg->getHopCount()+1);

    int n = gateSize("gate");
    int k = intuniform(0, n-1);
    EV << "Forwarding message " << msg << " on gate[" << k << "]\n";
    send(msg, "gate$o", k);
}

1)

    msg->setHopCount(msg->getHopCount()+1);

消息轉發前,使該消息的跳數加一

2)

    int n = gateSize("gate");
    int k = intuniform(0, n-1);
    EV << "Forwarding message " << msg << " on gate[" << k << "]\n";
    send(msg, "gate$o", k);

選擇合適端口第一二行)把消息轉發第四行)出去,轉發前向控制台輸出信息第三行),表明已經進行了消息轉發。

通過gateSize("gate")我們知道了這個節點有多少可供使用的端口。再通過intuniform(0,n-1)我們選擇了一個隨機的和正常的端口以供消息轉發,這里的正常是指,不會選擇大於端口數量的端口號進行轉發(由gateSize進行保證)。

handleMessage

void Txc13::handleMessage(cMessage *msg)
{
    TicTocMsg13 *ttmsg = check_and_cast<TicTocMsg13 *>(msg);

    if (ttmsg->getDestination() == getIndex()) {
        // Message arrived.
        EV << "Message " << ttmsg << " arrived after " << ttmsg->getHopCount() << " hops.\n";
        bubble("ARRIVED, starting new one!");
        delete ttmsg;

        // Generate another one.
        EV << "Generating another message: ";
        TicTocMsg13 *newmsg = generateMessage();
        EV << newmsg << endl;
        forwardMessage(newmsg);
    }
    else {
        // We need to forward the message.
        forwardMessage(ttmsg);
    }
}

消息處理函數一直是鏈路轉發的核心函數,所以我們放在最后來說。

1)

    TicTocMsg13 *ttmsg = check_and_cast<TicTocMsg13 *>(msg);

由於本例中我們的消息都是之前自己建立的消息TicTocMsg13這種類型,而handleMessage默認收到的消息是cMessage類型,所以我們要進行類型轉換,這就是這句話的目的。

轉換之后,我們就得到自定義的message ttmsg

2)

if (ttmsg->getDestination() == getIndex())

如果消息的目的地字段(即destination字段)是當前節點,那么說明該節點就是該消息的終點,我們就可以在其中寫消息到達目的節點后的相關處理了;否則本節點就是消息傳遞的中間節點,就需要做另一些處理了。

3)

  if(ttmsg->getDestination() == getIndex()) {
        // Message arrived.
        EV << "Message " << ttmsg << " arrived after " << ttmsg->getHopCount() << " hops.\n";
        bubble("ARRIVED, starting new one!");
        delete ttmsg;

        // Generate another one.
        EV << "Generating another message: ";
        TicTocMsg13 *newmsg = generateMessage();
        EV << newmsg << endl;
        forwardMessage(newmsg);
    }

消息到達終點時,終點節點要做兩件事——I、顯示消息到達的信息;II、刪除該消息(因為沒用了);III、產生另一個新消息,繼續之前的轉發過程。

  •         EV << "Message " << ttmsg << " arrived after " << ttmsg->getHopCount() << " hops.\n";
            bubble("ARRIVED, starting new one!");

    輸出消息經歷的跳數到控制台日志中;彈出一個bubble,告訴人們消息已經到達了;

  • delete ttmsg;

    刪除舊的消息,因為已經沒用了;

  •         EV << "Generating another message: ";
            TicTocMsg13 *newmsg = generateMessage();
            EV << newmsg << endl;
            forwardMessage(newmsg);

    通過generateMessage()生成新消息,通過forwardMessage把它轉發出去;整個網絡會一直重復這一創建——轉發——刪除過程,過程中只有一個消息在網絡上傳播。

4)

else{
    forwardMessage(ttmsg);
}

如果節點是中間節點,就只需要轉發消息forwardMessage就可以了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM