使用 TicToc 学习 OMNeT++——第 4 部分 - 将其变成真正的网络

系列文章目录

提示:本文是本系列教程的第5篇,前期工作请参考以下4篇 :
使用TicToc学习OMNeT++——第 0 部分 - 简介
使用TicToc学习OMNeT++——第 1 部分 - 入门
使用TicToc学习OMNeT++——第 2 部分 - 运行仿真
使用TicToc学习OMNeT++——第 3 部分 - 增强 2 节点 TicToc


提示:本人的第5篇文章,写得不好,请读者多多包涵


前言

提示:这里说明了本文要记录的大概内容:

本文基于原版英文教程的第4部分:Part 4 - Turning it Into a Real Network进行翻译和论述,并结合仿真手册,用户指南以厘清基本概念。以及对应于原教程中布置的练习(如果有的话),由本人完成的的作业。


提示:以下是本篇文章正文内容

一、原文翻译

原文经过本人的人工润色,主要对一些复杂嵌套的英文语句调整语序,修改一些术语翻译错误,然后是修正句内的修饰关系。此外,原文翻译中还会补充一些自己实操过程中的截图。

Part 4 - Turning it Into a Real Network 第 4 部分 - 将其变成真正的网络

4.1 More than two nodes 4.1 两个以上节点

Now we’ll make a big step: create several tic modules and connect them into a network. For now, we’ll keep it simple what they do: one of the nodes generates a message, and the others keep tossing it around in random directions until it arrives at a predetermined destination node.
现在我们将迈出一大步:创建多个 tic 模块并将它们连接到网络中。现在,我们将保持简单的操作:其中一个节点生成一条消息,其他节点继续沿随机方向抛掷它,直到它到达预定的目标节点。

The NED file will need a few changes. First of all, the Txc module will need to have multiple input and output gates:
NED 文件需要进行一些更改。首先,该 Txc 模块需要有多个输入和输出门:

simple Txc10
{
    parameters:
        @display("i=block/routing"); //i 指 icon,i=block/routing意为 图标=block目录下的routing图标
    gates:
        // declare in[] and out[] to be vector gates 声明 in[]和 out[] 为向量门
        input in[];
        output out[];
}

The [ ] turns the gates into gate vectors. The size of the vector (the number of gates) will be determined where we use Txc to build the network.
[ ] 门变成门向量。矢量的大小(门数)将取决于我们使用 Txc 构建网络的位置。

network Tictoc10
{
    submodules:
        tic[6]: Txc10; // 类型为 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++;
}

Here we created 6 modules as a module vector, and connected them.
在这里,我们创建了 6 个模块作为模块向量,并将它们连接起来。

The resulting topology looks like this:
生成的拓扑如下所示:
生成的拓扑
In this version, tic[0] will generate the message to be sent around. This is done in initialize(), with the help of the getIndex() function which returns the index of the module in the vector.
在此版本中, tic[0] 将生成要发送的消息。这是在 initialize() 中完成的,借助函数 getIndex() 返回向量中模块的索引。

The meat of the code is the forwardMessage() function which we invoke from handleMessage() whenever a message arrives at the node. It draws a random gate number, and sends out message on that gate.
代码的核心是 每当消息到达节点时 我们从 handleMessage() 中调用的 forwardMessage() 的函数。它抽取一个随机的门号,并在该门上发出消息。
提示:下面的2块代码是本人改进过的,以实现exercises的要求

void Txc10::forwardMessage(cMessage *msg)
{
    // In this example, we just pick a random gate to send it on. 在这个例子中,我们只是随机选择一个门来发送它。
    // We draw a random number between 0 and the size of gate 'out[]'. 我们在 0 和 门(向量)'out[]'的大小之间抽取一个随机数。
    int n = gateSize("out");
    int k = intuniform(0, n-1); // 服从整数均匀分布的随机变量函数
    int arrvlGtIndex;
    if(getIndex() != 0){ // 若当前模块的索引非 0,因为到达模块0中的自消息是没有到达的门的,所以肯无法获取门索引
        arrvlGtIndex = msg->getArrivalGate()->getIndex(); // 获取消息到达的门Index/索引
    }
    if(n > 1){ // 模块0 的 out门向量恰为1,因此 n>1 隐式地排除了 修改模块0 的 k, 从而避免了死循环

        EV << "Message " << "arrived at " << "port in[" << arrvlGtIndex << "]\n";
        while(k == arrvlGtIndex){ // 避免消息再次从到达端口出发
            k = intuniform(0, n-1); // 服从整数均匀分布的随机变量
        }
    }


    EV << "Forwarding message" << msg << " on port out[" << k << "]\n"; // 在端口k上转发消息
    send(msg, "out", k);
}

