NS2分裂对象模型浅析(一)

NS2中的网络构件一般是由相互关联的两个类来实现的,分别是C++类和OTcl类,这种方式称为分裂对象模型。构件的主要功能是在C++类中实现的,OTcl类用来向用户提供操作C++对象的接口。在NS2中,C++类和OTcl类通常具有对应关系,两者的继承关系保持一致。当实例化一个构件对象时,NS2会同时创建一个OTcl对象和一个与之对应的C++对象。两者的映射和互操作是由Tclcl(OTcl C++ Linkage)机制实现的。Tclcl提供了Tcl、TclObject、TclClass、TclCommand、EmbeddedTcl、InstVar六个类。

Tcl

提供了C++访问OTcl解释器的方法

TclObject

是所有具有映射关系的OTcl类和C++类的基类

TclClass

定义了OTcl类层次和用户创建TclObject对象的接口

TclCommand

用以定义简单的全局解释命令

EmbeddedTcl

用于定义Tcl命令

InstVar

提供了OTcl访问C++类属性的方法

Tclcl主要通过TclObject、TclClass(两个C++类)和SplitObject(OTcl类),实现OTcl类和C++类的关联的。

下面,我们以创建一个Agent/UDP对象为例,解释Tclcl的具体实现方法。

当我们执行 new Ageng/UDP 命令的时候,会调用到tclcl/tcl-object.tcl中的new{}过程

[plain]  view plain copy
  1. proc new { className args } {   
  2.    # 获取一个唯一的id,形如_oX,其中X表示对象被创建的次序
  3.     set o [SplitObject getid]
  4.     # 下面调用create{}过程,实际上调用的是Class的create{}(Agent/UDP没有create{})
  5.     # 在otcl/doc/class.html文件中,说明了该create{}是在$className没有定义create{}时被unkown{}调用的
  6.     # create{}提供了创建其他对象和类的机制  
  7.     if [catch "$className create $o $args" msg] {  
  8.         if [string match "__FAILED_SHADOW_OBJECT_" $msg] {
  9.             #
  10.             # The shadow object failed to be allocated.  
  11.             #   
  12.             delete $o  
  13.             return ""  
  14.         }  
  15.         global errorInfo  
  16.         error "class $className: constructor failed: $msg" $errorInfo  
  17.     }  
  18.     return $o 
  19. }

运行ns命令,%Agent/UDP info instcommands,输出结果如下,不含有create(如果执行%Agent/UDP info instprocs则输出只有process_data、done)


% Agent/UDP info instcommands
process_data done delete-shadow create-shadow



instprocs只给出类的方法,instcommands还会给出通过C++添加的方法


继续分析Class的create{},class.html给出了该过程的概念定义


Class instproc create {obj args} {              
  set h [$self info heritage]              
  foreach i [concat $self $h] {                 
    if {[$i info commands alloc] != {}} then {  
      set args [eval [list $i] alloc [list $obj] $args]       
      $obj class $self                          
      eval [list $obj] init $args                      
      return $obj                               
    }                                           
  }                                             
  error {No reachable alloc}                    
}


而调用该过程的unknwon过程定义如下

Class instproc unknown {m args} {       
  if {$m == {create}} then {            
    error "$self: unable to dispatch $m"
  }                                     
  eval [list $self] create [list $m] $args 
} 


此时,两个过程中的$self应该是Agent/UDP,$self info heritage即 Agent SplitObject Object,三者都是Agent/UDP的基类

create{}继续检查Agent/UDP Agent SplitObject Object是某含有alloc这个命令,经过实测,确定Object类含有alloc

% Agent/UDP info commands alloc
% Agent info commands alloc
% SplitObject info commands alloc
% Object info commands alloc
alloc
% 



然后,调用Object的alloc{}过程分配对象 (还需跟踪),设置新创建的对象的类型为Ageng/UDP

然后调用init{},通过 * info instprocs可以判断出,Agent/UDP继承了Agent的init{}过程

而Agent 的int{}过程定义在gen/ns_tcl.cc中

Agent instproc init args {\n\
eval $self next $args\n\
}       \n\

可见,其调用基类SplitObject的init{},SplitObjecting定义在tclcl/tcl-object.tcl文件中

[plain]  view plain copy
  1. SplitObject instproc init args {  
  2.     $self next  
  3.     if [catch "$self create-shadow $args"] {  
  4.         error "__FAILED_SHADOW_OBJECT_" ""  
  5.     }  
  6. }  
$self next调用Object的init{}  ??(还需追踪)

最后,调用Agent/UDP的create-shadow{},到了这里,就需要结合C++源码来分析了。


首先,来看类UdpAgentClass

[cpp]  view plain copy
  1. static class UdpAgentClass : public TclClass {  
  2. public:  
  3.     UdpAgentClass() : TclClass("Agent/UDP") {}  
  4.     TclObject* create(intconst char*const*) {  
  5.         return (new UdpAgent());  
  6.     }  
  7. } class_udp_agent;  

