(转)关于TCP的一个跨层设计的 具体实现思路 (兼讨论 tcl.eval与Tcl_Eval的区别)

老师要求为他的一篇关于跨层相关论文做仿真验证,其中有一个如下需求,描述如下:

每个节点既可以是TCP 发端,也可以是TCP收端,两个身份可以同时兼职,也可以只任其一。

在本需求中,发端的TCP的下层MAC IFQ队列向下放行一个数据包以后,需要及里依次通知上层的路由及TCP层下放一个包,以便提高网络的反应速率。 当路由层没有待下行的数据包时,便通知上层的TCP下放包。 

原先已经实现了,当TCP发端 的下层MAC发送成功一个TCP包以后,可以通过回调机制,再通知本节点的TCP发端再次下放数据(回调函数传入当前发送TCP Agent的对象指针 this) 
后来,发现有一种情形没有考虑到:那就是当一个节点既是收端又是发端时,收端收到一个通信对方实体发过来的TCP包并回送一个ACK后,也应及时通过回调机制,通知TCP 发端 Agent下放数据。 
可问题在于,收端和发端是由两个不同的Agent完成的功能,一个是SACKTCPAgent,另一个是 TCPSinkAgent;
不同于原来的实现,在这种情况下,必须要求 TCPSinkAgent 在发送ACK包后,及时通知SACKTCPAgent 及时下放一个数据包(前提是路由层无包可下放)。 如前所述,这种通知我们是通过回调函数实现的。 在原实现中,回调函数传入的就是当前SACKTCPAgent对象的指针,因为下放新包的动作执行者就是当前SACKTCPAgent对象;但在当前的这种情况下,回调函数赋参时,所处的是 TCPSinkAgent 方法中,想要获得SACKTCPAgent对象的指针并赋给回调函数 就比较麻烦了。

{特别指明,在这两种实现中,一开始都只是将回调函数的参数准备好,真正调用回调函数的地方都位于 MAC层的 IFQ队列的 deque函数中, 这跟老师的论文思想有关。即:MAC层发包后,就触发新的上层包下发,从而部分取代TCP ACK的激励发包功能 }

{另外,回调函数的参数设置都放在包结构中,因为ns2中包在同一节点的各层传输时,都是恒定的,丢释放}

因为,大体上来讲,NS2本身的框架就是用 TCL脚本当作粘合剂,各个C++影像类负责各自的实现,相互之间的联系完全靠TCL层次的沟通(本博客的其余贴中,有说明TCL脚本是如何组装一个节点的)
而在原NS2实现中,TCPSinkAgent 和 SACKTCPAgent,虽处同一节点中,但一个管发,一个管收,完全不交叉。 因此,想要在 TCPSinkAgent 的方法中,获得同节点的 SACKTCPAgent的对象指针,在n2中没有现成的实现~ 

想要达成目的,唯一的方法就是从粘合剂下手:

我们知道,任何一个节点,都是通过 attach-agent将自己与Agent绑定的(Agent 还分TCP Agent, Route Agent 等等), 而 TCL类 node 的attach-agent方法中,只是将 agent OTCL对象的node_ 成员置为当前节点;    因此,对于 TCPSinkAgent 和 SACKTCPAgent 所对应的OTCL对象来说,都有一个共同的成员为 node_,其值就是为包含这两个Agent的节点 (节点此时其实可以看成一个容器)

知道这个线索后,大致的实现思路便可阐述如下:

1 . 在 TCPSinkAgent 接收完一个数据包并发送ACK后,通过 Tcl::instance.Eval() 函数执行一段 tcl代码,代码的具体内容是:
$self set node_ 
然后 再通过获得TCl.result()获得上句tcl脚本执行的结果。此结果应该为 包含当前 TCPSinkAgent的 节点在OTCL环境中的 ID值(通常为 _O+integer形式) . 通过本博客的另一贴 “ns2中常用的命令” 的分析可以知道,得用这个ID值以及 TCL::Lookup函数 是可以获得当前节点对应的C++影像类的对象指针的。 但是,从Node C++类的定义中,我们无法找到可以获得当前SACKTCPAgent 的方法或者变量(这也很好理解。因为,一个节点作为容器,里面装什么东西,本来就是灵活配置的。因此,肯定不会写死在C++代码里,只可能在tcl层次中进行灵活组装。正如一个节点也可以用不同版本或类型的TCP Agent用来发数据)

