当前位置:文档之家› 局域网仿真

局域网仿真

第一章、背景

1.1 局域网仿真

局域网仿真是局域网设计的重要环节,也是网络性能分析的关键。

计算机局域网设计、模拟和性能分析系统的研究是一个非常有意义的课题。它使设计者在设计阶段就能对所设计的网络的性能作出准确的预测,合理修改其设计,选择符合要求的性价比较优的方案。进行计算机仿真,利用仿真中间参数分析系统性能是分析某一计算机网络系统的性能的常用方法。采用系统仿真的方法,能对各组件的行为进行较精确的模拟,获得足够数据来对系统的性能进行较准确的预测。

在构造一个局域网之前,需要设计此网络的拓扑图,各层协议等等。要达到最优的效果,需要对图的结构等等进行不断的修改。如果用手工计算、调整,则不仅耗功耗时,还可能留下不可预计的漏洞。而要直接进行操作系统上的编程工作,则不断的修改、调试让人无法忍受,更重要的是由于实际机器的影响,很多工作都无法进行。

类似的情况发生在我们研究网络算法时,如果直接在各种操作系统上编程、测试,便无可避免与操作系统内核打交道。这需要特别熟悉操作系统的内核,操作系统内核的复杂性大大增加了编程的难度,这使我们将更多的精力投入对操作系统的编程而不是对算法的研究。

1.2 仿真器

基于以上种种考虑,我们需要一个与操作系统无关的平台,它可以屏蔽掉对操作系统的实际的访问,且能近乎真实地模拟网络环境,让我们可以在各个层次上模拟网络的运行。

事实上,已有许多研究机构对此作出努力,并开发出自己的网络仿真器。比如,Columbia University的NEST,作者为Alex Dupuy。University of California的S.Keshav在NEST 的基础上,进行修改,就形成后来的REAL网络仿真器。之后,Lawrece Berkeley National Laboratory 实验室的Network Research Group 工作组对REAL进行进一步的修改,终于得到LNBL Network Simulator,即ns的第一版。UC Berkeley的MASH研究组对LNBL继续改进,将它的版本提高到2.1b7a,这是一个很优秀的软件。用一句话概括这个软件的功能就是:它可以在一台计算机上动态仿真一个网络的运行。

本文的任务就是尽可能介绍UC Berkeley改进过的NS2,介绍其功能、使用方法,与其一起工作的工具,分析其源代码,并介绍对它进行简单扩展的方法。

第二章、运行环境及语言支持

2.1 运行环境

NS工具包有许多模块组成,不同的组合可以在不同的操作系统上运行。如:Windows 系列,Linux、Unix、Macintosh等等。为了运行ns,还要求系统装有C++编译器。

整合的Ns-allinone2.1b7a包含了比较完整的工具包,但是只能在Linux下运行。

2.2 语言支持

值得注意的是NS的主代码由两种语言写成:C++和OTcl。

因为仿真器有两方面的需要:

1)一方面,对协议细节的仿真需要一种比较系统的编程语言,它能高效地对字节、数据报头进行操作,能高效实现运行在大量数据集上的算法。对于这些任务,运行时间(run time)是我们最关心的,要尽可能少,而转换时间(turn-around time,含运行,调试)可以长一些。

2)另一方面,对网络研究会涉及到对网络实体的参数或配置的修改,或是对网络事件的修改。在这些情况下,反复时间(iteration time,即变换模型,重运行)更重要。由于配置仅运行一次(在仿真刚开始时),因此这一部分任务的运行时间可不用考虑。

C++与Otcl解决了这两种需要。C++程序的运行时间很短,但转换时间很长,正适合用于实现具体的协议。Otcl运行得很慢,但可以很快的转换(或是交互),用来进行仿真的配置最适合不过。然后,通过tclcl模块将出现在两种语言里的变量、对象胶合起来。

当然以上的分法并不是绝对的,例如:许多路由是由Otcl实现的,但其核心——Dijkstra 算法是用C++来实现)。通常,如果需要在一秒钟内调用许多次的模块,最好还是用C++代码实现。

第三章、NS各模块及功能

这章着重介绍:

●NS内各大模块及功能,

●结合模块介绍整个工具包的工作机制

●NS内含的几个单独的小工具包

3.1 模块概述