UdpAgentClass派生自TclClass(一个纯虚类),并实现了create函数,返回一个UdpAgent实例。

需要注意的是, 这里定义了一个静态对象,也就是说在NS启动是,就会调用构造函数,即执行TclClass("Agent/UDP")

现在,来分析一下TclClass("Agent/UDP")所做的工作。

[cpp]  view plain copy
  1. TclClass* TclClass::all_;  
  2.   
  3. TclClass::TclClass(const char* classname) : class_(0), classname_(classname)  
  4. {  
  5.     if (Tcl::instance().interp()!=NULL) {  
  6.         // the interpreter already exists!  
  7.         // this can happen only (?) if the class is created as part  
  8.         // of a dynamic library  
  9.   
  10.         bind();  
  11.     } else {  
  12.         // the interpreter doesn't yet exist  
  13.         // add this class to a linked list that is traversed when  
  14.         // the interpreter is created  
  15.           
  16.         next_ = all_;  
  17.         all_ = this;  
  18.     }  
  19. }  

在初始化列表中,成员变量classname_被初始化为"Agent/UDP"
我们应经打开了解释器,进入bind()

[cpp]  view plain copy
  1. void TclClass::bind()  
  2. {  
  3.     Tcl& tcl = Tcl::instance();  
  4.     tcl.evalf("SplitObject register %s", classname_);  
  5.     class_ = OTclGetClass(tcl.interp(), (char*)classname_);  
  6.     OTclAddIMethod(class_, "create-shadow",  
  7.                (Tcl_CmdProc *) create_shadow, (ClientData)this, 0);  
  8.     OTclAddIMethod(class_, "delete-shadow",  
  9.                (Tcl_CmdProc *) delete_shadow, (ClientData)this, 0);  
  10.     otcl_mappings();  
  11. }  

bind()首先获取Tcl的一个实例,可以通过它来操作OTcl对象。

然后,通过evalf方法执行了Tcl命令:SplitObject regsiter Agent/UDP,我们来看看它做了什么工作

[plain]  view plain copy
  1. SplitObject proc register className {  
  2.     set classes [split $className /]  
  3.     set parent SplitObject  
  4.     set path ""  
  5.     set sep ""  
  6.     foreach cl $classes {  
  7.         set path $path$sep$cl  
  8.         if ![$self is-class $path] {  
  9.             Class $path -superclass $parent  
  10.         }  
  11.         set sep /  
  12.         set parent $path  
  13.     }  
  14. }  

该过程的工作就是注册一系列类,比如,执行%SplitObject A/B/C将会创建三个类,A、A/B、A/B/C,都派生自SplitObject,

并且,A/B说明了B是A的派生类

继续分析,看到了create-shadow,SplitObject 的init{}调用的create-shadow正是OTclAddIMethod添加的,

参数"create-shadow"指明了在OTcl中的方法名称,OTclAddIMethod添加了OTcl类Agent/UDP的create_shadow{}和

TclClass的create_shadow()的映射关系。SplitObject 的init{}调用create-shadow{}时,就会调用TclClass的create_shadow()。

而且this指针,即前面定义的静态对象class_udp_agent的指针,作为参数被传入(篇幅受限,咱不详述)


再来看TclClass中的create_shadow方法,代码如下

[cpp]  view plain copy
  1. int TclClass::create_shadow(ClientData clientData, Tcl_Interp *interp,  
  2.                 int argc, CONST84 char *argv[])  
  3. {  
  4.     /* 
  5.      * clientData正是上述OTclAddIMethod函数传入的静态对象class_udp_agent的指针 
  6.      * 然后,调用类UdpAgentClass的create方法,简单返回一个UdpAgent对象指针 
  7.      */  
  8.   
  9.     TclClass* p = (TclClass*)clientData;  
  10.     TclObject* o = p->create(argc, argv);  
  11.     Tcl& tcl = Tcl::instance();  
  12.     if (o != 0) {  
  13.         /* 
  14.          * argv[0]是新创建的对象的名称,就是前面提到的_oX 
  15.          * enter()方法将该对象名和对象指针o添加到OTcl中的hash表中,这样就可以在Tcl中通过对象名访问C++对象了 
  16.          */  
  17.         o->name(argv[0]);  
  18.   
  19.         tcl.enter(o);  
  20.         if (o->init(argc - 2, argv + 2) == TCL_ERROR) {  
  21.             tcl.remove(o);  
  22.             delete o;  
  23.             return (TCL_ERROR);  
  24.         }  
  25.         /* 
  26.          *将对象名返回给Tcl 
  27.          */  
  28.         tcl.result(o->name());  
  29.         /* 
  30.          * 给对象所属类,即Agent/UDP添加新的方法:cmd{}和instvar{},分别映射到TclClass的dispatch_cmd()和dispatch_instvar() 
  31.          * 在Tcl中执行_oX cmdx args时,将会调用到dispatch_cmd() 
  32.          */  
  33.         OTclAddPMethod(OTclGetObject(interp, argv[0]), "cmd",  
  34.                    (Tcl_CmdProc *) dispatch_cmd, (ClientData)o, 0);  
  35.         OTclAddPMethod(OTclGetObject(interp, argv[0]), "instvar",  
  36.                    (Tcl_CmdProc *) dispatch_instvar, (ClientData)o, 0);  
  37.         o->delay_bind_init_all();  
  38.         return (TCL_OK);  
  39.     } else {  
  40.         tcl.resultf("new failed while creating object of class %s",  
  41.                 p->classname_);  
  42.         return (TCL_ERROR);  
  43.     }  
  44. }  

