用omnetpp仿真dsr协议

本人也是第一次用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 nodev;
     //static vector 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 //初始网络拓扑的存储结构,将链表置空
         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::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
         if (nodev[i] !=  this  &&  this ->distance_sq_to_node(nodev[i]) <=  this ->tranRange){
             for (j= 0 ; 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(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(msg);             //为简单,这里不再构建新包,而将刚收到的响应包当做新包使用
             data->setKind(DATA_MSG);                              //只要修改一下包的类型,就可以当一个新包使用了
             this ->getDisplayString().setTagArg( "i" , 1 , "white" );
             this ->transmitData(data);
             break ;
         case  DATA_MSG:
             data = check_and_cast(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

长沙 无线网络作业

你可能感兴趣的:(网络)