Ns-allinone2.1b7a中含有12个模块:

模块版本号必选/可选

1 Tcl Tcl release 8.3.2必选

2 Tk Tk release 8.3.2必选

3 Otcl otcl release 1.0a6必选

4 TclCL tclcl release 1.0b10必选

5 Ns ns release 2.1b7必选

6 TclDebug tcl-debug relase 1.9可选

7 Nam Nam release 1.0a9可选

8 Xgraph xgraph version 12可选

可选

9 GT-ITM Georgia Tech Internetwork Topology

Modeler

10 SGB Stanford GraphBase package可选

11 CWEB CWeb version 1.0 (?)可选

12 Zlib zlib version 1.1.3可选

上表中各大模块说明如下:

1、Tcl:Tcl提供了一个强有力的平台,可以生成面向多种平台的应用程序,协议,

驱动程序等等。它与Tk(toolkit)协作,可生成GUI应用程序,可在PC、Unix和Macintosh 上运行。Tcl 还可用来完成与网页相关的任务,或是为应用程序提供强有力的命令语言。

2、Tk:与Tcl协调工作的图形工具包。

3、OTcl:MIT Object Tcl的简称,是Tcl/Tk 面向对象编程的扩展。

4、Tclcl:此目录下含tcl/C++的接口,vic、vat、ns、rtp_play、和nam都会用到。

5、ns:NS的主体代码,内含一个节点移动产生器、两个传输事件产生器。

6、TclDebug:Tcl调试工具包。

7、Nam:即UCB/LBNL Network AniMator,它与NS协同工作,将NS仿真过程动态

表现出来。

8、Xgraph:Xgraph是一X-Windows应用程序,包含:

?交互式测量和绘制;

?动画效果

9、Gt-itm:GT Internetwork Topology Models的简称,产生模拟internt网络结构的拓

扑图,还提供了一些例子。

10、SGB:Standford GraphBase的简称,图形产生器。

11、Cweb:与网页相关的工具。

12、zlib:通用数据数据压缩库(data compression library)。

对于NS来说Tcl,Tk,Otcl,TclDebug,Nam,Xgraph,Gt-itm,SGB,Cweb,zlib 等均为现成工具包,本文不在细述其工作机制,而将焦点集中在ns及Tclcl目录下的代码,研究NS的仿真机制。

附录里会介绍Nam的一些功能、使用方法。

3.2 功能示意图:

在下图中,粗框里为NS的模块,方框外的script为我们写的脚本文件。NS解释脚本,将输出写到输出文件中,然后调用Nam或Xgraph显示输出文件。

3.3 NS的小工具

?Nam:图形显示工具,动态显示网络的动态运行。

?Xgraph:图形显示工具,显示网络运行的数值特征。

?Gt-itm:internet拓扑图生成器。

?

?Setdest:节点移动生成器。

?cbrgen.tcl,tcpgen.tcl:传输事件生成器。

它们的详细使用方法在下一章给出。

第四章、NS的使用

这一章介绍NS的运行方法和几个小工具的使用,同时会介绍脚本和输出文件等。

4.1 NS的运行

可以将整个NS2看成一个解释器。有两种方法可以对网络事件进行仿真:

a)命令行方式,即敲入一个命令,返回一个结果。如:

输入命令:ns-version

返回版本号: 2.0a12

b)脚本方式,指定一个脚本文件(*.tcl文件),让NS执行。如:

ns

因为脚本文件可以对网络结构、事件的作细致的描述,所以通常我们使用b)。4.2 脚本的编写

脚本用Otcl语言编写。基本流程图如下:

4.3 输出文件

NS允许我们在脚本中定义一个输出文件(又叫跟踪文件),记录仿真过程,文件名为“*.out”或“*.tr”,分别采用以下两种图形方式显示:

a)nam方式:NS工具包中有一个nam模块,编译后,产生nam.exe文件。语句

%nam

显示仿真的整个过程,比如说,数据包的传输,链路断开,节点的移动,丢包等等。

b)Xgraph方式:与nam相似,NS工具包中还有一个Xgraph模块,产生Xgraph.exe,

语句如下:

%xgraph “filename.tr”

不过,“*.tr”文件一般记录的是,仿真过程中的内部状态,比如,传输数率、各种峰值等等。

