NS2中几个重要的类

来源:互联网 发布:淘宝品控违规怎么处理 编辑:程序博客网 时间:2024/05/29 13:45

1.Tcl类

这个类封装是OTcl解释器的真正实例,可以当成解释器理解。其中定义了解释器访问及通信的方法。这个类是在~tclcl/tclcl.h和~tclcl/tcl.cc中定义的,提供了以下的操作方法:

1.1 获得 Tcl 实例的一个指针

   在类定义有 static Tclinstance_;获取的方法是通过一个静态的内联函数static inline Tcl& instance() {return (instance_); }实现的。

1.2 通过解释器调用 OTcl 过程

   通过解释器实例tcl来调用OTcl命令,他们在调用参数方面有本质的区别。每个函数都传递一个字串(string)给解
释器,然后解释器通过一个全局文本来识别这个字符串。如果解释器返回TCL_OK,则这些函数将会返一个相应的OTcl
过程。反过来,如果解释器返回TCL_ERROR,则这些函数将调用tkerror{}。用户可以重载(overload)个过程,以便有选
择地忽略某些类型的错误。

1.3 取出或将结果返回解释器

   //tcl的解释器
    Tcl_Interp*tcl_; Tcl_Interp是在~include/tcl.h中的定义的,

    typedefstruct Tcl_Interp {
       char*result;      
       void(*freeProc) _ANSI_ARGS_((char *blockPtr));
       interrorLine;             
    }Tcl_Interp;

   结果就保存在tcl_->result中。

   取出结果:tcl.result(void)必须用于取回结果。注意这里的结果是一个字符串,它必须被转化成一个适合结果类型的内部格式。

    结果传给解释器,即设置tcl_->result的值,a)tcl.result(const char*s),b)tcl.resultf(const char* fmt, . . . )

1.4 报告错误状态并以统一的方法退出

   编译代码中提供了一种统一的报告错误的方法。

   tcl.error(const char* s)执行以下功能:将s写入stdout;将tcl_->result写入stdout;退出,并将错误代码(error code)置1。
   tcl.resultf("cmd = %s", cmd);
   tcl.error("invalid command specified");

1.5 存储并查找“TclObjects“

   ns将每个TclObject在编译层次的一个指针(reference)存在一个hash表中;这样就能快速地访问该对象了。该hash
表在解释器的内部。Ns用户以TclObject的名字为关键字(key)在hash表中进行插入、查找或者删除TclObject的操作。

   tcl.enter(TclObject* o)将在hash表插入一个指向TclObjecto的指针(pointer)。它被TclClass::create_shadow()
用来在对象建立时,将其插入表中。

   tcl.lookup(char*s)将取回名为s的TclObject。可以这样被使用:TclObject::lookup()。

   tcl.remove(TclObject* o)将删除hash表中TclObjecto的指针。可以用TclClass::delete_shadow()来移出hash表中
存在的入口,此时该对象已经被删除。

2.TclObject类

   TclObject类是解释和编译层次大多数其它类的基类(baseclass)。TclObject类中的每个对象都由用户从解释器中创
建。编译层次中同时有一个与之对应的影子对象(shadow object)被创建。这两个对象相互紧密联系。下一小节描述的
TclClass类,包含了执行这种投射(shadowing)的机制。
   TclObject类包含了早期的NsObject类的函数。因此,它储存了变量绑定(bindings)接口,这些变量绑定绑定了解释对象中的实例变量(instance variables)和相应的编译对象中的C++成员变量(membervariables)。这种绑定比ns版本1要强,因为OTcl变量的任何变化都是被跟踪(trapped)的,而且每次当前的C++和OTcl的值被解释器访问后都要保持一致。这种一致性是由InstVar类来完成的。同样,和ns版本1不同的是,tclObject类的对象不再存储在一个全局链表(link list)中。而是存储在Tcl类的一个hash表中。

2.1 创建(creating)和撤销(Destroying)TclObjects

   当用户创建一个新的TclObject时,通常调用new{}和delete{}过程(procedures),这些过程定义在~tclcl/tcl-object.tcl中【未找到】。它们可以用于创建和撤销所有类的对象。

   创建TclObjects:用户可以调用new{}来创建一个解释类的TclObject。这时解释器将执行这个对象的构造函数
(constructor)init{},同时给它传递用户提供的任何参数。ns自动创建相应的编译对象。shadow对象是通过基类TclObject
的构造函数被创建的。因此,新的TclObject的构造函数必须首先调用父类的构造函数。new{}方法返回一个对象的handle,
用户可以通过这个handle对对象进行进一步的操作。
   如下面是Agent/SRM/Adaptive的构造函数:
Agent/SRM/Adaptive instproc init args {
    eval $selfnext $args
    $self arrayset closest_ "requestor 0 repairor 0"
    $self seteps_ [$class set eps_]
}

   创建一个gent/SRM/Adaptive对象时执行的步骤:

