OMNeT++ tutorial 2节点tic-toc的进阶

OMNeT++ tutorial 2节点tic-toc的进阶

实验目的

成功运行tictoc之后,实现了两个节点相互发送消息的功能。本组实验包含从tictoc2到tictoc9的内容,在tictoc的基础上进行修改,实现各种新功能。实验的完整源代码在tutorial中可以找到。

实验内容

前置知识

  • 安装和运行OMNeT++模拟器,并运行tictoc(可参考链接
  • tictoc项目由一个ned文件,一个cc文件和omnetpp.ini文件构成。ned文件用于定义组件并组合成大的单元(例如网络),C++文件中实现模块的具体功能,omnetpp.ini指定希望模拟的具体网络
  • 接下来的实验代码可以和tictoc放在同一个目录下,每次运行实验需要修改对应的omnetpp.ini

tictoc2&3:添加图标、输出日志与计数器设置

tictoc3.ned中,描述了在GUI中添加图标的方法:

simple Txc3
{
    parameters:
        @display("i=block/routing");//添加一个默认图标
    gates:
        input in;
        output out;
}

network Tictoc3
{
    submodules:
        tic: Txc3 {
            parameters:
                @display("i=,cyan");//不改变图标,只设置颜色(青色)
        }
        toc: Txc3 {
            parameters:
                @display("i=,gold");//同上,设置金色
        }
    connections:
        tic.out --> {  delay = 100ms; } --> toc.in;
        tic.in <-- {  delay = 100ms; } <-- toc.out;
}

原始tictoc中互相发送消息的操作是不会自动停止的,本实验新增一个计数器,在十次交换之后删除消息,使交换过程停止。其中EV<<语句用于在日志窗口中输出日志信息。

#include <stdio.h>
#include <string.h>
#include <omnetpp.h>
using namespace omnetpp;
 //添加一个计数器,在十次交换之后删除信息
class Txc3 : public cSimpleModule
{
  private:
    int counter;  //添加一个计数器,int类型

  protected:
    virtual void initialize() override;
    virtual void handleMessage(cMessage *msg) override;
};

Define_Module(Txc3);

void Txc3::initialize()
{
    // 初始化计数器
    counter = 10;
    WATCH(counter);
    //watch可以用来在qtenv下可视化检查变量

    if (strcmp("tic", getName()) == 0) {
        EV << "Sending initial message\n";
        //用EV输出日志信息
        cMessage *msg = new cMessage("tictocMsg");
        send(msg, "out");
    }
}

void Txc3::handleMessage(cMessage *msg)
{
    //计数器-1
    counter--;
    if (counter == 0) {
        EV << getName() << "'s counter reached zero, deleting message\n";
        //getName是当前实体的名字,例如toc
        delete msg;//删除消息,模拟将会停止,提示没有更多事件
    }
    else {
        EV << getName() << "'s counter is " << counter << ", sending back message\n";
        send(msg, "out");
    }
}

运行效果如下图,当消息被删除后,会弹出No more events的提示,日志窗口中也会出现由EV语句输出的INFO信息,提示tic或toc中的计数器当前值。

tictoc4:添加参数、通配符

本实验添加一个输入参数(limit),控制消息交换的次数;并添加一个布尔类型的参数,决定该模块在初始化代码中是否发送第一条消息。

tictoc4.ned如下所示,在simple中声明了参数及其默认值

simple Txc4
{
    parameters:
        bool sendMsgOnInit = default(false); //指定初始化时是否发送消息
        int limit = default(2);   // limit参数,默认为2
        @display("i=block/routing");
    gates:
        input in;
        output out;
}

network Tictoc4
{
    submodules:
        tic: Txc4 {
            parameters:
                sendMsgOnInit = true;//由tic来发送第一条消息
                @display("i=,cyan");
        }
        toc: Txc4 {
            parameters:
                sendMsgOnInit = false;
                @display("i=,gold");
        }
    connections:
        tic.out --> {  delay = 100ms; } --> toc.in;
        tic.in <-- {  delay = 100ms; } <-- toc.out;
}

在C++代码中,读取参数并赋值给计数器

class Txc4 : public cSimpleModule
{
  private:
    int counter;

  protected:
    virtual void initialize() override;
    virtual void handleMessage(cMessage *msg) override;
};

Define_Module(Txc4);

void Txc4::initialize()
{
    counter = par("limit");//用limit参数初始化计数器

    // 不再使用模块名决定初始化时是否发送初始消息
    if (par("sendMsgOnInit").boolValue() == true) {//参数值为真,说明是tic
        EV << "Sending initial message\n";
        cMessage *msg = new cMessage("tictocMsg");
        send(msg, "out");
    }
}

在ned文件或omnetpp.ini中都可以配置参数,但ned文件中的配置优先。如果ned使用了default,可以在ini中设置参数的值,也可以使用ned文件的默认值。

如果omnetpp.ini如下所示:

network = Tictoc4
Tictoc4.toc.limit = 5

那么toc的limit值为5,tic的limit值为2(是ned文件中设置的默认值),由于tic的计数器值更小,运行过程中执行两次交互就会结束消息传输,如下图所示:

  • 关于通配符

如果将ini文件改为Tictoc4.*.limit=5,那么tic和toc的limit值都会是5

通配符有*和**,区别在于**可以通配.符号,*不可以。例如**.limit=5

tictoc5:使用ned继承

simple Tic5 extends Txc5
{
    parameters:
        @display("i=,cyan");
        sendMsgOnInit = true;   //tic模块发送初始消息
}

一旦我们创建了新的简单模块,我们就可以将它们用作网络中的子模块类型,可以让网络定义变得更短更简单:

network Tictoc5
{
    submodules:
        tic: Tic5;  //limit参数这里没有指定,可以从ini文件获取
        toc: Toc5;
    connections:

tictoc6:模拟处理时延

在之前的实验中,tic和toc在接收到消息之后直接发回,本实验模拟tic和toc在发送回信息之前,持有消息1秒。这种时延是通过模块向自己发送一个消息实现的,这种消息被称为自消息。自消息用scheduleAt()函数发送,可以指定自消息何时返回该模块。

ned文件内容不变,cc文件如下

#include <stdio.h>
#include <string.h>
#include <omnetpp.h>

using namespace omnetpp;

class Txc6 : public cSimpleModule
{
  private:
    // 将指针设置为nullptr,这样即使启动过程中initialize()没有被调用,析构函数也不会崩溃
    cMessage *event = nullptr;  // 指向一个事件对象,该事件对象用于延时
    cMessage *tictocMsg = nullptr;  // 记录消息的变量,直到消息发回

  public:
    virtual ~Txc6();

  protected:
    virtual void initialize() override;
    virtual void handleMessage(cMessage *msg) override;
};

Define_Module(Txc6);

Txc6::~Txc6()
{
    // 处理动态分配的对象
    cancelAndDelete(event);
    delete tictocMsg;
}

void Txc6::initialize()
{
    // Create the event object we'll use for timing -- just any ordinary message.
    event = new cMessage("event");

    // No tictoc message yet.
    tictocMsg = nullptr;

    if (strcmp("tic", getName()) == 0) {
        //并不马上开始,而是发送一个消息给自己,当自消息到达(5.0s)时,才发送第一条消息
        EV << "Scheduling first send to t=5.0s\n";
        tictocMsg = new cMessage("tictocMsg");
        scheduleAt(5.0, event);//指定在5.0s时,自消息event返回tic
    }
}

void Txc6::handleMessage(cMessage *msg)
{
    //有几种区分信息的方法,例如消息类型(cMessage的一个整型属性),代码中是直接识别指针
    if (msg == event) {//是自消息到达
        EV << "Wait period is over, sending back message\n";
        send(tictocMsg, "out");//向对方发送消息
        tictocMsg = nullptr;//让消息指向空
    }
    else {//是对方发来的tictoc消息
        EV << "Message arrived, starting to wait 1 sec...\n";
        tictocMsg = msg;//将tictoc消息的指针存到变量中
        scheduleAt(simTime()+1.0, event);//指定自消息在1s后返回
    }
}

if (msg->isSelfMessage())也等同于if (msg == event)的判断。模拟处理时延的效果如下所示,从模拟时间的变化可以观察到:

tictoc7:引入随机数、概率丢包

将处理时延从1s改为一个随机值,这个随机值可以在ned文件中设置,也可以在omnetpp.ini文件中设置。在.ned文件中增加一个参数volatile double delayTime @unit(s);指定等待的时间,单位是秒。注意omnet++中生成的随机数是确定性的,即重复多次运行,产生的随机数相同。

在txc7.cc文件中:

if (uniform(0, 1) < 0.1) {//以0.1的概率丢弃这个消息
    EV << "\"Losing\" message\n";
    delete msg;
}
else {
    //delayTime参数可以在ned或ini文件中设置,在这里就可以每次获得不同的值
    simtime_t delay = par("delayTime");//变量类型是simtime_t

    EV << "Message arrived, starting to wait " << delay << " secs...\n";
    tictocMsg = msg;
    scheduleAt(simTime()+delay, event);
}

本实验在ini文件中设置随机数的范围

Tictoc7.tic.delayTime = exponential(3s)//exponential()的参数是平均值
Tictoc7.toc.delayTime = truncnormal(3s,1s)//truncnormal()返回正态分布,只保留非负值

产生的随机时延效果如下:

tictoc8:超时、取消计时器

本实验模拟停等协议,其中toc会以某个非零的概率丢失消息,在这种情况下,tic需要重新发送消息。

ps:如果不容易观察到丢弃消息的效果,可以提高丢包概率

#include <stdio.h>
#include <string.h>
#include <omnetpp.h>
using namespace omnetpp;
 //如果数据包在一段时间内不能到达,就假设丢包并创建一个新数据包
class Tic8 : public cSimpleModule
{
  private:
    simtime_t timeout;  //指定超时时间
    cMessage *timeoutEvent = nullptr;  //指向超时自消息的指针

  public:
    virtual ~Tic8();

  protected:
    virtual void initialize() override;
    virtual void handleMessage(cMessage *msg) override;
};

Define_Module(Tic8);

Tic8::~Tic8()
{
    cancelAndDelete(timeoutEvent);
}

void Tic8::initialize()
{
    timeout = 1.0;//超时时间设为1s
    timeoutEvent = new cMessage("timeoutEvent");

    EV << "Sending initial message\n";
    cMessage *msg = new cMessage("tictocMsg");
    send(msg, "out");//发送初始消息
    scheduleAt(simTime()+timeout, timeoutEvent);//自消息将在超时时间之后到达
}

void Tic8::handleMessage(cMessage *msg)
{
    if (msg == timeoutEvent) {
        // 收到超时的自消息
        EV << "Timeout expired, resending message and restarting timer\n";
        cMessage *newMsg = new cMessage("tictocMsg");
        send(newMsg, "out");//创建一个新消息并发送
        scheduleAt(simTime()+timeout, timeoutEvent);//进入下一个等待超时
    }
    else {
    	//收到消息,也就是收到确认
        EV << "Timer cancelled.\n";
        cancelEvent(timeoutEvent);//取消超时事件
        delete msg;//删除收到的消息

        cMessage *newMsg = new cMessage("tictocMsg");
        send(newMsg, "out");//创建一个新的消息并发送
        scheduleAt(simTime()+timeout, timeoutEvent);//进入下一个等待超时
    }
}

class Toc8 : public cSimpleModule
{
  protected:
    virtual void handleMessage(cMessage *msg) override;//只决定是否向tic发送一个确认
};

Define_Module(Toc8);

void Toc8::handleMessage(cMessage *msg)
{
    if (uniform(0, 1) < 0.1) {//可以修改以提高丢包概率
        EV << "\"Losing\" message.\n";
        bubble("message lost");  // 会在可视化界面提示丢弃信息
        delete msg;
    }
    else {
        EV << "Sending back same message as acknowledgement.\n";
        send(msg, "out");
    }
}

toc丢弃消息的动画效果如图所示:

tictoc9:重传相同的信息

在tictoc8中,如果需要重传,只创建了另一个数据包。数据包没有包含太多内容时是可以的,但实际中通常更实用的方法是保留原始数据包的副本,可以重新发送而不用重新构建。

保留已发送消息的指针是简单的,但消息在另一个节点上被破坏时,指针就会失效。因此,本实验保留原始数据包,只发送其副本,当toc确认消息之后,才删除原始数据包。此外,在消息名中会包含消息序列号,更容易直观验证模型。

class Tic9 : public cSimpleModule
{
  private:
    simtime_t timeout;  //超时时间
    cMessage *timeoutEvent = nullptr;  //超时自消息的指针
    int seq;  //消息的序列号
    cMessage *message = nullptr;  //需要被重新发送的消息

  public:
    virtual ~Tic9();

  protected:
    virtual cMessage *generateNewMessage();
    virtual void sendCopyOf(cMessage *msg);
    virtual void initialize() override;
    virtual void handleMessage(cMessage *msg) override;
};

Define_Module(Tic9);

Tic9::~Tic9()
{
    cancelAndDelete(timeoutEvent);
    delete message;
}

void Tic9::initialize()
{
    seq = 0;//初始序列号为0
    timeout = 1.0;
    timeoutEvent = new cMessage("timeoutEvent");

    EV << "Sending initial message\n";
    message = generateNewMessage();
    sendCopyOf(message);//发送初始消息
    scheduleAt(simTime()+timeout, timeoutEvent);//进入超时等待
}

void Tic9::handleMessage(cMessage *msg)
{
    if (msg == timeoutEvent) {
        // 收到超时自消息,需要重新发送
        EV << "Timeout expired, resending message and restarting timer\n";
        sendCopyOf(message);
        scheduleAt(simTime()+timeout, timeoutEvent);
    }
    else {//收到确认
        EV << "Received: " << msg->getName() << "\n";
        delete msg;
        
        EV << "Timer cancelled.\n";
        cancelEvent(timeoutEvent);//取消超时事件
        delete message;//删除存储的消息

        message = generateNewMessage();//准备发送下一个消息
        sendCopyOf(message);
        scheduleAt(simTime()+timeout, timeoutEvent);
    }
}

cMessage *Tic9::generateNewMessage()//函数返回一个消息指针
{
    // 每次生成名称不同的消息
    char msgname[20];
    sprintf(msgname, "tic-%d", ++seq);//消息名中序列号不同
    cMessage *msg = new cMessage(msgname);
    return msg;
}

void Tic9::sendCopyOf(cMessage *msg)
{
    // 复制消息,并发送备份
    cMessage *copy = (cMessage *)msg->dup();//复制
    send(copy, "out");
}

消息名的变化如下图所示:

posted @ 2024-06-20 08:44  瑞图恩灵  阅读(46)  评论(0编辑  收藏  举报