当然以上的语句可以显式给出,但一般可以写入脚本的退出过程中。这样在仿真任务结束之后,可以得到比较直观的结果。

4.4 节点移动产生器和传输产生器

通常比较大的拓扑图,经常把节点的移动和传输事件写在一个单独的文件里。这个文件可以由CMU的节点移动产生器和传输产生器来产生。

节点移动产生器

例子:~ns/tcl/mobility/scene/scen-670x670-50-600-20-*。这些文件定义了在拓扑图大小为670 ×670m ,有50个节点,移动速度为20m/s,暂停时间为600s。每一节点定义了初始位置。关于节点间的“跳数”的信息,由对象"GOD"和节点的移动方向速度得出。

产生节点移动文件的产生器在目录~ns/indep-utils/cmu-scen-gen/setdest/下。使用方法:./setdest -n -p -s -t

-x -y > /

注:节点下标从0开始。

传输产生器

例子:~ns/tcl/mobility/scene/cbr-50-{10-4-512, 20-4-512}.

传输产生器在目录~ns/indep-utils/cmu-scen-gen/ 下,名字为cbrgen.tcl、tcpgen.tcl(无)。分别用来产生CBR和TCP链接。

要想产生CBR链接:

ns cbrgen.tcl [-type cbr|tcp] [-nn nodes] [-seed seed]

[-mc connections] [-rate rate]

要想产生TCP链接:

ns tcpgen.tcl [-nn nodes] [-seed seed]

第五章、NS主代码简析——基类

NS基类有6种:

●Tcl类:C++代码与Tcl代码之间的桥梁;

●TclObject类:所有仿真对象的基类;

●TclClass类:定义了解释类的类层次,并允许用户实例化TclObject,与TclObject

一一对应;

●TclCommand类:封装了C++代码和Tcl代码相互调用命令的方法;

●EmbeddedTcl类:封装了装载更高级别的内置命令的方法;

●InstVar类:访问C++成员变量,如Otcl变量方法。

这一章详细说明每一基类的作用、构造和工作机制

5.1Tcl类

Tcl类最重要的功能有以下六种:

5.1.1获得访问Tcl实例的入口

C++代码将整个Tcl平台视为一个类,首先取到访问此类的入口:

Tcl& tcl = Tcl::instance();

5.1.2通过解释器调用Otcl过程,有四种方法,对应有四个成员函数:

void eval(char *s):通过调用解释器的Tcl_GlobalEval()执行命令*s

void evalc(const char *s):将串*s复制入内部buffer,然后调用[char *s]eval,完成对*s的执行。

void eval:假定命令已存入Tcl.bp_,先通过tcl.buffer()获得Tcp.bp_的指针p,然后调用tcl.eval(char *p),执行命令*p。

void evalf(const char *fmt,…) 带不定个数的参数,第一参数*fmt为输出格式,

举例如下:

Tcl& tcl = Tcl::instanc e();

char wrk[128];

strcpy(wrk, "Simulator set NumberInterfaces_ 1");

tcl.eva l(wrk);

sprintf(tcl.buffe r(), "Agent/SRM set requestFunction_ %s", "Fixed");

tcl.eva l();

tcl.eval c("puts stdout hello world");

tcl.eval f("%s request %d %d", name_, sender, msgid);

5.1.3与解释器交换结果,分两种情况:

解释器调用C++方法,期望将结果返回并写入私有成员变量tcl_->result。有两种方法:

tcl.result(const char *s):将结果串*s写入tcl_->result。

tcl.resultf(const char* fmt, . . . ):按格式*fmt,将结果写入tcl_->result

看个例子:

if (strcmp(argv[1], "now") == 0) {

tcl.result f("%.17g", clock());

return TCL_OK;

}

tcl.resul t("Invalid operation specified");

return TCL_ERROR;

当C++方法调用Otcl命令时,解释器也将结果返回到tcl_->result,使用的方法是不带参数的tcl.result(void),应该注意到返回的结果是字串,所以,还要将它转变成相应的数据类型。例如:

tcl.evalc("Simulator set NumberInterfaces_");

char* ni = tcl.resul t();

if (atoi(ni) != 1)

tcl.evalc("Simulator set NumberInterfaces_ 1");

5.1.4报告出错状况,并以统一方式退出:

相应的成员函数是:tcl.erro r(const char*s),此函数将*s、tcl->result写入stdout,退出并置error code为1。

tcl.result f("cmd = %s", cmd);

tcl.erro r("invalid command specified");

注:调用Tcl::error(),不同于函数tcl.result()中返回TCL_ERROR。后者者在解释器内部产生一个异常,用户可以跟踪此异常,并有可能从错误中恢复,若用户没有指定任何跟踪,解释器将输出错误信息,并退出。但如果代码调用了error(),则仿真用户不能跟踪错误,

同时,ns不会输出任何错误信息

5.1.5存储、查找TclObject类对象:

ns 将每一个TclObject对象的入口都存储在一个哈希表里,并以TclObject对象名为关键字,对哈希表进行操作。类Tcl提供了一组方法:

tcl.enter(TclObject*) :将一个指向TclObject对象的指针插入哈希表,通常是在生成一个新的对象时,由函数TclClass::create_shadow()调用。

tcl.lookup(char*):参数为TclObject对象名,由TclObject::lookup()调用。

tcl.remove(TclObject*):当需要释放一TclObject对象时TclClass::delete_shadow()调用此函数。

注:以上三个函数只在类TclObject或类TclClass内部使用

5.1.6取解释器的句柄:

若以上的方法不够用,我们只好直接取到解释器的句柄,再自己写代码,获得解释器句柄的方法是:

tcl.interp(void)

5.2TclObject类:

类TclObject是ns中绝大多数类的基类,封装了绑定、跟踪和对相关命令的调用机制。5.2.1生成和释放:ne w{}和delet e{}。

生成(create)

ne w操作返回类TclObject的解释对象。解释器调用对象的构造函数,初始函数ini t{},还有相应的编译类的构造函数以生成相应的编译对象。最后,ne w{}返回对象的句柄。

详细过程如下:

●New{}调用方法name(){},在解释器的名字空间(name space)取一句柄返回给用户。

通常,句柄由getid{}产生,为_o型, 此处是一整数。

●执行新对象的构造函数,该构造函数会调用其父类的构造函数,最后会调用到

TclObject的构造函数。

●TclObject的构造函数为调用实例过程create-shadow(void){},构造编译对象,并

完成一系列的初始化、绑定。

●生成编译对象后,create_shadow(void)还要完成以下工作:

(a) 将新对象加入TclObjects哈希表中。

(b) 为新的解释对象实现cm d{},cm d{}过程将用来调用编译对象的comman d() 完成

想要的操作。

注:以上的过程仅发生在用户通过解释器构造新的对象,而直接用C++代码生成对象不会如此。

流程图如下:

释放(deletion)

Delete操作将释放解释对象和相应的编译对象,可以说是new操作的逆过程。可能的语句是:

delete scheduler_

5.2.2变量的绑定(bindding)

一般而言,编译代码只能访问编译变量,解释代码只能访问解释变量。因此,每一实体都由两个变量来表示,需要系统进行双向绑定。在这里,绑定的意思是,相应的两个变量时刻保持一致,当一方发生改变,另一方也要发生同样的变化。

变量的绑定有显式、隐式两种。隐式变量绑定是在初始化一个对象时,由编译对象的构造函数建立的。之后,解释对象便将它视为实例变量。

5.2.3变量的跟踪(Trace)

跟踪变量(traced variable)由C++ 或Tcl生成、修改。

对解释变量的跟踪

若想用Tcl代码对变量进行跟踪,则此变量必须是Tcl代码可见的,比如:已绑定的C++/Tcl变量,或是纯的Tcl变量。根据被跟踪变量的拥有者不同,可分两种情况:

1)拥有者为自己,例如\$tcp跟踪自己的变量cwnd_:

\$tcp trace cwnd_

2)拥有者为别人,例如,有一普通跟踪者\$tracer跟踪\$tcp的变量

ssthresh_:

set tracer [new Trace/Var]

\$tcp trace ssthresh_ \$tracer

对编译变量的跟踪

被跟踪的变量必须属于TraceVar类的派生类。虚基类TracedVar的部分定义如下:class TracedVar {

virtual char* value(char* buf) = 0;

protected:

TracedVar(const char* name);

const char* name_; // name of the variable

TclObject* owner_; // the object that owns this variable

TclObject* tracer_; // callback when the variable is changed

………………

};