2. 获得当前节点的ID值后的操作:
我们知道在OTCL环境中,当前节点有一个成员变量 agents_ 这个变量记载了节点容器中所装配的所有 Agent对象,有routing agent, Tcp agent等~
因为,一个节点只可能有一个用于发送的 TCP Agent作为发端,因此,通过遍历agents_中存储的所有agent 并根据其是否为 TCPAgent的子类实现(实现时应该直接用info class命令判断其是否为Sack1TcpAgent类,info superclass命令不可用于此处),便可唯一确定地获当前管发送的 agent_ 值了。 在本实现中,管发送的是Sack1TcpAgent. 当然,这个agent_的值也是 _O+ integer形式,全局唯一。 通过 前面所述的 TCLObject::lookup 以及 强制类型转换, 我们便可以获得当前节点的 SACKTCPAgent C++实例了,然后,再将其对象指针作为参数赋给回调参数。

(遍历Agents的工作都需要用TCL来完成; 1,2 步的工作合成一下,具体实现如下:)


set curNode_ [$self set node_]
set curAgentList_     [$curNode set agents_]
foreach Agent_  $curAgentList_ {
if { [$Agent_ info class ]=="Agent/TCP/Sack1"}
return $Agent_
}



这样,当在真正调用回调函数时,便可以真正地调用 SACKTCPAgent 的新包发送函数 下放新的数据包了。

兼讨论 tcl.eval与Tcl_Eval的区别


总结: 关于这个区别,在ns2 源代码中,有一处特别说明:

void
TclObject::create_instvar(const char* var)
{
/*
* XXX can't use tcl.evalf() because it uses Tcl_GlobalEval
* and we need to run in the context of the method.
*/
char wrk[256];
sprintf(wrk, "$self instvar %s", var);
Tcl_Eval(Tcl::instance().interp(), wrk);
}

在Tcl_Eval函数的定义处,我们也无法获取到更多的 信息来 区别 Tcl_Eval 与 tcl.eval 
但,通过create_instavar函数处的说明, 我猜测:
tcl.eval(或者是evalc evalf 等等),这些函数在执行 tcl语句时,其所处环境都为全局环境,也就是说,在调用tcl.eval前,ns2框架可能要先通过栈操作保存原先的otcl执行环境;而Tcl_Eval 执行的脚本所处环境即为当前ns2执行到的 otcl环境,并没有发生环境切换. 因此,我们可以在Tcl_Eval 脚本中执行 $self 语句,但却不能在 tcl.eval中执行(当然,前提是我们知道当前所处的是哪一个具体的OTCL过程,才能知道有哪些局部变量或者方法可调用)。
另外,Tcl_Eval可以执行return 语句,但在tcl.eval中却不能。 因为, Tcl_Eval所处环境为某个OTCL的具体过程,当然可以返回,而tcl.eval所处的全局环境显然不允许return了。 
在对本实现的编程实现过程中,我们也间接验证了这些猜测。
其实,通过跟踪tcl.eval的具体实现,我们发现它最终调用的是一个Tcl_GlobalEval函数,而这个函数的定义处,明确说明如下:
*
 * Tcl_GlobalEval --
*
*    Evaluate a command at global level in an interpreter.
*
* Results:
*    A standard Tcl result is returned, and the interp's result is
*    modified accordingly.
*
* Side effects:
*    The command string is executed in interp, and the execution
*    is carried out in the variable context of global level (no
*    procedures active), just as if an "uplevel #0" command were
*    being executed.

从该说明中可以确定: tcl.eval中执行的脚本,其所处的环境都是全局环境。因此,再次验证上面的猜测。

今天看了season的29问后,发现其实还有一种实现是可以达到同样的目的的。只是,在上篇提及的实现中,所有更改均是在 TCPSinkAgent的C++实现中,仿真脚本中不需要针对这个实现作任何多余的设定;本篇中,受season29问的最后一问的启发,找到一种需要在具体的仿真脚本中额外设定,但实现起来相对容易一些的方法,阐述如下:
1.  在TCPSinkAgent中定义一个指向 同节点的发端的 SACKTcpAgent的对象指针。
2. 在 TCPSinkAgent的command实现中,添加一个对 set-sackagent的方法的实现。 在tcl中,tcpsinkagent对应的otcl实例调用set-sackagent(其中参数设为同节点的 SACKTcpAgent对应的OTCL实例)。 这样,在command的set-sackagent实现中, 将传入的参数,也就是SACKTcpAgent的OTCL实例ID值(_o+integer)通过 TCLObject::Lookup() 返回 SACKTcpAgent的C++实例指针,并将其赋给 TCPSinkAgent的SACKTcpAgent指针。
3.  在TCPSinkAgent接收完数据包并发送完ACK后,将其成员变量 SACKTcpAgent * 指针的值 赋给回调参数
4. 在真正调用回调参数的地方(MAC IFQ dequeue)  便可以 跳转至 TCPSACKTcpAgent的 发包函数,起到激励发新包的作用~

这种实现思路对于动态拓扑的情况来说,不大方便。因为,所有节点间的TCPSink与TCPSACKAgent 的绑定必须全部自己手动实现。

posted on 2013-05-07 12:21  原来...  阅读(663)  评论(0编辑  收藏  举报

导航