elenna's profile๑۩ﺴ 猪猪艾美丽的学习乐园BlogListsGuestbook Tools Help

Blog


    August 10

    《NS与网络模拟》学习10——trace

         trace可以根据用户的需要记录模拟过程中的任何一个细节。为此,每个分组都包含一个特殊的common分组头,包含了分组的序列号、分组的类型值、分组的大小和端口标识等。

         Tcl中使用Trace类的命令包括:

         flush-trace{}——刷新模拟过程中所有Trace对象的缓冲区
         create-trace {type file src dst}——在给定的src节点和dst节点之间创建一个类型为type的Trace对象。
         trace-queue {n1 n2 file}——设定节点n1和n2之间链路的trace,使能了Enque、Deque和Drop的trace。
         trace-callback {ns command}——每当新增一行trace时调用command
         drop-trace {n1 n2 trace}——设置trace对象为n1和n2链路队列的drop-target

         C++的Trace类用来实现Otcl中的Trace/Hop、Trace/Enque、Trace/Deque、Trace/Drop类的功能。Trace::format()方法定义了由Trace产生的trace文件的格式。

         有线模拟和无线模拟的trace文件格式参考manual手册。

    《NS与网络模拟》学习8——分组头管理

         分组(Packet)是对象间交互的基本单元,由一系列分组头和一个可选的数据空间组成。分组头的结构在Simulator对象创建时就被初始化了,同时每个分组头相对于分组的起始地址的偏移量也被记录下来。

         用户可以为新的协议定义该协议自己的分组头,也可以通过增加域的方式扩展现有的分组头。假如我们想要增加一个叫newhdr的新的分组头,需要完成以下几个步骤:

    1)在C++中创建一个名为hdr_newhdr的新的structure来定义所需要的字段,定义offset_字段和访问字段的方法。
    2)定义所需要的访问其他字段的成员函数
    3)创建一个叫做PacketHeader/Newhdr的static类来完成Otcl连接,在它的构造函数里进行bind_offset()。(PacketHeaderClass)
    4)编辑~ns/tcl/lib/ns-packet.tcl来激活新的分组头(PacketHeaderManager)。另外也需修改~/ns/common/packet.h,来绑定分组类型值和它们的名字(p_info)。

         与分组相关的类概括起来有四个:Packet、p_info、PacketHeaderClass、PacketHeaderManager

    1.Packet类定义了分组的结构(bits_,hdrlen_等),提供了处理Packet对象的一系列成员函数(alloc()、copy()、free()等)。

    2.p_info类,用来绑定各个分组类型值和它们的名字。

      当定义新的分组类型后,数字代码应添加到枚举类型packet_t中,同时应添加到p_info类的构造函数中。注意,PT_NTYPE必须是最后一个。

    3.PacketHeaderClass是各种分组头的基类。其中,hdrlen_是在构造函数中被设置,offset_则是通过调用bind_offsest()函数来设置的。

    4.PacketHeaderManager类用来管理所有处于激活状态的分组头,并在BOB中分配给它们唯一的偏移量。同时定义于C++和Otcl中。

       模拟过程初始化时,ns-packet.tcl代码被执行。foreach循环中所有需要激活的分组头,通过调用add-packet-header来激活(Common头始终被激活)。create_packet_format{}被调用一次,它首先创建一个PacketHeaderManager对象,之后,对于被激活的分组头,由实例过程allochdr{}来给出分组头的位置。

       在Tcl中选择分组头的命令包括:remove-packet-header、remove-all-packet-header和add-packet-header。remove-packet-header必须在Simlator对象创建前执行。

    《NS与网络模拟》学习7——定时器

           定时器(timer)既可以载C++中实现也可以在Otcl中实现。

    1.C++中

       在C++中,各种定时器都是基于抽象基类TimerHandler的,经常用于agent对象中,但也可以被其他对象使用。这里介绍在agent中使用timer的方法:

    1)定义新的定时器

    class MyTimer:public TimerHandler {
    public:
        MyTimer(MyAgentClass *a):TimerHandler() {a_=a;}
        virtual double expire(Event* e);
    protected:
        MyAgentClass* a_;
    }

    2)在agent初始化函数中,调用MyTimer(this)

    3)定义纯虚函数expire()

    MyTimer::expire(Event* e) {
       //do the work
       //return TIMER_HANDLED;            //=>do not reschedule timer
       //return delay;                    //=>reschedule timer after delay            
    }

    2.Otcl Timer类

       定义的一些实例方法包括:sched、cancel、resched、expire等,Timer类没有定义timeout过程,需要派生类自己定义这个过程来完成定时器超时后的相关处理。

    《NS与网络模拟》学习6——链路

           创建单向链路的命令为:$ns simple-link <node0> <node1> <bandwidth> <delay> <queue_type>

           单向链路的结构示意图如下:

                                                     image

          SimpleLink类中有5个主要的实例变量,enqT_、deqT_、recvT_和drpT_等是与trace相关的对象

         1)head_link:指向link的第一个对象。
         2)queue_:指向link的主队列元素。
         3)link_:指向完成延迟和带宽计算的对象。
         4)ttl_:指向完成TTL计算的对象。
         5)drophead_:指向完成对分组丢弃功能的对象。

         与链路有关的queue、DelayLink和TTLChecker均为Connector类的子类,一般都通过重载Connector类的recv()函数,来完成自己独特的功能。Connector类与Classifier类不同:收到一个分组,对分组做相应的处理,然后将分组递交给target_对象,或者将分组丢弃(递交给drop_对象)。

    《NS与网络模拟》学习5——节点

    一. 节点

     1.创建节点——$ns node

        单播节点的结构                                                                         组播节点的结构

        image            image  

    2. 配置节点——$ns node-config

       配置的各项参数见书。注意的是,当配置无线节点时,不需要设置-addressType,并且-channelType的设置也有所不同。

    3.分类器(classifier)

        NS中有各种不同的Classifier对象,分别负责检查分组的不同部分,来完成不同的匹配、筛选工作。每个classifier都包括一个由slot number作索引的对象表。classifier的工作是检查收到的分组的slot number,然后把分组转发给由该slot number索引的对象。介绍几种主要的Classifier类:

    1)address classifier:按照分组的目的地址进行匹配,对分组的目的地址做位运算来产生一个slot number

    2)port classifier:按照分组的目的端口进行匹配,将分组传递给相应的Agent对象。dmux_是节点的PortClassifier对象。

    3)replicatior:生成一个分组的多份拷贝,并把这些拷贝转发给slot表中的所有对象。

    4.Tcl中相关命令包括:

       routing-->add-route/delete-route
       transport-->attach/detach
       Classifier-->insert-entry/install-entry/install-demux

    二.移动节点

         NS的无线模块是由CMU的Monarch工作组引入到NS中的,以MobileNode为基本核心。MobileNode是Node类的派生类,它的功能(包括节点移动、周期性的位置更新、维护拓扑边界等)是在C++中实现的,而设定MobileNode的各个网络构件则是在Otcl中实现的。

         同样用node-config函数来配置一个移动节点。移动节点的各个网络构件如下:

                                                             image

    1)Link Layer:对于所有发出的分组,路由Agent会把分组传递给LL,LL把分组传递给接口队列(IFQ)。对于所有接收到的分组,MAC层将分组传递给LL,LL再将分组传递给node_entry_。移动节点的LL还连接了一个ARP模块,用来把IP地址解析成物理(MAC)地址。

    2)ARP:如果ARP已经知道目标节点的MAC地址,直接写入分组的MAC头中;否则,存放到分组缓冲区,并广播ARP请求。

    3)InterfaceQueue:由PriQueue类实现。是一个优先级队列,优先处理路由协议分组,并可以对分组进行过虑。

    4)MAC层:实现了IEEE802.11的DCF MAC协议。

    5)Network Interface:网络接口是移动节点访问信道的接口,通过碰撞和无线传输模块来接收其他节点发送到信道上的分组。将波长、传输功率等信息写入分组头。

    6)Antenna:使用单一增益的全向天线。

    7)Radio Propagation Model:用来计算每个分组在到达接收节点时的信号强度,小于阈值时标记为error并被MAC层丢弃。包含3种模型:Free-space、Two-ray ground reflection和Shadowing。

    8)Channel:无线信道的功能是将分组复制给所有连接到本信道上的移动节点。

        移动节点运动的方法:1.指定节点的起始位置和终止位置,通常放在一个单独的场景文件中;2.节点随机运动:$mobilenode start使得节点从随机位置开始随机运动,目标和速度是随机产生的。无论采用哪种方式,都需在创建移动节点之前定义移动节点的移动范围,用下面的办法定义平面拓扑的长和宽:

             set topo [new Topography]
             $topo load_flatgrid $opt(x) $opt(y)     

    《NS与网络模拟》学习4——NS的事件调度机制

         在NS中,整个模拟过程由一个名位Simulator的Tcl类来定义和控制。Simulator提供了一些与建立模拟有关的方法,分为3类:(1)创建和管理拓扑结构(即管理node和link);(2)与tracing有关的方法;(3)与事件调度器有关的方法。

         Simulator对象的一系列初始化工作包括:(1)通过调用create_packetformat来初始化packet的结构;(2)创建一个“事件调度器scheduler”;(3)创建一个“null agent”。

         NS是一个事件驱动的模拟器。支持2种类型的事件调度器:非实时(linked-list、heap、calendar缺省)和实时的。scheduler的主要功能是处理分组的延迟和充当定时器。执行过程:从所有事件中选择发生时刻最早的事件,调用它的handler函数,把该事件执行完毕,然后从剩余的所有事件中选择发生时刻最早的事件,如此反复执行。

         一个事件(event)通常由触发时间和Handler函数组成,有两个派生类:at-events和packets。相关的Tcl命令包括:

                set ns [new Simulator]
                $ns halt
                $ns run
                $ns at <time> <event>

    August 09

    《NS与网路模拟》学习3——Tcl类和EmbeddedTcl类

    1.Tcl类

       类Tcl封装了Otcl解释器的实例。

    1)Tcl实例获取方法:Tcl& tcl=Tcl::instance();

    2)c++调用Otcl过程:通过tcl.eval()等。

    3)返回值:tcl.result(void)来获取执行结果。

    4)对象查找:NS使用TclObject的名字作为健值在哈希表中插入、查找或删除TclObject。用户可能用到的是查找:

       TclObject* Tcl::lookup(const char* s)

    2.如何修改NS各种构件的Otcl代码以及很多初始配置脚本?

      先来看一下NS如何通过EmbeddedTcl类将一些初始Otcl脚本载入NS的:EmbeddedTcl类的load()方法将构造函数中传入的code_字符串作为Otcl命令执行了;而ns_tcl.cc中包含两个语句,第一个定义巨大字符常量code,第二,创建EmbeddedTcl对象et_ns_lib;而ns_tcl.cc是在编译NS的过程中自动生成的。如下:

      Makefile文件中:tclsh bin/tcl-expand.tcl tcl/lib/ns-lib.tcl |tcl2c++ et_ns_lib > gen/ns_tcl.cc。

    1)用tclsh来运行tcl-expand.tcl,将tcl/lib/ns-lib.tcl中的所有source语句行用所对应的Tcl脚本替换掉。

    2)把所得文本通过管道传送给tcl2c++程序。

    3)tcl2c++用扩展后的Tcl脚本作为字符串常量code,构造了EmbeddedTcl对象et_ns_lib

    4)最终将生成的C++程序重定向为gen/ns_tcl.cc

      因此,扩展NS的Otcl脚本不仅把它放在~ns/tcl/目录下,需要在tcl/lib/ns-lib.tcl中加一条source命令,还需重新编译NS,以便重新生成ns_tcl.cc。

    《NS与网络模拟》学习2——分裂对象模型和TclCL

         NS利用TclCL建立起分裂对象模型,形成了其丰富的构件库。C++是强制类型语言,适合用于具体协议的实现,而Otcl不是强制类型的,适合用来做模拟配置。所有C++类(编译类)都是从类TclObject一级级继承出来的,而所有Otcl类(解释类)都是从SplitObject一级级继承出来的。

         每个编译对象都是当用户从解释器中创建解释对象的同时再C++类结构中产生出的影像对象。而类TclClass则包含了执行这种映像的机制。

    1. 通过构建解释对象来构建其影像对象——编译对象(反之不行)

         Otcl脚本中使用过程new{}和delete{}来完成。具体如下:调用new{}创建解释对象->调用初始化实例过程init{}->调用父类init{}->最终调用基类SplitObject的init{}->create-shadow创建对应的编译对象->构造函数被执行,进行变量绑定等工作。

         可见每个编译类都必须在初始化实例过程中调用其父类的初始化实例过程,并且最好先于当前类的初始化,以便绑定变量能够尽早被创建;对象的销毁过程不许在其最后一条语句显示调用父类的销毁过程(最终,调用delete-shadow,导致影像对象被析构)。但一般来说,如果没有其他特殊的事情要做,初始化实例过程可以省去。

    2.create-shadow如何知道创建编译对象所属类?

        通过C++类TclClass来解决。如

    static class RenoTcpClass:public TclClass {
    public:
        RenoTcpClass():TclClass("Agent/TCP/Reno") {}                        //调用基类构造函数,登记申明所属Otcl类 Agent/TCL/Reno
        TclObject* create(int argc,const char*const* argv) {         //每当创建解释对象Agent/TCL/Reno时,creat-shadow就会来调用RenoTcpClass的
            return (new RenoTcpAgent());                                           //create函数,创建出正确的影像对象,并返回了其指针
            }
    }

    3.在解释对象的成员变量和编译对象的成员变量间建立双向的连接(不适用于静态成员变量的绑定)

         变量绑定——通常在编译对象初始化时在编译对象的构造函数中建立,在解释对象中作为实例变量来访问。大多数绑定变量的初始值在~ns/tcl/lib/ns-default.tcl中定义。

    4.在Otcl对象中调用对应的C++对象的方法

         对于每个TclObject,ns为其Otcl中的解释对象建立一个实例过程cmd{},cmd{}根据保存的编译对象的指针调用其方法command(),并将cmd的参数作为一个参数数组传递给command。两种方法调用cmd{}:显式和隐式,如:

          $mobilenode setdest <X><Y><speed> 等价于 $mobilenode cmd setdest <X><Y><speed>
         即如果setdest{}不存在->调用基类过程unknown{}->调用cmd{}。argv[0]为cmd,argv[1]为操作名称,argv[2...(argc-1)]其他参数。参数作为字符串传递,须转化为适当的数据类型。

        我们把通过command()执行的操作叫做instproc-likes。command()必须返回TCL_OK或TCL_ERROR表明操作成功或失败。command()可以调用父类的command()。

    《NS与网路模拟》学习1——TCL和OTCL语言

         Tcl是Tool Command Language的缩写。包括两个部分:一种脚本语言和相应的解释器(能方便地向应用程序中添加)。TCL只支持字符串这种数据结构,命令基本语法为:Command arg1 arg2 arg3...。用换行或者分号结束一条命令,用#或;#来注释。

         组合和替代是TCL中的重要语法,Tcl解释器在解析命令参数前,会从左到右遍历参数,首先进行组合,遇到需要替换的部分进行替代,最后才调用这个命令。替代包括变量替代($)、命令替代([])以及反斜杠替代(\);组合主要用来处理空格,包括花括号({})和双引号(""),其中花括号内不允许进行替代。程序中使用了花括号来组合条件表达式和命令体。TCL的各种命令参见书中的详细内容。

         OTCL是ObjectTCL,引入了类和对象的概念。

    1) 类和对象的定义:关键字Class。如Class Animal;Animal animal_1;

       可以通过info命令查看类和对象之间的关系。例如:info+class(查看类)、+instance(查看对象)、+superclass(查看父类)、+heritage(查看继承树)

    2) 成员变量和函数的定义:Otcl中定义成员函数采用关键字instproc,定义成员变量采用关键字instvar。Otcl中成员变量并不预先定义,在成员函数中定义再使用,因此,当其他成员函数使用该变量时需重新申明。如

    Animal instproc run {speed} {
       $self instvar speed_
       set speed_ $speed
       puts "Animal run with speed $speed_"
    }                           其中$self的含义和C++中的this类似

    3) 对象的初始化和销毁:采用init函数来进行初始化,destroy函数来完成析构过程。如

    Animal instproc init {args} {
         $self set speed_ 0
         eval $self next $args
    }

    Animal instproc destroy {} {
         puts "zap!"
         $self next
    }                            其中next函数的意义是父类中的同名函数

    4) 继承:用到superclass关键字。Otcl中成员函数和变量的属性均是public,可以被子类继承。Otcl中所有的类都继承自Object类或它的后代。

       Otcl中子类也可以重写父类的成员函数。Otcl中所有成员函数都是虚函数,并且子类的成员函数同样使用next关键字来调用父类被覆盖的函数。

    IMEP

        IMEP是Internet MANET Encapsulation Protocol的缩写,位于网络层。主要功能包括(翻译自IMEP draft):

        1)通过封装和聚合多个MANET控制分组(如路由分组、ACK、链路状态感知分组、“网络层”地址解析等)或一个大的IMEP消息,减少网络控制分组广播数目,从而提高网络性能。

        2)从多个独立协议中提炼出共同的功能到一个统一的公用的协议中(如链路状态感知、邻近路由器安全认证、一跳邻居广播、控制分组可靠性等)。

        包括以下几个部分:Message Aggregation(AGGR)、Network-layer Address Resolution(NARP)、Link/Connection Status Sense(LCSS)、Broadcast Reliability(REL)、Multipoint Relaying(MPR)以及Authentication(AUTH)。我们主要关注其中的LCSS功能,在ns的TORA仿真协议中,利用IMEP子层来感知链路状态。通过考察IMEP代码和TORA部分代码,总结IMEP在ns中的LCSS功能应用如下:

    1.在**agent.h中,加入IMEPAgent

      #include <imep/imep.h>
      class **agent: {
          ......
          imepAgent  *imepagent;      //a handle to the IMEP layer
          ......
      }

    2.在**agent.c中,初始化函数中加入imepagent=0;

    3.command()函数中加入例如下面的句子(imepagent需要通过imepRegister注册):

       else if(strcmp(argv[1],"imep-agent")==0) {
         imepagent = (imepAgent*)TclObject::lookup(argv[2]);
        if(imepagent == 0)
              return TCL_ERROR;
            imepagent->imepRegister((rtAgent*)this);
        return TCL_OK;
       }

    4.imep中与LCSS相关的部分函数:

      void
         imepAgent::imepRegister(rtAgent *rt) {
              rtagent_ = rt;
              assert(rtagent_);
         }

      其中rtAgent *rtagent_ //pointer to the "upper layer" routing agent,用来指向IMEP上我们自己的路由协议。

      与链路状态设置相关函数:

      下列函数由imep_hello_input或者imep_ack_input调用

      imepAgent::imepSetLinkBiStatus(nsaddr_t index){
           imepSetLinkInStatus(index);
           imepSetLinkOutStatus(index);
      }

      其中包含的两函数中都由以下语句:rtagent_->rtNotifyLinkUP(index)

      另外,通过调用imepagent->imepGetBiLinks(nblist,nbcnt)可以获得完整的邻居列表。

    5.因此,我们所要做的就是定义**agent中的函数rtNotifyLinkUP(index),将其作为路由协议的Hook(如tora_api.c中)。

    August 08

    在NS2中用GDB来调试

         我想对于用NS来仿真的人来说,很重要的一个问题就是调试,因为并没有像VC那样方便的集成的调试环境。《NS与网络模拟》的书中介绍了tcl debug和KDevelop调试的方法,这里主要介绍gdb调试的方法。因为偶个人第一次写ns代码调试用的就是gdb,感觉安装使用都很方便,这里简单介绍一下:

         1.重新运行cygwin的setup文件,选择gdb组件,下载安装。

         2.修改Makefile,添加调试信息,即CCOPT = -g//(后面可能还有其他参数,保留),其实就是在ns编译的时候gcc后面添加-g选项。

         3.重新编译NS2:make clean,make depend然后make

           我make的时候indep-utils\webtrace-conv\dex\proxytrac2any.cc出了声明错误,在main函数前添加extern int IsLittleEndian(void);extern void ToOtherEndian(TEntry *e);

         常用命令:

         进入gdb调试状态:$gdb ns

         运行脚本:<gdb>r file.tcl

         设置断点:<gdb>b file.cc:行数

         删除断点:<gdb>d b 断点编号(1,2,...)

         显示变量或函数值:<gdb>display var

         删除变量或函数值显示:<gdb>d d 变量编号

         单步执行:<gdb>n

         单步跳入:<gdb>s

         循环执行:<gdb>c

         列出运行栈的内容:<gdb>bt——主要针对遇到segment fault的情况

         退出调试:<gdb>q    

    Cygwin 1.5.24 + ns-allinone-2.29

          记得第一次安装NS2,就是在windows下,当时在师姐的指导下,下载安装了一堆东西(貌似当时没有用allinone),修改了环境变量什么的,还修正了一系列莫名其妙的错误才把ns艰难地装上。对于当时的我来说,很多操作都不知道为什么,更不用说记住了。于是,之后一直在Linux跑NS2,实在是害怕了在windows下装。现在,NS2的安装也越来越方便了,有allinone包不说,NS2.27之后的版本,只需要先安装Cygwin就很容易搞定了。基本过程都是参照官方网站上来做的,没有遇到什么太大问题,一些小细节而已~~

          用setup.exe安装Cygwin,注意以下几点:

          1.用UNIX text模式(缺省)而不要用DOS,检验命令:mount|grep textmode                                    

          2.保证Cygwin的安装目录中没有空格(缺省)

          3.login name中不要有空格(偶好像都木有设哎。。。。)

          4.nam需要安装X11,Xfree86或者X.org(xorg-x11-bin,xorg-x11-bin-dlls,xorg-x11-devel,xorg-x11,libs-data和xorg-x11-etc),这个版本的Cygwin是安装X.org

          5.为了安装ns2,还需要安装以下包:diffutils, gcc-g++, gawk, tar, gzip, make, patch, perl和w32api。另外,为了以后的调试工作,还需要安装gdb组件。

          之后ns的安装与linux下的差不多,下载,解压之后,输入命令./install,注意这里可能是由于环境变量设置等原因,可能会显示gdb未安装,不用理会,继续即可。安装完成后照例要修改环境变量,在~/.bashrc中修改。

    怎样使用NS自己的链表(zz from NS2 &SeaSon)

    1.头文件  #include<lib/bsd-list.h>
     
    2.初始化(宏定义)
    1) 在链表节点中添加LIST_ENTRY结构,包括了指向前驱和后继节点的指针。
    #define LIST_ENTRY(type)                                    
    struct {                                                      
        type *le_next;      /* next element */                    
        type **le_prev;     /* address of previous next element */    
    } 
     
        2) 定义一个链表头,链表节点类型为type,表头为lh_first
        #define LIST_HEAD(name, type)                                       
    struct name {                                                       
           type *lh_first;      /* first element */       
    }
     
    3) 初始化链表
    #define     LIST_INIT(head) {                                 
        (head)->lh_first = NULL;                               
    }
     
    4) 插入节点
    #define LIST_INSERT_AFTER(listelm, elm, field) {               
        if (((elm)->field.le_next = (listelm)->field.le_next) != NULL)     
               (listelm)->field.le_next->field.le_prev = &(elm)->field.le_next;                           
        (listelm)->field.le_next = (elm);                           
        (elm)->field.le_prev = &(listelm)->field.le_next;         
    }
     
    #define LIST_INSERT_BEFORE(listelm, elm, field) {                   
        (elm)->field.le_prev = (listelm)->field.le_prev;            
        (elm)->field.le_next = (listelm);                           
        *(listelm)->field.le_prev = (elm);                          
        (listelm)->field.le_prev = &(elm)->field.le_next;         
    }
     
    #define LIST_INSERT_HEAD(head, elm, field) {                           
        if (((elm)->field.le_next = (head)->lh_first) != NULL)         
               (head)->lh_first->field.le_prev = &(elm)->field.le_next;
        (head)->lh_first = (elm);                               
        (elm)->field.le_prev = &(head)->lh_first;                    
    }
     
    5) 删除节点
    #define LIST_REMOVE(elm, field) {                               
        if ((elm)->field.le_next != NULL)                          
               (elm)->field.le_next->field.le_prev = (elm)->field.le_prev;                       
        *(elm)->field.le_prev = (elm)->field.le_next;               
    }
     
    3一个例子
      1) 定义节点类型
    class MFlood_RTEntry {
        friend class MFlood_RTable;
        friend class MFlood;
    public:
        MFlood_RTEntry();
        MFlood_RTEntry(nsaddr_t src,u_int32_t seq);
        bool       isNewSeq(u_int32_t seq);    // old -> false, new->true
        void        addSeq(u_int32_t seq);       // add a seqno to seqno array(rt_seqnos)
    protected:
        LIST_ENTRY(MFlood_RTEntry) rt_link;
        nsaddr_t src_;
    //  u_int32_t seq_;
        u_int32_t              rt_seqnos[REM_SEQ_COUNT]; //seqno array
        u_int32_t       max_seqno;   //max seqno
        u_int32_t       min_seqno;   //max seqno
        u_int16_t              seq_it;    // seqno's iterator
    };
     
      2) 建立一个链表节点类型为MFlood_RTEntry的链表 rthead
      LIST_HEAD(, MFlood_RTEntry) rthead;
     
     3) 初始化链表rheadNULL
    LIST_INIT(&rthead)
     
    4) 怎样使用
    MFlood_RTEntry* 
    MFlood_Rtable::rt_lookup(nsaddr_t id) {
      Mflood_RTEntry *rt = rthead.lh_first;//获取链表表头
      for(; rt; rt = rt->rt_link.le_next) {
             if(rt->src_ == id)
                    break;
      }
      return rt;
    }
     
        5) 删除节点
    void
    MFlood_RTable::rt_delete(nsaddr_t id) {
      MFlood_RTEntry *rt = rt_lookup(id);
      if(rt) {
             LIST_REMOVE(rt, rt_link);
             delete rt;
      }
    }
        6) 插入节点
    rt = new MFlood_RTEntry(ih->saddr(), fh->seq_);
                  LIST_INSERT_HEAD(&rtable_.rthead,rt,rt_link);     
     
    双链表示意图:
    200704133081176442967641  
     

    关于NS2的学习

         学习NS2已经很久,从最初的不知道从哪里开始到现在能够仿真自己的协议,小小的进步。
         问题大大的有,经常在同一个问题上花费冤枉的时间,究其原因是因为偶的记性太差了!
         因此,想要在这里记录下,平时学习时经常遇到的问题的解答,以及用ns2过程中的一些想法与心得。。。