用omnetpp仿真dsr协议

xiaoxiao2021-02-28  125

本人也是第一次用omnetpp平台,模拟网络dsr协议,写这篇文章希望对实验代码做一些梳理和剖析。仿真网络的基础是构建网络,将节点,基站,消息,dsr网络定义好,并在初始化文件中将节点基站数量和坐标,网络半径和交流范围设置好。使用ned语言,一个特点就是模块化,便于组装和使用。初始化是写在ini文件中,节点个数过多,最好自己写个随机数生成函数自动生成节点的坐标。

节点代码如下:

1 2 3 4 5 6 7 8 9 10 11 12 simple Node{ parameters: double  tranRange;  //交流范围   double  x; double  y; @display ( "p=$x,$y;i=misc/node_vs,black" );  //坐标,图标   gates: input in[];  //端口 output out; }

  

 这便是一个完整的节点模块,类型为simple,包含参数,门,模块和模块之间通过门进行连接。input,output可以看做是节点的端口。sink的定义代码是类似的。

DSR网络代码如下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 network Dsr     //网络 {      parameters:                           double  R;          int  node_num;                       submodules:                         node[node_num] : Node{                   }          sink : Sink{                     }                  connections allowunconnected:   //允许门不连接   }

  这是由节点和基站组成的复合模块,网络必不可少的基础设施就是节点和基站了,参数中定义了网络半径和节点数量。

在omnetpp.ini文件中将各参数初始化,运行时编译程序首先会在定义处找初始值,如果没找到则会到ini文件中查找。  这样一个基本的网络就建好了。但这网络中没有操作,没有消息,这些就是接下来需要完成的工作。在omnetpp中,定义是写在ned文件中,但具体操作是写在源文件中,为了方便使用和查看,将源文件中参数和函数声明都写在头文件中。

节点需要些什么操作呢?

节点会跟其他节点交互,需要收发消息,需要计算距离,需要根据dsr协议检查缓存,判断有无有效路径,判断收到的消息是否重复,是否是发给自己的,消息是否完整等等。但在这里我们就实现最基本的功能就好。

头文件的定义如下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 class  Node :  public  omnetpp::cSimpleModule{   private :        double  R;        double  x;      double  y;      double  tranRange;      int  node_num;        struct stateInfo              //存储网络拓扑信息      {          Node* endNode;              //本节点的出边邻居          double  r;                   //保存链路权值,即距离,在DSR中并未使用      } link[NODESUM];        struct nodestate                //记录当前已知的路由请求包的发送者和其发送的最大包ID      {          int  sNode;                  //源节点          int  reqID;                  //路由请求包的序号          struct nodestate *next;      };      typedef struct nodestate nst;      typedef nst *nlink;      nlink head;        cModule* sink;        cMessage* transTopoMessage;      cMessage* transRoutMessage;        MycMessage* data;      MycMessage* reqpacket;        static  vector<Node*> nodev;      //static vector<Node*> nodev;        void  transmitData(MycMessage* msg);                //发送数据 cMessage是所有消息的基类      void  buildiniTc();                                 //      void  setrouteReqInfo(MycMessage* msg);             //      void  broadcastReq(MycMessage* msg);                //      bool checkisOldreq(MycMessage* msg);               //      void  addrouteAddrInfo(MycMessage* msg);            //        double  distance_sq_to_node(Node* nod);      //计算此节点到nod节点的距离      double  distance_sq_to_sink();                //计算和sink距离      double  node_distance_to_sink(Node* node);     //nod和sink距离   public :      Node();   //构造函数  私有成员的指针赋初值      ~Node();   //析构函数  delete指针 防止内存泄漏      int  getx();      int  gety();   protected :      virtual  void  initialize();                     //初始化      virtual  void  handleMessage(cMessage* msg);     //处理消息      virtual  void  finish();                //结束   };  

  首先继承cSimpleModule模块,最后的三个函数就是继承而来的,initialize()将网络初始化。

网络拓扑结构就是网络如何连接的,具体到节点的层面,就是指节点跟周围的节点有无连接,跟周围那些节点有连接,使用链表来存储网络拓扑,需要存储的信息有跟本节点相连接的邻居,和距离,即权值。链表结构如下:使用链表来存储来自其他节点的路由请求包的路径记录。       

1 2 3 4 5 6 struct stateInfo  //存储网络拓扑信息       {         Node* endNode;              //本节点的出边邻居         double  r;                   //保存链路权值,即距离,在DSR中并未使用     } link[NODESUM];

initialize()函数实现如下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 void  Node::initialize() {      cModule* parent =  this ->getParentModule();   //取到父模块      x = par( "x" ); //赋初值,从ini文件中        y = par( "y" );      tranRange = par( "tranRange" );      node_num = parent->par( "node_num" );      R = parent->par( "R" );        for ( int  i= 0 ; i<node_num; i++){                                //初始网络拓扑的存储结构,将链表置空          link[i].endNode = NULL;      }        head = NULL;                                                  //初始链表头指针,链表结构用来存储网络中其他节点发送的路由请求包记录        sink = parent->getSubmodule( "sink" );                          //基站的指针,父类取子模块取sink        this ->setGateSize( "in" , this ->node_num+ 10 );                     //多给10个位置,怕指针出错 in门,门数为节点数+10        this ->transTopoMessage =  new  cMessage( "tranTopo" );            //生成一个自消息,用于触发网络拓扑的构建      this ->transTopoMessage->setKind(TOPO_EVENT);                  //设置消息类型,便于函数handleMessage()进行识别并处理        this ->transRoutMessage =  new  cMessage( "RoutTopo" );            //生成一个自消息,用于触发一次路由发现过程      this ->transRoutMessage->setKind( );        scheduleAt(simimTime()+uniform( 0 ,TOPO_TIME),transTopoMessage);  //调度自消息何时出现,设置自消息transTopoMessage在随后的某个时刻发生      scheduleAt(sTime()+uniform(TOPO_TIME+ 50 ,ROUT_TIME),transRoutMessage);        nodev.push_back( this );   //每个对象初始化的存入其中 } 

  可以看出主要的操作是将节点模块中的参数赋初值,并初始化网络拓扑结构,即将链表置空。从父类中取子模块sink,得到基站的指针,设置in门的数量,可以接受来自所有节点的消息,并在节点数上加10。为了构建网络拓扑,触发路由发现过程,将给自己发的消息设置为两个类型,TOPO_EVENT,RoutTopo,作用体现在handleMessage对不同消息的处理过程中,实现如下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 if ( msg->isSelfMessage() )      {          switch (msg->getKind())          {          case  TOPO_EVENT:               this ->buildiniTc();                                      //构建网络拓扑               break ;          case  ROUT_EVENT:               reqpacket =  new  MycMessage( "reqpacket" );                 //构建新包               reqpacket->setKind(REQ_MSG);                             //设置新包的类型               if ( this ->getIndex()== 90 ){                                //为节省仿真时间,只指定一个节点发送路由请求                   this ->getDisplayString().setTagArg( "i" , 1 , "red" );                   this ->setrouteReqInfo(reqpacket);                     //将路由请求设置到包reqpacket中                   this ->broadcastReq(reqpacket);                        //广播到邻居               }               delete reqpacket;               break ;          default :              error( "Invalid event:%s" ,msg->getName());          }  

  TOPO_EVENT触发buildiniTc()函数,构建网络拓扑结构。ROUT_EVENT用于路由发现过程,要发现路由,可能需要数据包,所以首先构建新包,并指定新包类型,并将其标记,设置路由请求,并广播到邻居。

buildiniTC()实现如下:nodev是节点类型的容器,每个对象 的初始化都存入其中:vector<Node*> Node::nodev;  nodev.push_back(this); 对于接到TOPO_EVENT消息的节点,循环判断其他节点,是否是自己以及距离是否在交换范围内,即对自己交换范围之内的每一个节点,将其与自己所在链表连接。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 void  Node::buildiniTc() {      int  i,j;      for (i= 0 ; i<nodev.size(); i++){          if (nodev[i] !=  this  &&  this ->distance_sq_to_node(nodev[i]) <=  this ->tranRange){              for (j= 0 ; j<node_num; j++){                  if ( this ->link[j].endNode == NULL){                      this ->link[j].endNode = nodev[i];                      break ;                  }              }          }      } } 

  两种类型的自消息都是有scheduleAt函数调度发送的。

节点的handleMessage()函数上面讲了处理自消息的部分,下面将处理发送给其他节点的部分,代码如下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 else {          switch ( msg->getKind() )          {          case  REQ_MSG:              reqpacket = check_and_cast<MycMessage *>(msg);        //凡是使用自己定义的消息格式,该语句是必须要的              if (! this ->checkisOldreq(reqpacket)){                  //检查reqpacket是否是以前处理过的                  this ->addrouteAddrInfo(reqpacket);                //将自己的地址加到包reqpacket中                  this ->broadcastReq(reqpacket);              }              delete reqpacket;                                     //删除余下的副本              break ;          case  REP_MSG:              //data = new MycMessage("data");                     //在这里应该构建一个新的数据包,并将响应包中路由置于该包头中,再发送              data = check_and_cast<MycMessage *>(msg);             //为简单,这里不再构建新包,而将刚收到的响应包当做新包使用              data->setKind(DATA_MSG);                              //只要修改一下包的类型,就可以当一个新包使用了              this ->getDisplayString().setTagArg( "i" , 1 , "white" );              this ->transmitData(data);              break ;          case  DATA_MSG:              data = check_and_cast<MycMessage *>(msg);              this ->getDisplayString().setTagArg( "i" , 1 , "green" );              this ->transmitData(data);                              //将收到的数据包按包头的路由指示进行转发              delete data;                                           //删除余下的副本              break ;          default :              error( "Invalid message:%s" ,msg->getName());          }      }

  

  这里就体现了dsr协议的内容。如果是REQ_MSG类型的包,自己不是目的节点,首先检查此消息是否是以前接受到过的,如果是则删除副本,将包丢弃。如果未收到过,则将自己的地址加入reqpacket,并再次广播,这里使用的就是洪泛算法,这也是dsr的一个缺点。如果是REP_MSG的消息,自己就是目的节点,返回应答数据包即可。transmitData(data),这是转发数据的函数。

initial(),handlemessage()这两个函数是节点最基本最主要的操作,此头文件中其它函数除了计算距离的以外,都是为了实现这两个函数需要的功能而写的。比如: transmitData(MycMessage* msg); buildiniTc(); setrouteReqInfo(MycMessage* msg);  broadcastReq(MycMessage* msg); checkisOldreq(MycMessage* msg); addrouteAddrInfo(MycMessage* msg); 根据名字便可看出函数实现的功能,具体实现这里就不多说了。

接下来看基站的操作实现。

声明:class Sink:public cSimpleModule  可知它同节点一样,继承了三个函数:

void virtual initialize();

void virtual handleMessage(cMessage* msg);  //继承而来的方法,所有的消息都会触发,判定那种消息

void virtual finish();

初始化操作:

cModule* parent = this->getParentModule();

this->setGateSize("in",parent->par("node_num"));

得到父模块的指针,并设置in门的大小,因为基站只接受来自其他节点的消息,所以只需要节点中handleMessage()函数判断为其他消息的方法就可以了,即消息类型只有REQ_MSG,DATA_MSG两种。当然转发包的函数也必须的,即transmitReppacket(MycMessage* msg)。

最后还需要完善消息的操作,头文件内容为:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 class  MycMessage :  public  ::omnetpp::cMessage {    protected :      int  addrS;      int  reqID;      int  path[ 100 ];      private :      void  copy( const  MycMessage& other);      protected :      // protected and unimplemented operator==(), to prevent accidental usage      bool operator==( const  MycMessage&);      public :      MycMessage( const  char  *name=nullptr,  int  kind= 0 );      MycMessage( const  MycMessage& other);      virtual ~MycMessage();      MycMessage& operator=( const  MycMessage& other);      virtual MycMessage *dup()  const  { return  new  MycMessage(* this );}      virtual  void  parsimPack(omnetpp::cCommBuffer *b)  const ;      virtual  void  parsimUnpack(omnetpp::cCommBuffer *b);        // field getter/setter methods      virtual  int  getAddrS()  const ;      virtual  void  setAddrS( int  addrS);      virtual  int  getReqID()  const ;      virtual  void  setReqID( int  reqID);      virtual unsigned  int  getPathArraySize()  const ;      virtual  int  getPath(unsigned  int  k)  const ;      virtual  void  setPath(unsigned  int  k,  int  path); };   inline  void  doParsimPacking(omnetpp::cCommBuffer *b,  const  MycMessage& obj) {obj.parsimPack(b);} inline  void  doParsimUnpacking(omnetpp::cCommBuffer *b, MycMessage& obj) {obj.parsimUnpack(b);}     #endif  // ifndef __MYCMESSAGE_M_H

  大多函数根据名字便知晓功能,这里就不详细描述了。最后两个函数是omnetpp中Simulation Core下cPacket中的,具体实现为:

1 2 3 4 5 6 7 void  MycMessage::parsimPack(omnetpp::cCommBuffer *b)  const {      ::omnetpp::cMessage::parsimPack(b);      doParsimPacking(b, this ->addrS);      doParsimPacking(b, this ->reqID);      doParsimArrayPacking(b, this ->path, 100 ); } 

  功能是Serializes the object into an MPI send buffer Used by the simulation kernel for parallel execution.MPI:Message Passing Interface is a standardized and portable message-passing(message passing sends a message to a process (which may be an actor or object) and relies on the process and the supporting infrastructure to select and invoke the actual code to run. system .

至此,网络能根据dsr协议运作。

2016.5.25

长沙 无线网络作业

转载请注明原文地址: https://www.6miu.com/read-37517.html

最新回复(0)