When the message arrives at tic[3], its handleMessage() will delete the message.
当消息到达时 tic[3] ,它的handleMessage()将删除该消息

void Txc10::handleMessage(cMessage *msg) // 定义收到消息后的消息处理行为
{
    if(getIndex() == 3){ // 若当前模块的索引为 3
        // Message arrived. 消息到达
        EV << "Message " << msg << "arrived.\n";
        delete msg;
    }
    else{
        // We need to forward the message. 我们需要转发消息
        forwardMessage(msg); // 注意 若当前模块索引为0,则此时初始消息在作为自消息到达后会在这里被转发出去
    }
}

See the full code in txc10.cc
在 txc10.cc 中查看完整代码

Exercise 锻炼
You’ll notice that this simple “routing” is not very efficient: often the packet keeps bouncing between two nodes for a while before it is sent to a different direction. This can be improved somewhat if nodes don’t send the packet back to the sender. Implement this. Hints: cMessage::getArrivalGate(), cGate::getIndex(). Note that if the message didn’t arrive via a gate but was a self-message, then getArrivalGate() returns NULL.
您会注意到,这种简单的“路由”效率不高:通常数据包在发送到不同方向之前会在两个节点之间持续一段时间。如果节点不将数据包发送回发送方,则可以对此有所改进。实现这一点。提示: cMessage::getArrivalGate() , cGate::getIndex() .请注意,如果消息不是通过门到达的,而是自消息,则 getArrivalGate() 返回 NULL 。

Sources: tictoc10.ned, txc10.cc, omnetpp.ini
源(代码):tictoc10.ned, txc10.cc, omnetpp.ini

4.2 Channels and inner type definitions 4.2 通道和内部类型定义

Our new network definition is getting quite complex and long, especially the connections section. Let’s try to simplify it. The first thing we notice is that the connections always use the same delay parameter. It is possible to create types for the connections (they are called channels) similarly to simple modules. We should create a channel type which specifies the delay parameter and we will use that type for all connections in the network.
我们的新网络定义变得非常复杂和冗长,尤其是连接部分。让我们试着简化它。我们注意到的第一件事是连接始终使用相同的 delay 参数。可以为连接创建类型(它们称为通道),类似于简单模块。我们应该创建一个指定delay参数的通道类型,我们将该类型用于网络中的所有连接。

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

As you have noticed we have defined the new channel type inside the network definition by adding a types section. This type definition is only visible inside the network. It is called as a local or inner type. You can use simple modules as inner types too, if you wish.
如您所见,我们通过添加一个 types 段在网络定义中定义了新的通道类型。此类型定义仅在网络内部可见。它被称为局部或内部类型。如果您愿意,您也可以将简单模块用作内部类型。

Note 注意
We have created the channel by specializing the built-in DelayChannel. built-in channels can be found inside the ned package. Thats why we used the full type name ned.DelayChannel after the extends keyword.
我们通过专门化内置的 DelayChannel. 内置通道可在 ned 包内找到。这就是为什么我们在 extends 关键字后使用完整类型名称 ned.DelayChannel 的原因

Now let’s check how the connections section changed.
现在让我们检查一下该 connections 部分是如何变化的。

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

As you see we just specify the channel name inside the connection definition. This allows to easily change the delay parameter for the whole network.
如您所见,我们只在连接定义中指定通道名称。这样可以很容易地更改整个网络的延迟参数。

