本人也是第一次用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
//static vector
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
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
//凡是使用自己定义的消息格式,该语句是必须要的
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
//为简单,这里不再构建新包,而将刚收到的响应包当做新包使用
data->setKind(DATA_MSG);
//只要修改一下包的类型,就可以当一个新包使用了
this
->getDisplayString().setTagArg(
"i"
,
1
,
"white"
);
this
->transmitData(data);
break
;
case
DATA_MSG:
data = check_and_cast
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
长沙 无线网络作业