本人也是第一次用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
长沙 无线网络作业