Sources: tictoc11.ned, txc11.cc, omnetpp.ini
源(代码):tictoc11.ned, txc11.cc, omnetpp.ini

4.3 Using two-way connections 4.3 使用双向连接

If we check the connections section a little more, we will realize that each node pair is connected with two connections. One for each direction. OMNeT++ 4 supports two way connections, so let’s use them.
如果我们再检查一下该 connections 部分,我们将意识到每个节点对都通过两个连接相连。每个方向一个。OMNeT++ 4 支持双向连接,让我们使用它们。

First of all, we have to define two-way (or so called inout) gates instead of the separate input and output gates we used previously.
首先,我们必须定义双向(或所谓的 inout )门,而不是我们之前使用的单个 input 门和 output 门。

simple Txc12
{
    parameters:
        @display("i=block/routing"); //i 应该是指 icon/图标
    gates:
        inout gate[];  // declare two way connections 声明双向连接
}

The new connections section would look like this:
新的 connections 段将如下所示:

connections: // 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++;
}

We have modified the gate names so we have to make some modifications to the C++ code.
我们修改了门名,因此我们必须对 C++ 代码进行一些修改。

void Txc12::forwardMessage(cMessage *msg)
{
    // In this example, we just pick a random gate to send it on. 在这个例子中,我们只是随机选择一个门来发送它。
    // We draw a random number between 0 and the size of gate 'gate[]'. 我们在 0 和 门(向量)'gate[]'的大小之间抽取一个随机数。
    int n = gateSize("gate");
    int k = intuniform(0, n-1); // 服从整数均匀分布的随机变量

    int arrvlGtIndex;
    if(getIndex() != 0){ // 避免求模块0中到达自消息的 门索引
        arrvlGtIndex = msg->getArrivalGate()->getIndex(); // 获取消息到达的门Index/索引
    }
    if(n > 1){ // 若端口数大于1

        EV << "Message " << "arrived at " << "port gate$i[" << arrvlGtIndex << "]\n";
        while(k == arrvlGtIndex){ // 避免消息再次从到达端口出发
            k = intuniform(0, n-1); // 服从整数均匀分布的随机变量
        }
    }


    EV << "Forwarding message" << msg << " on gate[" << k << "]\n"; // 在端口k上转发消息
    // $o and $i suffix is used to identify the input/output part of a two way gate $o和$i后缀用于标识双向门的输入/输出部分
    send(msg, "gate$o", k);
}

Note 注意

The special $i and $o suffix after the gate name allows us to use the connection’s two direction separately.
门名后的特殊的 $i 和 $o 后缀允许我们单独使用连接的两个方向。

Sources: tictoc12.ned, txc12.cc, omnetpp.ini
源(代码):tictoc12.ned, txc12.cc, omnetpp.ini

4.4 Defining our message class 4.4 定义我们的消息类

In this step the destination address is no longer hardcoded tic[3] – we draw a random destination, and we’ll add the destination address to the message.
在此步骤中,目标地址不再是硬编码的 tic[3] - 我们抽取一个随机目标,并将目标地址添加到消息中。

The best way is to subclass cMessage and add destination as a data member. Hand-coding the message class is usually tedious because it contains a lot of boilerplate code, so we let OMNeT++ generate the class for us. The message class specification is in tictoc13.msg:
最好的方法是子类化 cMessage 并将目标添加为数据成员。手动编码消息类通常很繁琐,因为它包含大量样板代码,因此我们让 OMNeT++ 为我们生成类。消息类规范位于 tictoc13.msg

message TictocMsg13 // 一定要注意消息类名
{
    int source;
    int destination;
    int hopCount = 0;
}
Note 注意
See Section 6 of the OMNeT++ manual for more details on messages.
有关消息的更多详细信息,请参阅 OMNeT++ 手册的第 6 节。