TclCL库对int型和double型变量分别定义了两类TracedVar:TracedInt和TracedDoubl e。它们重载了所有的操作符。当变量的值发生改变时,用assign方法给变量赋新值,并调用tracer 。TracedInt和TracedDouble还实现了value ,将变量的值转为串。

5.2.4 命令方法的定义和调用

ns为每一的TclObject建立一个实例过程cmd{},cmd{}自动调用编译对象的command()函数。用户可以通过两种方法调用cmd{} :显示调用,指定想要的操作为第一参数;或是隐式调用,就好像存在与想要的操作同名的实例过程。绝大多数的脚本使用后一种形式。我们先介绍这种方式,简单起见,以srmObject对象为例子:

SRM中有关距离的计算是由编译对象完成的,而由解释对象使用。调用如下:$srmObject distance? (agentAddress)

若不存在名为distance?实例过程,解释器将调用实例过程unknown{},(在基类TclObject 中定义),然后unknown{}调用:

$srmObject cmd distance? _ agentAddress

通过编译对象的command()过程来执行操作。

当然用户可以显式调用操作,好处是可以重载,对应的Otcl代码和C++代码如下:

Agent/SRM/Adaptive instproc distance? addr {

$self instvar distanceCache_

if ![info exists distanceCache_($addr)] {

set distanceCache_($addr) [$self cmd distance? $addr]

}

set distanceCache_($addr)

}

int ASRMAgent::command(int argc, const char*const*argv) {

Tcl& tcl = Tcl::instance();

if (argc == 3) {

if (strcmp(argv[1], "distance?") == 0) {

int sender = atoi(argv[2]);

SRMinfo* sp = get_state(sender);

tcl.tesultf("%f", sp->distance_);

return TCL_OK;

}

}

return (SRMAgent::command(argc, argv));

}

int ASRMAgent::command()函数有两个参数:

第一个参数(argc)指出命令行中的参数个数。

第二个为命令行参数向量,解释如下:argv[0]——方法名“cmd”;argv[1]——指定想要的操作;若用户还指定了其余参数,在argv[2...(argc - 1)]中。

参数是以字串形式给出的,应该先转化成相应的数据类型。

若操作匹配成功,返回操作结果,tcl.tesultf("%f", sp->distance_);

command() 必须返回TCL_OK或TCL_ERROR ;

如果操作不匹配,调用父类的command()函数,返回相应结果,若一直到最底层的基类都不匹配,返回错误信号。这样做,允许子类继承其父类的命令。

在多继承情况下,可以有两种选择:1、指定继承一个父类的command();2、按某种顺序访问几个父类的command(),一找到匹配的,就不再试下一个,如果都不行,返回一个错误信号。

5.3 TclClass类

类TclClass是一个纯虚类,从它派生的子类需实现两个成员函数:其一是构造函数,构造解释类层次来镜像编译类层次;其二是生成函数,生成与之相对应的TclObjects对象。在~ns/tclcl.h中可以找到定义。

用个简单的例子说明一下整个的流程。比如,脚本中有这样一行:

set o [new B/A ]

则:对象o的解释构造函数在ns 第一次启动时执行

●此构造函数以解释类的名字B/A调用ABClass的构造函数

●然后,ABClass构造函数将调用其父类TclClass的构造函数

●TclClass构造函数存储类的名字,并将对象插入TclClass对象链中

●在simulator的初始化过程中,Tcl_AppInit(void)调用TclClass::bind(void)。

bind()调用以解释类的名为参数register{},

register{}建立类层次,生成需要而还没有的类。

●最后,bind()为新类定义实例过程create-shadow和delete-shadow,生成并返

回对象o。

示意图如下:

5.4 TclCommand类

类TclCommond的作用就在为解释器提供全局命令。

由TclCommand在C++中的定义(~ns/tclcl.h),可以看出,它也是个纯虚类,需要派生类实现实现两个成员函数:构造函数和command()。

比如,如果用户希望敲入命令:%say I am ns user

返回:>hellow, I am ns user。

则我们可以从TclCommand派生出子类MyCommand,它的构造函数用“say”为参数,代码为:

class say_hello : public TclCommand {

public:

say_hello();

int command(int argc, const char*const* argv);

};

class MyClass:TclCommand(”say”){}

然后,在实现MyCommand::command(),即可。

注意,TclCommand定义里有一个dispatch_cmd()的静态函数,实现了从“say”到子类MyCommand的匹配,并调用MyCommand::command()。

流程图如下:

5.5 EmbeddedTcl类

用户对脚本~tclcl/tcl-object.tcl进行修改,或是修改、增加tcl/lib的文件来对ns进行扩展。对于新文件的装载是由类EmbeddedTcl的对象来完成的。

Tcl脚本其实就是由char类型数据组成的文本文件,所以类Embedded的构造函数可以用char*型指针指向脚本代码,并将此指针值赋与成员变量code_。EmbeddedTcl的定义中(~tclcl/tclcl.h)有一load()成员函数。Load()用Tcl::instance()获得解释器句柄,然后调用Tcl::evalc,调用Tcl命令eval,完成对代码的装载:

void EmbeddedTcl::load() {

Tcl::instance().evalc(code_); }

具体的扩展操作在后面的章节介绍。

5.6 InstVar类

类InstVar定义了显式实现绑定机制的方法。

绑定过程bind()通常需要指定解释变量名和编译对象的成员变量的地址。基类InstVar 的构造函数在解释器里创建一个实例变量,建立陷阱例程(trap routine),捕捉所有的通过解释器对变量的访问。当解释器有对变量的读操作发生时,触发陷阱例程,陷阱例程调用适当的get函数,取从对应的编译对象取成员变量的当前值,并给解释变量赋值。对写操作,只是触发时间略有不同,在解释器写完解释变量之后,触发陷阱例程,陷阱例程调用适当的set函数,将改变后的值写回编译对象。

类InstVar有一个成员变量tracedvar_指向类TracedVar对象,TracedVar对象封装了有关编译对象方面的信息和方法,定义如下(~/ns/tclcl/tracedvar.h):

class TracedVar {

public:

TracedVar();

virtual char* value(char* buf, int buflen) = 0;

inline const char* name() { return (name_); } //取变量名

inline void name(const char* name) { name_ = name; } //置变量名

inline TclObject* owner() { return owner_; } //返回owner_

inline void owner(TclObject* o) { owner_ = o; } //置owner_

inline TclObject* tracer() { return tracer_; } //返回tracer_

inline void tracer(TclObject* o) { tracer_ = o; } //置tracer_ protected:

TracedVar(const char* name);

const char* name_; //变量名

TclObject* owner_; //拥有该变量的对象指针

TclObject* tracer_; //当变量改变时回叫tracer_

public:

TracedVar* next_; //指向下一个TracedVar变量,形成队列

};

从基类InstVar又派生了五个子类:class InstVarReal,class InstVarTime,class InstVarBandwidth,class InstVarInt,和c lass InstVarBool。分别用来绑定real型,time型,bandwidth型,integer型,和boolean型变量。

第六章、NS主代码简析——功能模块NS对网络实体的仿真和各种功能模块都封装在派生类中。

首先澄清两个问题:

1、由于NS是由C++代码与Otcl代码组成的,其派生类的构成也因此很复杂,会由相应的解释类与编译类绑定而成。解释类基本上是与编译类是相对应的,这里的对应,指的是名字相似的C++类与Otcl类一般实现对同一实体的仿真。但两者的作用是不一样的,一般说来,C++类实现底层的计算、操作,比如说路由计算、数据包的接收丢弃等等。Otcl 类在高层配置节点、拓扑图,提供用户接口,调用C++代码完成实际工作。值得注意的是,许多复杂的解释类在C++代码里没有相应的编译类,比如说以下提到的类simulator,它由node、link等成员类组成,在C++里就没有相应的类。

2、C++代码中的派生类大多数为TclObject和TclClass派生类。如前述,TclObject的派生类与TclClass的派生类几乎是一一对应的,每一TclClass派生类有一create()函数创建对应的TclObject对象。但两类之间有很多不同。网络中各实体,功能、结构大不一样,且本身就存在许多层次,组合。所以模拟网络实体的TclObject系列,结构都比较复杂,经常由几个相关类又组合成一个新的类,甚至会有多继承现象发生。而TclClass系列则简单得多,几乎所有的子类都不再有派生类,也就是说几乎所有的子类都直接从基类TclClass 派生,只有一个特殊情况,就是它的子类PackHeaderClass派生出许多子类,比如ARPHeaderClass、encapHeaderClass等等,但也就到此为止了。