2.1.1 从TclObject的名字空间(namespace)获取一个惟一的新的对象的handle。这个handle将返回给用户。ns中大多数handle都是以_o的形式出现的,这里的是一个整数。这个handle由getid{}创建。它可以从C++中的name(){}的方法获得。

   在TclObject类中定义:

    #if0
   
    #defineTCLCL_NAME_LEN 12
    charname_[TCLCL_NAME_LEN];
    #else
    char*name_;
    #endif
    inline constchar* name() { return (name_); }
    voidname(const char*);

2.1.2执行新对象的构造函数。任何的特定的用户输入参数都将作为构造函数的参数传入。这个构造函数必调用其父类的构造函数。

   在上面的例子里,Agent/SRM/Adaptive在第一行就调用了其父类。
   需要注意的是,每个构造函数,依次调用其父类的构造函数。 那么ns中的最后一个构造函数就是TclObject的构造函数。
这个构造函数用来创建对应的shadow对象,并执行其它的初始化工作与绑定。因此最好在构造函数先调用父类的构造函数,然后再初始化。这样可以使shadow对象先被建立,从而有变量可以绑定。

2.1.3TclObject的构造函数为Agent/SRM/Adaptive类调用create-shadow{}实例过程。

2.1.4当shadow对象建立以后,ns为编译对象调用所有的构造函数,它们每个都有可能为类中的对象建立相应的变量绑定,同时执行其它的必要的初始化工作。因此我们最好把调用父类的构造函数的语句放在类初始化语句之前。

2.1.5在shadow对象成功创建后。这样就通过create_shadow(void)将创建的TclObject对象添加到tcl类的hash表中,如上文。使cmd{}成为一个新创建的解释对象的实例化过程。这个实例化过程会调用编译对象中的command()方法。在接下来的一节里,我们将介绍command方法是如何定义并被调用的。

2.1.6 注意映射机制(shadowingmechanisms)只有当用户通过解释器创建新的TclObject的时候才有效。但如果程序员只是单向创建编译TclObject,这个机制将会失效。因此,程序员不要直接用C++的新方法来创建编译对象。
    TclObject的撤销delete操作将同时撤销解释对象和相当的shadow对象,
   delete过程来移除默认的链表计划(list scheduler),同时在原处实例化一个替代的计划。
    Simulatorinstproc use-scheduler type {
    $selfinstvar scheduler_
    deletescheduler_# 首先删除已有的list scheduler
    setscheduler_ [new Scheduler/$type]
    }
   同构造函数一样,对象的析构函数必须明确地调用其父类的析构函数,作为该析构函数的声明的最后部分。TclObject的析构数将调用delete-shadow实例过程,从而依次调用对应的编译方法来撤销shadow对象。解释对象将由解释器自身撤销。

2.2 变量绑定(Variable Bindings)

   大多数情况下,访问编译成员变量只能通过编译代码,同样地,访问解释成员变量只能通过解释代码;但是,在它们之建立一个双向绑定是有可能的,这就使得解释成员变量和编译成员变量都可以访问同样的数据,而且它们中的任何一个变值的变化均可以使另一个相应地变为同样的值。

   这种绑定是对象在实例化的时候,由编译对象的构造函数建立起来的;它也作为一个实例变量被解释对象自动访问。ns持五种不同的数据类型:实型(reals) 带宽变量 , (bandwidth valued variables) 时间变量,(time valuedvariables),整型(integers),布尔型(booleans)。

   如下例:ASRMAgent类(这是个编译类)的构造函数:

   SRMAgent::ASRMAgent() {
      bind("pdistance_", &pdistance_);
      bind("requestor_", &requestor_);
      bind_time("lastSent_", &lastSessSent_);
      bind_bw("ctrlLimit_", &ctrlBWLimit_);
      bind_bool("running_", &running_);
       }

      上述所有的函数都需要两个参数:OTcl变量的名字和绑定的相应的编译成员变量的地址。通常来说,这些绑定是在由构造函数建立的,但是也可以由其它方式完成。如可以通过InstVar类来完成。

   每个被绑定的变量在对象创建的初始化时候,自动地被赋予默认值。这些默认值被指定为解释类变量。这个初始化过程是在init-instvar{}中被执行的,而这又是被Instvar类中的方法调用的。init-instvar{}检查(check)解释对象的类和该对象所有的父类,从而找到定义变量的第一个类。它利用那个类中的变量值来初始化这个对象。大部分绑定的初始化值定义在~ns/tcl/lib/ns-default.tcl中。

  需要注意的是,实际的绑定过程是在InstVar类的对象实例化过程中完成的。InstVar类中的每个对象绑定一个编译成员变量和一个解释成员变量。一个TclObject存储一个InstVar对象及相应的成员变量的链表。这个链表头被存储在TclObject的成员变量instvar_里。