来看dispatch_cmd()方法

[cpp]  view plain copy
  1. int TclClass::dispatch_cmd(ClientData clientData, Tcl_Interp *,  
  2.                int argc, CONST84 char *argv[])  
  3. {  
  4.     TclObject* o = (TclObject*)clientData;  
  5.     return (o->command(argc - 3, argv + 3));  
  6. }  

参数clientData正是OTclAddPMethod()的参数o,本例中,o就是一个UdpAgent对象指针

class UdpAgent声明和command()实现如下


[cpp]  view plain copy
  1. class UdpAgent : public Agent {  
  2. public:  
  3.     UdpAgent();  
  4.     UdpAgent(packet_t);  
  5.     virtual void sendmsg(int nbytes, const char *flags = 0)  
  6.     {  
  7.         sendmsg(nbytes, NULL, flags);  
  8.     }  
  9.     virtual void sendmsg(int nbytes, AppData* data, const char *flags = 0);  
  10.     virtual void recv(Packet* pkt, Handler*);  
  11.     virtual int command(int argc, const char*const* argv);  
  12. protected:  
  13.     int seqno_;  
  14. };  



[cpp]  view plain copy
  1. int UdpAgent::command(int argc, const char*const* argv)  
  2. {  
  3.     if (argc == 4) {  
  4.         if (strcmp(argv[1], "send") == 0) {  
  5.             PacketData* data = new PacketData(1 + strlen(argv[3]));  
  6.             strcpy((char*)data->data(), argv[3]);  
  7.             sendmsg(atoi(argv[2]), data);  
  8.             return (TCL_OK);  
  9.         }  
  10.     } else if (argc == 5) {  
  11.         if (strcmp(argv[1], "sendmsg") == 0) {  
  12.             PacketData* data = new PacketData(1 + strlen(argv[3]));  
  13.             strcpy((char*)data->data(), argv[3]);  
  14.             sendmsg(atoi(argv[2]), data, argv[4]);  
  15.             return (TCL_OK);  
  16.         }  
  17.     }  
  18.     return (Agent::command(argc, argv));  
  19. }  




[cpp]  view plain copy
  1. int UdpAgent::command(int argc, const char*const* argv)  
  2. {  
  3.     if (argc == 4) {  
  4.         if (strcmp(argv[1], "send") == 0) {  
  5.             PacketData* data = new PacketData(1 + strlen(argv[3]));  
  6.             strcpy((char*)data->data(), argv[3]);  
  7.             sendmsg(atoi(argv[2]), data);  
  8.             return (TCL_OK);  
  9.         }  
  10.     } else if (argc == 5) {  
  11.         if (strcmp(argv[1], "sendmsg") == 0) {  
  12.             PacketData* data = new PacketData(1 + strlen(argv[3]));  
  13.             strcpy((char*)data->data(), argv[3]);  
  14.             sendmsg(atoi(argv[2]), data, argv[4]);  
  15.             return (TCL_OK);  
  16.         }  
  17.     }  
  18.     return (Agent::command(argc, argv));  
  19. }  

[cpp]  view plain copy
  1. int UdpAgent::command(int argc, const char*const* argv)  
  2. {  
  3.     if (argc == 4) {  
  4.         if (strcmp(argv[1], "send") == 0) {  
  5.             PacketData* data = new PacketData(1 + strlen(argv[3]));  
  6.             strcpy((char*)data->data(), argv[3]);  
  7.             sendmsg(atoi(argv[2]), data);  
  8.             return (TCL_OK);  
  9.         }  
  10.     } else if (argc == 5) {  
  11.         if (strcmp(argv[1], "sendmsg") == 0) {  
  12.             PacketData* data = new PacketData(1 + strlen(argv[3]));  
  13.             strcpy((char*)data->data(), argv[3]);  
  14.             sendmsg(atoi(argv[2]), data, argv[4]);  
  15.             return (TCL_OK);  
  16.         }  
  17.     }  
  18.     return (Agent::command(argc, argv));  
  19. }  

刚接触NS2,难免有粗糙、错误之处,欢迎批评指正,QQ:706543730
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值