这章按照仿真模块来介绍比较重要的派生类。

其中simulator类,可以看成是对整个仿真器的封装,含成员类node、link、agent、package、LAN等。Node类主要仿真网络中的节点,它的主要组分是Classifier类。Classifier实现了部分路由功能。Link类仿真链路,它的主要组分是Cnnector、Queue、Delay。对各层协议的仿真由Agent类实现,它与计时器timmer合作。Pakage类模拟数据包,可以实现区别服务模型。由于局域网的特性,NS又实现了LAN类。对于移动节点及无线模型。

6.1仿真的开始:仿真器(class Simulator)

仿真器类是一个解释类,没有相应的编译类。但仿真器类是由许多更小的类构成的,这些类有相应的编译类。从ns外部看来,整个的仿真过程可以看成对仿真器的操作。因此,我们的工作从创建一个仿真器的实例对象开始,之后,通过这个仿真器调用各种方法生成节点,进而构造拓扑图,对仿真的各个方面进行配置,定义事件,然后,根据定义的事件,模拟整个网络活动的过程。

有关仿真器的文件有~ns/tcl/lib/ns-lib.tcl,~ns/scheduler.{cc,h},和~ns/heap.h。相对于C++代码中类的构造函数,OTcl代码有类的初始化函数,类仿真器的初始化函数如下:

Simulator instproc init args {

$self create_packetformat

$self use-scheduler Calendar

$self set nullAgent_ [new Agent/Null]

$self set-address-format def

eval $self next $args

}

在解释器中创建一个新的仿真器对象,初始化函数执行以下操作:

?初始化数据包格式(又称create_packetformat)

?创建一个调度员(scheduler)

调度员以事件驱动的方式控制仿真的运行,缺省的调度员为calendar scheduler。当然,用户可以指定别的调度员,以实现别的调度策略。目前可用的调度员有四种:Sceduler/Link,Sceduler/Heap,Sceduler/Calendar queue和Sceduler/RealTime。

?创建一个“null agent”

语句set nullAgent_ [new Agent/Null]可以创建一个null agent,用来发送、接收一个数据包。

总的说来,仿真器提供了一系列的方法,可以分三类:创建、管理拓扑图(通过管理节点,和链来实现),跟踪,协调函数与调度员。

仿真器封装了许多功能模块,最基本的是节点、链路、代理、数据包格式等等。下面分别是这些模块的解释。

6.2节点(Node)和数据包的转发

节点是ns最重要的模块,其实是由分类器(classifier)构成的。节点也是一个由OTcl 实现的解释类,也没有相应的编译对象。有关节点的函数、过程可以在文件目录~ns/tcl/lib 下的文件ns-nodes.tcl, ns-rtmodule.tcl和~ns目录下的rtmodule.{cc,h}, classifier.{cc, h}, https://www.doczj.com/doc/ce13956219.html,, https://www.doczj.com/doc/ce13956219.html,, https://www.doczj.com/doc/ce13956219.html,,https://www.doczj.com/doc/ce13956219.html,文件中找到。

由以下Otcl代码可以得到一个节点:

set ns [new Simulator]

$ns nod e

缺省状况下,node为独播节点。要想得到多播节点需要加入参数“-multicast on”,如:set ns [new Simulator -multicast on]

6.2.1 普通节点(node)和多播节点的内部构造如下页

6.2.2 node类的方法,可以分为以下四类:

?控制功能

$node entry:返回指向节点的入口(entry)。对独播节点,由地址分类器检查目的节点的高地址位,实例变量classifier_ 指向分类器。但对于多播节点,由switch_ 检查地址第一位,判断是由独播分类器转发数据包,还是由多播分类器处理。

$node reset:重新设置节点所有的agent。

?管理地址、端口id,实现独播功能:

$node id:返回节点id。类Simulator保持一个存储指向node的入口的数组Node_,并以节点id为下标。

$node agent < port>:返回特定端口的agent的句柄,若此端口没有agent,返回空串。

………………