2.3 变量跟踪(Variable Tracing)

   除变量绑定以外,TclObject同时也支持对C++和Tcl实例变量的跟踪。一个被跟踪的变量既可以在C++又可以在Tcl中建和配置。如果在Tcl层建立变量跟踪,变量必须在Tcl下是可视的,这也就意味着它必须是一个绑定的C++/Tcl,或者是一个纯Tcl实例变量。

  如果一个TclObject去跟踪变量,它必须扩展C++中的trace方法,那本来是定义在TclObject中的一个虚函数。Trace类只实现一个简单的trace方法,因此,它可以作为一个一般的变量跟踪函数。

   下面是一个在Tcl中设置跟踪变量的简单的例子:

    #$tcp跟踪它自己的变量cwnd_
    $tcp tracecwnd_
    #$tcp中的变量ssthresh_ 被一个一般的$tracer跟踪
    set tracer[new Trace/Var]
    $tcp tracessthresh_ $tracer

2.4 command方法: 定义与调用(Invocation)

参考http://csutornado.blog.sohu.com/43543936.html

   对于每个建立的TclObject,ns都将建立cmd()实例过程,作为一个挂钩(hook)使编译的影子(shadow)对象可以执行一些方法。cmd()过程自动调用影子对象的command()方法,并将cmd()的参数以向量的形式传递给command()方法。用户可以通过下面两种方法调用cmd():显示地调用过程,将所要进行的操作作为第一个参数;隐示地调用,就好像有一个同名的实例过程作为所需的操作。大多数模拟脚本都采用后者,因此,我们将先介绍后者。

我们都知道SRM 中的距离计算是在编译对象中执行的;但是,它却通常被解释对象使用。它通常以如下方式被调用:$srmObjectdistance? (agentAddress)
如果没有实例过程叫做distance?,那么解释器将调用实例过程unknown(),这个过程在基类TclObject中定义。然后unknown 过程将调用
$srmObject cmd distance?
通过编译对象的command()过程来执行操作。当然,用户可以显示的直接调用操作。其中一个原因可能是用一个同名的实例过程来重载这个操作。例如,

Agent/SRM/Adaptive instproc distance? addr {
$self instvar distanceCache_
if ![info exists distanceCache_($addr)] {
set distanceCache_($addr) [$self cmd distance? $addr]
}
set distanceCache_($addr)
}
我们现在就来说明command()方法是怎样定义的,以ASRMAgent::command()为例。
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));
}
我们可以从上述代码中得到一下几点提示:
  函数调用时需要两个参数:第一个参数(argc)代表解释器中该行命令说明的参数个数。命令行向量(argv)包括
——argv[0]为方法的名字,"cmd"。
——argv[1]为所要求的操作。
——如果用户还有其他特殊的参数,他们就被放在argv[2...(argc-1)]。
 参数是以字符串的形式传递的;他们必须被转换成适合的数据形式。如果操作成功匹配,将通过前面(3.3.3)的方法返回操作的结果。command()必须以TCL_OK或TCL_ERROR 作为函数的返回代码,来表明成功或者失败。
  如果操作在这个方法中没有找到匹配的,它将调用其父类的command方法,同时也就返回相应的结果。这就允许用户创建和对象过程或编译方法一样层次特性的操作。当command方法是为多继承的类定义时,程序员可以自由的选择其中一个实现;
1)可以调用其中一个的父command 方法,然后返回其相应的结构,或
2)可以以某种顺序依次调用每一个的父command 方法,然后返回第一个调用成功的结果。如果没有调用成功的,将返回错误。
在我们这个文件里,我们把通过command()执行的操作叫做准成员函数(instproc-likes)。这个名字反映了这些操作作为一个对象的OTcl实例过程的用途,但是也存在一些微小的实际和用途上的差距。

3 TclClass类

   这个编译类(classTclClass)是一个纯虚类。从这个基类派生的类提供两种功能:构建与编译类层次镜像的解释类层次;提供实例化新TclObjects的方法。每一个这样的派生类与编译类层次中的特定的编译类相互关联,同时可以实例化这个关联类的新对象。

   static class RenoTcpClass:public TclClass {
   public:
   RenoTcpClass() : TclClass("Agent/TCP/Reno") {}
    TclObject*create(int argc, const char*const* argv) {
    return (newRenoTcpAgent());
    }
    }class_reno;

   当ns初次启动时,执行对象的构造函数。
   这个构造函数以解释类名为其参数,调用TclClass的构造函数。
   TclClass的构造函数存储这个类的名字,并把这个对象插入到TclClass对象的链表中。
   在模拟器的初始化过程中,Tcl_AppInit(void)调用TclClass::bind(void)。
   对TclClass对象链表中的每一个对象,bind()调用register{},以解释器名作为其参数。
   register{}建立层次类,建立那些必需,却还创建的类。
   最后,bind()为新类定义create-shadow和delete-shadow两个实例过程。

4 TclCommand类

   该类(TclCommand类)仅仅为ns提供向解释器输出简单命令的机制,然后由解释器在全局范围内执行。

5 EmbeddedTcl类

   ns允许对编译代码或者解释代码的功能扩展,这个扩展代码将在初始化的时候被执行。

6 InstVar类

   该类定义了将编译的shadow对象中的C++成员变量绑定到与之对应的解释对象中的Tcl实例变量上的方法与机制。这种绑定的建立可以使变量的值在任何时候都能从解释器或编译代码中设置和访问。

0 0