The makefile is set up so that the message compiler, opp_msgc is invoked and it generates tictoc13_m.h and tictoc13_m.cc from the message declaration (The file names are generated from the tictoc13.msg file name, not the message type name). They will contain a generated TicTocMsg13 class subclassed from [cMessage]; the class will have getter and setter methods for every field.
设置生成文件是为了调用消息编译器 opp_msgc 并从消息声明生成 tictoc13_m.h tictoc13_m.cc (文件名是从 tictoc13.msg 的文件名生成的,而不是从消息类型名称生成的)。它们将包含生成的 继承自[cMessage]的 TicTocMsg13 类;该类将为每个字段提供 getter 和 setter 方法。

We’ll include tictoc13_m.h into our C++ code, and we can use TicTocMsg13 as any other class.
我们将包含 tictoc13_m.h 进我们的 C++ 代码中,我们可以像任何其他类一样使用 TicTocMsg13

// Include a generated file: the header file created from tictoc13.msg. 包含一个生成文件:从 tictoc13.msg 创建的头文件。
// It contains the definition of the TictocMsg13 class, derived from cMessage. 它包含从 cMessage派 生的 TictocMsg13类的定义。
#include "tictoc13_m.h" // _后的 m 应该指的是 msg,所以这个文件名的意思是从 tictoc13.msg 生成的文件

For example, we use the following lines in generateMessage() to create the message and fill its fields.
例如,我们使用 generateMessage() 中的以下行来创建消息并填写其字段。

    // Create message object and set source and destination field. 创建消息对象并设置源(地址)和目的(地址)字段。
    TictocMsg13 *msg = new TictocMsg13(msgname);
    msg->setSource(src);
    msg->setDestination(dest);
    return msg;