?管理与节点相关的Agent:

过程attach{}将agent加入节点的agents_表;

过程detach{}将agent从agents_表中去掉;.

?跟踪相连接点:

每一节点保持一张表,(由neighbor_指向),记录相连节点。过程add-neighbor{} 将新的相连节点加入表中,过程neighbors{} 返回此表。

6.2.3 节点属性的配置在附录中给出。

6.3 节点的组分:分类器(Classifier)

node接收到一个包后,要检查特定的域,通常是目的地址(还有可能是源地址),决定如何处理这个包,如果是转发,则还要决定转发到哪里。这项任务由分类器对象来完成,不同类型的分类器对象检查包不同的域决定如何转发。

6.3.1 五种分类器:base,address,multicast,multipath,replicator

?基类分类器(在文件~ns/classifier.h定义)是一虚基类:

每一分类器带一指针,指向一张表,这张表记录NsObject对象与槽号的对应关系。分类器的工作是判断哪个槽号与接收到的数据包相关,然后将包转发给相应的NsObject类对象。

它用方法recv()接收一个包,交给另一方法classify()处理。不同类型的分类器定义了不同的classify()方法。通常的格式是,classify()返回槽下标,若此下标有效,指向一个有效的TclObject对象,则classifier将数据包转发给这个对象,而该对象用自己的recv()方法接收,若下标无效,分类器调用实例过程no-slot{}。基类的no-slot{}仅是输出出错信息,中止执行。

command()方法还提供了以下的命令:

_ clear{ (slot) } :清空slot表。

_ installNext{(object)}:将对象装入下一槽中,返回槽的号码。

_ slot{(index)}:返回指定的槽的对象。

_ install{_ index_ , _ object_ }:将指定的对象装入指定的槽中。

?address classifier(在~ns/https://www.doczj.com/doc/ce13956219.html,中定义):支持独播,通过位移和掩码将包的目

的地址转化成槽的号码;

?multicast classifier(在~ns/https://www.doczj.com/doc/ce13956219.html,中定义):保持一个哈希链表,匹配地址对,

取到槽的号码。如为未知地址对,则用Node::new-group{}加入表中。

?multipath classifier(在~ns/https://www.doczj.com/doc/ce13956219.html,中定义):支持等价多路转发。

?hash classifier:使用哈希表,将包分发给“流”

?replicator:重载了recv() 方法,拷贝数据包,转发给slot表中的所有对象。

6.3.2 分类器组织和路由模块:

实质上,节点其实是分类器的集合,最主要的功能是路由。最简单的独播节点,仅包含一个地址分类器和一个端口分类器。在节点内加入更多的分类器,就能扩展节点的功能,多播节点就是个例子。当节点中的分类器越来越多,就需要一个统一的接口来组织这些分类器,也就形成一个路由模块。路由模块由三个功能子块组成:

?Routing agent :与邻居节点交换数据包;

?Route logic:利用routing agent收集到的信息确定路由,对于静态路由,还可以根据

全局拓扑图进行路由计算;

?Classifiers:根据路由计算的结果,实现数据包的转发。

目前,ns实现的路由模块有以下几种(在~ns/tcl/lib/ns-rtmodule.tcl中):

?RtModule/Base:面向独播协议,提供最基本的功能,如:添加/删除路由,连接/断

开agent。

?RtModule/Mcast:面向独播协议,只建立multicast classifiers。

?RtModule/Hier:多层次的路由,可以和其他的路由协议相结合,比如ad hoc路由。

?RtModule/Manual:Manual 路由。

?RtModule/VC:用虚拟classifier取代vanilla classifier。

?RtModule/MPLS:实现MPLS功能。

6.4 链路(Class Link)

就像节点是由分类器组成的一样,一个简单的链路对象是由一些连接器(connector)组成的。类Link是Otcl中的一个标准类,提供了一些简单的功能。提供了一些简单的功能,比如,通过函数simplex-link{}可以建立从一个节点到另一节点的单向链。

下面的Otcl语句创建了一条简单的连接:

set ns [new Simulator]

$ns simplex-link

函数创建了一条从node0到node1的链,指定了带宽、延迟和使用的队列类型,同时还给这条链加了一个TTL检查器(checker),

相关主题
文本预览
相关文档 最新文档