翻译总结自: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端口。
    另外,这种形式布置的端口,在network的connections中进行连接时,就不能用之前的例子中所写的诸如 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 module:tic[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关键字,并在其中定义了新的channel。types关键字只能用在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定义双向连接,而不是用input和output 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.h与tictoc13_m.cc(从文件名而不是message class名中创建)。这两个文件中将自动生成一个继承自cMessage的子类TicTocMsg13。该class将对每个字段生成getter与setter方法。

我们在写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.h与xxx_m.cc(与msg文件名而不是message名相对应)。这两个文件中会自动生成一个继承自cMessage的消息类,这个消息类就是我们在msg文件中用关键字message建立的那个消息。此外,这个消息类中,对每个字段都实现了getter与setter方法。
  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.h和xxx_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就可以了。

最新文章

  1. 深入理解Java之泛型
  2. 使用cordova插件barcodescanner遇到的坑
  3. select接收后台返回值的解决方案
  4. HTTPS背后的加密算法
  5. andriod 新建 Activity_ Form (详细设置)
  6. LINQ.CS
  7. dorado7第一次使用感受
  8. (太强大了) - Linux 性能监控、测试、优化工具
  9. Java8新特性
  10. Kernel-Scheduler implementation
  11. JavaScript中依赖注入详细解析
  12. 关于phpcms中模块_tag.class.php中的pc_tag()方法的含义
  13. Hibernate基础学习(四)&mdash;对象-关系映射(上)
  14. SpringBoot(7) SpringBoot启动方式
  15. ros查看摄像头是否打开正常
  16. Java - 20 Java 继承
  17. android基础----&gt;WidGet的使用
  18. 在SQL中有时候我们需要查看现在正在SQL Server执行的命令
  19. 一、怎样使用eclipse查看JDK源码
  20. Scrum Meeting Beta - 2

热门文章

  1. IEEE754浮点数表示法
  2. 学习Java第10天
  3. 使用require.context实现前端工程自动化
  4. AtCoder AGC002 简要题解
  5. CF1270G Subset with Zero Sum
  6. 一次线上服务高 CPU 占用优化实践 (转)
  7. java实现ajax excel导入
  8. nvidia-smi
  9. PHP+mysql常考题
  10. Solution -「ZJOI2012」「洛谷 P2597」灾难