Then, handleMessage() begins like this:
然后, handleMessage() 像这样开始:

    // 类型转换
    TictocMsg13 *ttmsg = check_and_cast<TictocMsg13 *>(msg);
    // 若消息的目的地址与当前模块的索引一致
    if(ttmsg->getDestination() == getIndex()){

In the argument to handleMessage(), we get the message as a cMessage* pointer. However, we can only access its fields defined in TicTocMsg13 if we cast msg to TicTocMsg13*. Plain C-style cast ((TicTocMsg13*)msg) is not safe because if the message is not a TicTocMsg13 after all the program will just crash, causing an error which is difficult to explore.
在 handleMessage() 的参数中,我们将消息作为 cMessage* 指针获取。但是,只有当我们将 msg 转换为 TicTocMsg13* 时,我们才能访问其的 在 TicTocMsg13 中 定义的 字段。普通的 C 样式转换 ((TicTocMsg13*)msg)是不安全的,因为如果消息不是一个 TicTocMsg13 (类实例),程序就会崩溃,导致难以探查的错误。

C++ offers a solution which is called dynamic_cast. Here we use check_and_cast<>() which is provided by OMNeT++: it tries to cast the pointer via dynamic_cast, and if it fails it stops the simulation with an error message, similar to the following:
C++ 提供了一种解法,称为 dynamic_cast .这里我们使用OMNeT++ 提供的 check_and_cast<>() :它尝试通过 dynamic_cast 强制转换指针,如果失败,它会停止模拟并显示错误消息,类似于以下内容:
类型转换失败的错误消息
In the next line, we check if the destination address is the same as the node’s address. The getIndex() member function returns the index of the module in the submodule vector (remember, in the NED file we declarared it as tic[6]: Txc13, so our nodes have addresses 0…5).
在下一行中,我们检查目标地址是否与节点的地址相同。 getIndex() 成员函数返回子模块向量中模块的索引(请记住,在 NED 文件中,我们将其声明为 tic[6]: Txc13 ,因此我们的节点的地址为 0…5)。

To make the model execute longer, after a message arrives to its destination the destination node will generate another message with a random destination address, and so forth. Read the full code: txc13.cc
为了使模型执行更长时间,在一条消息到达其目的地后,目标节点将生成另一条具有随机目的地址的消息,依此类推。阅读完整代码: txc13.cc

When you run the model, it’ll look like this:
运行模型时,它看起来像这样:
注:原教程里用得是Tictoc14的图
模型运行时
You can click on the messages to see their content in the inspector window. Double-clicking will open the inspector in a new window. (You’ll either have to temporarily stop the simulation for that, or to be very fast in handling the mouse). The inspector window displays lots of useful information; the message fields can be seen on the Contents page.
您可以单击消息以在检查器窗口中查看其内容。双击将在新窗口中打开检查器。(您要么必须暂时停止模拟,要么非常快速地处理鼠标)。检查器窗口显示许多有用的信息;消息的字段可以在“内容”页面上看到。
注:原教程里用得是Tictoc14的图
检查器窗口

Exercise 锻炼
In this model, there is only one message underway at any given moment: nodes only generate a message when another message arrives at them. We did it this way to make it easier to follow the simulation. Change the module class so that instead, it generates messages periodically. The interval between messages should be a module parameter, returning exponentially distributed random numbers.
在这个模型中,在任何给定时刻只有一条消息在行进:节点只有在另一条消息到达节点时才会生成一条消息。我们这样做是为了更容易跟踪模拟。更改模块类,以便它定期生成消息。消息之间的间隔应为模块参数,返回指数分布的随机数。

Sources: tictoc13.ned, txc13.cc, omnetpp.ini
源(代码):tictoc13.ned, txc13.cc, omnetpp.ini

二、补充内容

  1. 本人完成的原教程中所布置练习的作业。
  2. 为辨析教程中涉及到的概念,本人查阅仿真手册得到的相关内容。

1. 满足教程中练习的要求的代码

在NED文件中的简单模块Txc13类型定义中添加了易变参数msgInterval

simple Txc13
{
    parameters:
        volatile double msgInterval @unit(s); // 消息间隔 注意NED语句也要加分号!
        @display("i=block/routing"); //i 应该是指 icon/图标
    gates:
        inout gate[];  // declare two way connections 声明双向连接
}

在INI文件中添加了易变参数的赋值语句

[Config Tictoc13]
network = Tictoc13
Tictoc13.tic[*].msgInterval = exponential(6s)   # 网络中共6个结点,故参数设为6

修改了cpp文件中的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 a new one!");
        delete ttmsg;

        // Generate another one. 生成另一个消息
        EV << "Generating another message: ";
        TictocMsg13 *newmsg = generateMessage();
        EV << newmsg << endl;

        // 先调度新生成的消息 为自消息, 注意同1个消息不能被发送2次,我觉得是因为 哪怕是自消息被发送后,它也不再属于当前模块
        simtime_t msgInterval = par("msgInterval"); // 从NED文件中定义的参数msgInterval获取消息间隔
        EV << "Message arrived, starting to wait " << msgInterval << " secs...\n";
        scheduleAt(simTime() + msgInterval, newmsg); // 写太多python, 结果忘记加;和声明了/(ㄒoㄒ)/~~

        //forwardMessage(newmsg);
    }
    else{
        // We need to forward the message. 我们需要转发消息
        forwardMessage(ttmsg);
    }
}

2. 消息的本质

根据《仿真手册》的4.1.3节的第1段

OMNeT++ uses messages to represent events.1 Messages are represented by instances of the
cMessage class and its subclasses. Messages are sent from one module to another – this means that the place where the “event will occur” is the message’s destination module, and the model time when the event occurs is the arrival time of the message. Events like “timeout expired” are implemented by the module sending a message to itself.
OMNeT++ 使用消息来代表事件。消息由cMessage类及其子类的实例表示。1 消息从一个模块发送到另一个模块—这意味着“事件将发生”的地点是消息的目标模块,而事件发生的模型时间是消息的到达时间。像“超时到期”这样的事件是由模块向自身发送消息来实现的。


总结

提示:这里对文章进行总结:

终于完成了第5篇,主要补充了本人完成的练习的作业,还分析了一下消息的本质。总之,也许本文并不会有什么大的用处,但也是一种自我学习的过程,也是对自己的学习心得的一种记录,最后如果能对读者有点用处的话,那就再好不过了😂。


  1. 1For all practical purposes. Note that there is a class called cEvent that cMessage subclasses from, but it is only used internal to the simulation kernel.
    出于所有实际目的。请注意,有一个名为 cEvent 的被 cMessage 继承的类,但它仅在模拟内核内部使用。 ↩︎ ↩︎

  • 42
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值