2009年3月25日星期三

关于const char*, char const*, char*const

关于const char*, char const*, char*const

const char*, char const*, char*const的区别问题几乎是C++面试中每次都会有的题目。

事实上这个概念谁都有,只是三种声明方式非常相似很容易记混。
Bjarne在他的The C++ Programming Language里面给出过一个助记的方法:
把一个声明从右向左读。

char * const cp; ( * 读成 pointer to )
cp is a const pointer to char

const char * p;
p is a pointer to const char;

char const * p;
同上因为C++里面没有const*的运算符,所以const只能属于前面的类型。


C++标准规定,const关键字放在类型或变量名之前等价的。

const int n=5; //same as below
int const m=10;


const int *p; //same as below const (int) * p
int const *q; // (int) const *p


char ** p1;
// pointer to pointer to char
const char **p2;
// pointer to pointer to const char
char * const * p3;
// pointer to const pointer to char
const char * const * p4;
// pointer to const pointer to const char
char ** const p5;
// const pointer to pointer to char
const char ** const p6;
// const pointer to pointer to const char
char * const * const p7;
// const pointer to const pointer to char
const char * const * const p8;
// const pointer to const pointer to const char

说到这里,我们可以看一道以前Google的笔试题:

[题目]const char *p="hello";
foo(&p);//函数foo(const char **pp)
下面说法正确的是[]
A.函数foo()不能改变p指向的字符串内容
B.函数foo()不能使指针p指向malloc生成的地址
C.函数foo()可以使p指向新的字符串常量
D.函数foo()可以把p赋值为 NULL.

至于这道题的答案是众说纷纭。针对上面这道题,我们可以用下面的程序测试:


#include
#include
#include


void foo(const char **pp)
{
// *pp=NULL;
// *pp="Hello world!";
*pp = (char *) malloc(10);
snprintf(*pp, 10, "hi google!");
// (*pp)[1] = 'x';

}

int
main()
{
const char *p="hello";
printf("before foo %s\n",p);
foo(&p);
printf("after foo %s\n",p);
p[1] = 'x';

return;
}



结论如下:

1. 在foo函数中,可以使main函数中p指向的新的字符串常量。
2. 在foo函数中,可以使main函数中的p指向NULL。
3. 在foo函数中,可以使main函数中的p指向由malloc生成的内存块,并可以在main中用free释放,但是会有警告。但是注意,即使在foo中让p指向了由malloc生成的内存块,但是仍旧不能用p[1]='x';这样的语句改变p指向的内容。
4. 在foo中,不能用(*pp)[1]='x';这样的语句改变p的内容。

所以,感觉gcc只是根据const的字面的意思对其作了限制,即对于const char*p这样的指针,不管后来p实际指向malloc的内存或者常量的内存,均不能用p[1]='x'这样的语句改变其内容。但是很奇怪,在foo里面,对p指向malloc的内存后,可以用snprintf之类的函数修改其内容。

c++静态成员小结,c++,static(转)

类中的静态成员真是个让人爱恨交加的特性。我曾经在面试时,被主考官抓住这个问题一阵穷追猛打,直把我问的面红耳赤,败下阵来。所以回来之后,我痛定思痛,决定好好总结一下静态类成员的知识点,以便自己在以后面试中,在此类问题上不在被动。
静态类成员包括静态数据成员静态函数成员两部分。

一 静态数据成员:

类体中的数据成员的声明前加上static关键字,该数据成员就成为了该类的静态数据成员。和其他数据成员一样,静态数据成员也遵守public/protected/private访问规则。同时,静态数据成员还具有以下特点:

1.静态数据成员的定义。
静态数据成员实际上是类域中的全局变量。所以,静态数据成员的定义(初始化)不应该被放在头文件中。
其定义方式与全局变量相同。举例如下:

xxx.h文件
class base{
private:
static const int _i;//声明,标准c++支持有序类型在类体中初始化,但vc6不支持。
};

xxx.cpp文件
const int base::_i=10;//定义(初始化)时不受private和protected访问限制.

注:不要试图在头文件中定义(初始化)静态数据成员。在大多数的情况下,这样做会引起重复定义这样的错误。即使加上#ifndef #define #endif或者#pragma once也不行。

2.静态数据成员 的所有对象所共享,包括该类派生类的对象。即派生类对象与基类对象共享基类的静态数据成员。举例如下:
class base{
public :
static int _num;//声明
};
int base::_num=0;//静态数据成员的真正定义

class derived:public base{
};

main()
{
base a;
derived b;
a._num++;
cout<<"base class static data number _num is"< b._num++;
cout<<"derived class static data number _num is"< }
// 结果为1,2;可见派生类与基类共用一个静态数据成员。

3.静态数据成员可以成为成员函数的可选参数,而普通数据成员则不可以。举例如下:
class base{
public :
static int _staticVar;
int _var;
void foo1(int i=_staticVar);//正确,_staticVar为静态数据成员
void foo2(int i=_var);//错误,_var为普通数据成员
};

4.★静态数据成员的类型可以是所属类的类型,而普通数据成员则不可以。普通数据成员的只能声明为 所属类类型的 指针或引用。举例如下:

class base{
public :
static base _object1;//正确,静态数据成员
base _object2;//错误
base *pObject;//正确,指针
base &mObject;//正确,引用
};

5.★这个特性,我不知道是属于标准c++中的特性,还是vc6自己的特性。
静态数据成员的值在const成员函数中可以被合法的改变。举例如下:

class base{
public:
base(){_i=0;_val=0;}

mutable int _i;
static int _staticVal;
int _val;
void test() const{//const 成员函数

_i++;//正确,mutable数据成员
_staticVal++;//正确,static数据成员
_val++;//错误

}
};
int base::_staticVal=0;

二,静态成员函数
静态成员函数没有什么太多好讲的。

1.静态成员函数的地址可用普通函数指针储存,而普通成员函数地址需要用 类成员函数指针来储存。举例如下:
class base{
static int func1();
int func2();
};

int (*pf1)()=&base::func1;//普通的函数指针
int (base::*pf2)()=&base::func2;//成员函数指针


2.静态成员函数不可以调用类的非静态成员。因为静态成员函数不含this指针。

3.静态成员函数不可以同时声明为 virtual、const、volatile函数。举例如下:
class base{
virtual static void func1();//错误
static void func2() const;//错误
static void func3() volatile;//错误
};


最后要说的一点是,静态成员是可以独立访问的,也就是说,无须创建任何对象实例就可以访问。

(from: http://blog.csdn.net/sailing0123/archive/2007/07/12/1686269.aspx)

C++中三个修饰符的深层剖析:Static, Const, Inline

C++中三个修饰符的深层剖析Static Const Inline



  static 是c++中很常用的修饰符,它被用来控制变量的存储方式和可见性,下面我将从 static 修饰符的产生原因、作用谈起,全面分析static 修饰符的实质。

  static 的两大作用:

  一、控制存储方式:

  static被引入以告知编译器,将变量存储在程序的静态存储区而非栈上空间。

  1、引出原因:函数内部定义的变量,在程序执行到它的定义处时,编译器为它在栈上分配空间,大家知道,函数在栈上分配的空间在此函数执行结束时会释放掉,这样就产生了一个问题: 如果想将函数中此变量的值保存至下一次调用时,如何实现?

  最容易想到的方法是定义一个全局的变量,但定义为一个全局变量有许多缺点,最明显的缺点是破坏了此变量的访问范围(使得在此函数中定义的变量,不仅仅受此函数控制)。

  2、 解决方案:因此c++ 中引入了static,用它来修饰变量,它能够指示编译器将此变量在程序的静态存储区分配空间保存,这样即实现了目的,又使得此变量的存取范围不变。

  二、控制可见性与连接类型 :

  static还有一个作用,它会把变量的可见范围限制在编译单元中,使它成为一个内部连接,这时,它的反义词为”extern”.

  static作用分析总结:static总是使得变量或对象的存储形式变成静态存储,连接方式变成内部连接,对于局部变量(已经是内部连接了),它仅改变其存储方式;对于全局变量(已经是静态存储了),它仅改变其连接类型。

  类中的static成员:

  一、出现原因及作用:

  1、需要在一个类的各个对象间交互,即需要一个数据对象为整个类而非某个对象服务。

  2、同时又力求不破坏类的封装性,即要求此成员隐藏在类的内部,对外不可见。

  类的static成员满足了上述的要求,因为它具有如下特征:有独立的存储区,属于整个类。

  二、注意:

  1、对于静态的数据成员,连接器会保证它拥有一个单一的外部定义。静态数据成员按定义出现的先后顺序依次初始化,注意静态成员嵌套时,要保证所嵌套的成员已经初始化了。消除时的顺序是初始化的反顺序。

  2、类的静态成员函数是属于整个类而非类的对象,所以它没有this指针,这就导致了它仅能访问类的静态数据和静态成员函数。

   const 是c++中常用的类型修饰符,但我在工作中发现,许多人使用它仅仅是想当然尔,这样,有时也会用对,但在某些微妙的场合,可就没那么幸运了,究其实质原 由,大多因为没有搞清本源。故在本篇中我将对const进行辨析。溯其本源,究其实质,希望能对大家理解const有所帮助,根据思维的承接关系,分为如 下几个部分进行阐述。

  c++中为什么会引入const

  c++的提出者当初是基于什么样的目的引入(或者说保留)const关键字呢?,这是一个有趣又有益的话题,对理解const很有帮助。

  1. 大家知道,c++有一个类型严格的编译系统,这使得c++程序的错误在编译阶段即可发现许多,从而使得出错率大为减少,因此,也成为了c++与c相比,有着突出优点的一个方面。

  2. c中很常见的预处理指令 #define variablename variablevalue 可以很方便地进行值替代,这种值替代至少在三个方面优点突出:

  一是避免了意义模糊的数字出现,使得程序语义流畅清晰,如下例:
  #define user_num_max 107 这样就避免了直接使用107带来的困惑。

  二是可以很方便地进行参数的调整与修改,如上例,当人数由107变为201时,进改动此处即可,

  三是提高了程序的执行效率,由于使用了预编译器进行值替代,并不需要为这些常量分配存储空间,所以执行的效率较高。

  鉴于以上的优点,这种预定义指令的使用在程序中随处可见。

  3. 说到这里,大家可能会迷惑上述的1点、2点与const有什么关系呢?,好,请接着向下看:

  预处理语句虽然有以上的许多优点,但它有个比较致命的缺点,即,预处理语句仅仅只是简单值替代,缺乏类型的检测机制。这样预处理语句就不能享受c++严格类型检查的好处,从而可能成为引发一系列错误的隐患。

  4.好了,第一阶段结论出来了:

  结论: const 推出的初始目的,正是为了取代预编译指令,消除它的缺点,同时继承它的优点。

  现在它的形式变成了:

const datatype variablename = variablevalue ;

  为什么const能很好地取代预定义语句?

  const 到底有什么大神通,使它可以振臂一挥取代预定义语句呢?

  1. 首先,以const 修饰的常量值,具有不可变性,这是它能取代预定义语句的基础。

  2. 第二,很明显,它也同样可以避免意义模糊的数字出现,同样可以很方便地进行参数的调整和修改。

   3. 第三,c++的编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作, 使得它的效率也很高,同时,这也是它取代预定义语句的重要基础。这里,我要提一下,为什么说这一点是也是它能取代预定义语句的基础,这是因为,编译器不会 去读存储的内容,如果编译器为const分配了存储空间,它就不能够成为一个编译期间的常量了。

  4. 最后,const定义也像一个普通的变量定义一样,它会由编译器对它进行类型的检测,消除了预定义语句的隐患。

  const 使用情况分类详析

  1.const 用于指针的两种情况分析:

  int const *a;  file://a可变,*a不可变
  int *const a;  file://a不可变,*a可变

  分析:const 是一个左结合的类型修饰符,它与其左侧的类型修饰符和为一个类型修饰符,所以,int const 限定 *a,不限定a。int *const 限定a,不限定*a。

  2.const 限定函数的传递值参数:

  void fun(const int var);

  分析:上述写法限定参数在函数体中不可被改变。由值传递的特点可知,var在函数体中的改变不会影响到函数外部。所以,此限定与函数的使用者无关,仅与函数的编写者有关。

  结论:最好在函数的内部进行限定,对外部调用者屏蔽,以免引起困惑。如可改写如下:

  void fun(int var){
  const int & varalias = var;

  varalias ....

  .....

  }

  3.const 限定函数的值型返回值:

  const int fun1();

  const myclass fun2();

   分析:上述写法限定函数的返回值不可被更新,当函数返回内部的类型时(如fun1),已经是一个数值,当然不可被赋值更新,所以,此时const无意 义,最好去掉,以免困惑。当函数返回自定义的类型时(如fun2),这个类型仍然包含可以被赋值的变量成员,所以,此时有意义。

  4. 传递与返回地址: 此种情况最为常见,由地址变量的特点可知,适当使用const,意义昭然。

  5. const 限定类的成员函数:

  class classname {

  public:

  int fun() const;

  .....

  }

  注意:采用此种const 后置的形式是一种规定,亦为了不引起混淆。在此函数的声明中和定义中均要使用const,因为const已经成为类型信息的一部分。

  获得能力:可以操作常量对象。

  失去能力:不能修改类的数据成员,不能在函数中调用其他不是const的函数。

   在本文中,const方面的知识我讲的不多,因为我不想把它变成一本c++的教科书。我只是想详细地阐述它的实质和用处. 我会尽量说的很详细,因为我希望在一种很轻松随意的气氛中说出自己的某些想法,毕竟,编程也是轻松,快乐人生的一部分。有时候,你会惊叹这其中的世界原来 是如此的精美。
  在前面谈了const后,现在再来谈一下inline这个关键字,之所以把这个问题放在这个位置,是因为inline这个关键字的引入原因和const十分相似,下面分为如下几个部分进行阐述。

  c++中引入inline关键字的原因:

  inline 关键字用来定义一个类的内联函数,引入它的主要原因是用它替代c中表达式形式的宏定义。

  表达式形式的宏定义一例:

   #define expressionname(var1,var2) (var1+var2)*(var1-var2)

  为什么要取代这种形式呢,且听我道来:

  1. 首先谈一下在c中使用这种形式宏定义的原因,c语言是一个效率很高的语言,这种宏定义在形式及使用上像一个函数,但它使用预处理器实现,没有了参数压栈,代码生成等一系列的操作,因此,效率很高,这是它在c中被使用的一个主要原因。

   2. 这种宏定义在形式上类似于一个函数,但在使用它时,仅仅只是做预处理器符号表中的简单替换,因此它不能进行参数有效性的检测,也就不能享受c++编译器严 格类型检查的好处,另外它的返回值也不能被强制转换为可转换的合适的类型,这样,它的使用就存在着一系列的隐患和局限性。

  3. 在c++中引入了类及类的访问控制,这样,如果一个操作或者说一个表达式涉及到类的保护成员或私有成员,你就不可能使用这种宏定义来实现(因为无法将this指针放在合适的位置)。

  4. inline 推出的目的,也正是为了取代这种表达式形式的宏定义,它消除了它的缺点,同时又很好地继承了它的优点。

  为什么inline能很好地取代表达式形式的预定义呢?

  对应于上面的1-3点,阐述如下:

  1. inline 定义的类的内联函数,函数的代码被放入符号表中,在使用时直接进行替换,(像宏一样展开),没有了调用的开销,效率也很高。

  2. 很明显,类的内联函数也是一个真正的函数,编译器在调用一个内联函数时,会首先检查它的参数的类型,保证调用正确。然后进行一系列的相关检查,就像对待任何一个真正的函数一样。这样就消除了它的隐患和局限性。

  3. inline 可以作为某个类的成员函数,当然就可以在其中使用所在类的保护成员及私有成员。
在何时使用inline函数:

  首先,你可以使用inline函数完全取代表达式形式的宏定义。

  另外要注意,内联函数一般只会用在函数内容非常简单的时候,这是因为,内联函数的代码会在任何调用它的地方展开,如果函数太复杂,代码膨胀带来的恶果很可能会大于效率的提高带来的益处。 内联函数最重要的使用地方是用于类的存取函数。

  如何使用类的inline函数:

  简单提一下inline 的使用吧:

  1.在类中定义这种函数:

  class classname{

  .....

  ....

  getwidth(){return m_lpicwidth;}; // 如果在类中直接定义,可以不使用inline修饰

  ....

  ....

  }

  2.在类中声明,在类外定义:

  class classname{

  .....

  ....

  getwidth(); // 如果在类中直接定义,可以不使用inline修饰

  ....

  ....

  }

  inline getwidth(){

  return m_lpicwidth;

  }

  在本文中,谈了一种特殊的函数,类的inline函数,它的源起和特点在某种说法上与const很类似,可以与const搭配起来看。另外,最近有许多朋友与我mail交往,给我谈论了许多问题,给了我很多启发,在此表示感谢。

trace format


(from ns by example)

2009年3月24日星期二

Class Hierarchy (Partial)

OTcl类-结构、定义、继承

关键字

Class--创建一个类(object class)

instproc--定义类的成员函数

instvar--定义类的成员变量,首先检查变量在此类和父类中已声明(declare),是则引用(reference),否则声明

-superclass--类继承,父类

$self——相当于C++中的this指针

new--创建一个类对象(object instance)


示例:

# add a member function call "great"
Class mom
mom instproc greet {} {
$self instvar age_
puts "$age_ year old mom say:
How are you doing?"
}

# Create a child class of "mom" called "kid"
#and overide the member function "greet"
Class kid -superclass mom
kid instproc greet {} {
$self instvar age_
puts "$age_ year old kid say:
What's up, dude?"
}

#Create a mom and a kid object, set each age
set a [new mom]
$a set age_ 45
set b [new kid]
$b set age_ 15


#Calling member function "greet" of each object
$a greet
$b greet

#end

(参考ns by example ex-otcl.tcl)

GOD--General Operations Director

GOD

(from ns Tutorial)

The General Operations Director (GOD) object is used to store global information about the state of the environment, network, or nodes that an omniscent observer would have, but that should not be made known to any participant in the simulation.

Currently, the god object is used only to store an array of the shortest number of hops required to reach from one node to an other. The god object does not calculate this on the fly during simulation runs, since it can be quite time consuming. The information is loaded into the god object from the movement pattern file where lines of the form

$ns_ at 899.642 "$god_ set-dist 23 46 2"
are used to load the god object with the knowledge that the shortest path between node 23 and node 46 changed to 2 hops at time 899.642.

2009年3月18日星期三

NS-2 Trace Formats

NS-2 Trace Formats


This document lists various trace formats used by the NS-2 Network Simulator. The information is based on NS2 version 2.1b9a. The best effort has been made to document correctly all of the trace formats, but be warned that this list is not complete, and may contain errors. If you find any errors or omissions, please edit this page to fix the problems.

The various traces begin with a single character or abbreviation that indicates the type of trace, followed by a fixed or variable trace format. The tables listing the trace formats differ between fixed and variable trace formats:

  • For fixed trace formats, the table lists the event the triggers the trace under the Event heading and the characters that start the trace under the Abbreviation heading. The format is listed across the last two columns, and the the type and value for each element of the format are listed beneath under the Type and Value headings. Some events have multiple trace formats.
  • For variable trace formats, the table lists the event the triggers the trace under the Event heading and the characters that start the trace under the Abbreviation heading. The last three columns list the possible flags, types, and values for the event under the Flag, Type, and Value headings.

Normal trace formats

This information comes from "The ns Manual" "Trace and Monitoring Support: Trace File Format" chapter. This trace is used normal wired operations. The trace starts with one of four possible characters.

The tables that list the additional wireless trace information do not have an Abbreviation column, since the information is appended to the end of the regular wireless trace format.

Event Abbreviation Type Value
Normal Event r: Receive
d: Drop
e: Error
+: Enqueue
-: Dequeue
%g %d %d %s %d %s %d %d.%d %d.%d %d %d
double Time
int Source Node
int Destination Node
string Packet Name
int Packet Size
string Flags
int Flow ID
int Source Address
int Source Port
int Destination Address
int Destination Port
int Sequence Number
int Unique Packet ID

The flags are read as follow (the "value" is displayed instead of `-` if flag is set). Each row is one of the "slots", from left to right. From from ns/trace/trace.cc.

ValueMeaning
C ECN-echo
P pri_ (supposedly unused)
-
A Congestion Action
E Congestion Experienced (CE)
F Fast Start
N ECN-capable
SCTP-only

Depending on the packet type, the trace may log additional information:

Event Type Value
TCP Trace %d 0x%x %d %d
int Ack Number
hexadecimalFlags
int Header Length
int Socket Address Length
Satellite Trace %.2f %.2f %.2f %.2f
double Source Latitude
double Source Longitude
double Destination Latitude
double Destination Longitude

Wireless Trace Formats

This section covers the various wireless trace format:

Old Wireless Trace Formats

This information comes from "The ns Manual" "Mobile Networking in ns: Trace Support" chapter, and the "trace/cmu-trace.cc" file. Wireless traces begin with one of four characters followed by one of two different trace formats, depending on whether the trace logs the X and Y coordinates of the mobile node.

Event Abbreviation Type Value
Wireless Event s: Send
r: Receive
d: Drop
f: Forward
%.9f %d (%6.2f %6.2f) %3s %4s %d %s %d [%x %x %x %x]
%.9f _%d_ %3s %4s %d %s %d [%x %x %x %x]
double Time
int Node ID
double X Coordinate (If Logging Position)
double Y Coordinate (If Logging Position)
string Trace Name
string Reason
int Event Identifier
string Packet Type
int Packet Size
hexadecimalTime To Send Data
hexadecimalDestination MAC Address
hexadecimalSource MAC Address
hexadecimalType (ARP, IP)

Some older versions of NS2 (such as 2.1b5) have five hexidecimal values between the square braces. The first hexidecimal value is the MAC frame control information, and the remaining hexidecimal values are the same as listed above.

Depending on the packet type, the trace may log additional information:

Event Type Value
ARP Trace ------- [%s %d/%d %d/%d]
string Request or Reply
int Source MAC Address
int Source Address
int Destination MAC Address
int Destination Address
IP Trace ------- [%d:%d %d:%d %d %d]
int Source IP Address
int Source Port
int Destination IP Address
int Destination Port Address
int TTL
int Next hop node (or 0 if not valid)
DSR Trace %d [%d %d] [%d %d %d %d->%d] [%d %d %d %d-

无线网络仿真Trace 旧格式

An example of a trace for a tcp packet is as follows:西子博客_$OOV"@*IQ

r 160.093884945_6_RTR---5 tcp 1492[a2 4 6 800]-------[65536:0 16777984:0 31 16777984][1 0]2 0ldN xs,x-LM0

7PQa F3NE0Here we see a TCP data packet being received by a node with id of 6.UID of this pkt is 5 with a cmn hdr size of 1492.The mac details shows an IP pkt(ETHERTYPE_IP is defined as 0x0800,ETHERTYPE_ARP is 0x0806),mac-id of this receiving node is 4.That of the sending node is 6 and expected time to send this data pkt over the wireless channel is a2(hex2dec conversion:160+2 sec).Additionally,IP traces information about IP src and destination addresses.The src translates(using a 3 level hier-address of 8/8/8)to a address string of 0.1.0 with port of 0.The dest address is 1.0.3 with port address of 0.The TTL value is 31 and the destination was a hop away from the src.Additionally TCP format prints information about tcp seqno of 1,ackno of 0.See other formats described in~ns//cmu-trace.cc for DSR,UDP/MESSAGE,TCP/ACK and CBR packet types.西子博客%PV:JlG+m}_R

西子博客%a,T Lgu*tOther trace formats are also used by the routing agents(TORA and DSR)to log certain special routing events like"originating"(adding a SR header to a packet)or"ran off the end of a source route"indicating some sort of routing problem with the source西子博客.~&G$z1~Cw"l
route etc.These special event traces begin with"S"for DSR and"T"for Tora and may be found in~ns/tora/tora.cc for TORA and~ns/dsr/dsrgent.cc for DSR routing agent.

(from: http://blog.hz0752.com/?uid-15519-action-viewspace-itemid-163973)

2009年3月2日星期一

Ad-Hoc Routing Protocols (Characteristics)

Ad-Hoc Routing Protocols (Characteristics)
Classification of the Characteristics of Ad-Hoc Protocols and Routing-StrategiesExtension of Lang2003 and Murthy2004.

In general, routing can be divided into two strategies:Adaptive Routing vs. Not-Adaptive Routing
Adaptive: Changes of the network-topology are adapted by the routing-strategy
Not-Adaptive: The routing is done using fixed tables
For Ad-Hoc-Netorks, only adaptiv strategies are usefull.Reactive Routing/On-Demand Routing vs. Proactive Routing/Table-Driven Routing vs. Hybrid Routing
Reactive/On-Demand
A route is only calculated, when it is needed
Does not try to keep routing-information everytime to all node
Proactive/Table-Driven
Routes are calculated before one is needed
Tries to keep routing-information to all nodes everytime up-to-date
Update of the tables:
Event-driven: only if a change is recoginzed
Periodically
Hybrid
Reactive and Proactive at the same time
E.g.: Intra-Zone: Proactiv, Inter-Zone: Reactiv
Distance-Vector Routing vs. Link-State Routing
Distance-Vector
Calculates the distance to all nodes
Exchange of these information only with the neighbours
Link-State
Mesuare the distance to the neighbours
Exchange of these information with all nodes
Flat Routing vs. Hierarchical Routing / Clustered Routing
Hierarchical Routing / Clustered Routing:
Trying to structure/cluster the network
Clusterhead:
Responsible for the creation and extension of a cluster
Builds up a hierarchie of clusters
Manages the communication inside a cluster
Gateway-Node:
Responsible for the communication between clusters
Maybe bottleneck
Flat Routing:
Network has no hierarchy
Geographical Routing / Positionbased Routing / Direction-Based Routing
No routing-tables
Information is send in any way in the direction of the destination
No overhead to find or update routes But:
Position required
Determination of the position via
internal search-process
external service
Uniform vs. Non-Uniform
Uniform: All nodes are equal
Non-Uniform: Some nodes have special roles, e.g. Clusterhead, Gateway-Node
Full vs. Reduced Topology Information
Full: All topology-information will be distributed
Reduced: Only a fraction of the known topology-information will be distributed
Past History vs. Prediction
Past History: Information of past statuses is used to make a decision
Prediction: Expectation of future statuses are used to make routing decisions
Broadcast
Local Multicast: Only to some nodes in transmission-distance
Local Broadcast: Only to the nodes in transmission-distance
Networkwide Broadcast: Flooding
Restricted Networkwide Broadcast: Flooding with a time-to-live
Recovery Strategy
Mechansims to keep or to restore routes
Link-Reversal Routing
Does not try to find an (somehow) optimal way
Only tries to find any way
Source-Routing
The sender specifies the way to go
Under the circumstances, a node inbetween can decide to redefine the way
Route-Selection-Strategies
Power-Aware Routing
Signal Strength
Link Stability
Shortest Path
Link-State Routing / Distance-Vector Routing
Direction-Based Routing / Positionbased Routing / Geographical Routing
Link-Reversal Routing
Multipath Routing "Ad-Hoc Protocols (Characteristics)" wird erwähnt auf: Ad-Hoc Protocols

理解NS2中的OTcl/tclCL

from:http://stevieliu.bokee.com/3247914.html
题记
真正触动我写这篇短文的原因是试图理解NS2的基本原理. 在"the NS2 manual"中, 解释了为什么采用了两种语言来建构整个系统, 然后在第三章描述了tclcl的六个类. 这个手册中的对各个类描述性文字让我如坠雾里, 不明所以. 我查找了一些NS2的文章和站点, 有一些ppt倒是很形象, 但我的认识上总有些模糊. 后来, 我逐渐明白到OTcl/Tcl的嵌入特性. --- 这才是理解NS2框架的关键.

Abstract
本文的主要目的是理解NS2的architecture, 了解NS2的基本原理. NS2采用了Tcl/C++分裂的模型, 在这种模型中OTcl是处于比较关键的位置, NS2采用了Tcl的编程模式. 使用C++来编写应用实例, 使用OTcl来操纵这些实例. 理解了OTcl就理解了NS2的框架. 本文先简述Tcl语言的嵌入特性, 然后描述了NS2的应用场景, 进而分析NS2的架构, 以及实现该架构采用的技术.

Introduction
NS2是MIT的一个作品, 它是一个面向对象的网络仿真工具. 使用NS2可以完整的仿真整个网络环境, 只要你的机器足够快 :-) NS2使用一整套C++类库实现了大多数常见的网络协议以及链路层的模型, 使用这些类的实例我们就可以搭建起整个网络的模型, 而且包括了各个细节. --- 这简直就是一种梦想的实现, 试想如果手头能有这样一个工具, 我们就可以在单机环境中模拟网络的各个元素, 加深对网络的了解和认识; 同时, 加快我们开发新协议的速度.
与NS2类似的软件有OPNET, 这是一个商用的网络仿真软件, 据说它能够针对各款交换机和路由器来搭建网络, 够牛x. 与之相比, NS2是一个免费的软件, 它可以在Windows/Unix上运行, 我们可以看到NS2的所有源代码, 另外在学术界更多的是采用NS2来做仿真.
NS2采用了Tcl/C++分裂的模型来建构它的应用, 这样做的好处是显而易见的. 使用Tcl语言我们可以灵活的配置网络环境, 定制系统; 而采用C++来编程满足了我们对仿真效率的需要. 缺点也是明了的, 要同时维护两套代码, 对使用者要求较高.
NS2的Tcl/C++架构与Windows下的COM/VBScript编程模式有些类似, 使用VC来编写和实现COM对象, 然后使用VB来操纵COM对象. Windows提供了COM接口, 这就在系统范围内保证了这种机制的有效性. --- 这就是Windows的高明之处. 与之相比, NS2则能够使Tcl脚本解到它的C++类库结构, 同时按照它的类分级来创建对象. --- 这也很了不起.
要使用NS2来仿真开发一个新协议, 就必须对NS2的类库进行某些扩展. 撇开各个协议或链路的细节不谈, 对NS2的实现机制的了解是一个关键, 否则难免会疏漏百出. 如果不了解NS2的机制, 在刚开始开发协议时, 你看着NS2的代码可能会感觉到无处下手.
NS2的手册中对它的机制和原理主要在"the NS manual"一书的第三章, 但是这一章的内容写来就象tclcl类的简单介绍, 读来非常费解. 它对NS2的整体设计思路并没有交待的很清楚, 本文就打算解析这第三章背后的话.
以下的内容安排如下, 首先简单介绍Tcl语言的嵌入特性, 然后描述NS2的应用场景, 分析NS2的框架, 然后考察实现这个NS2框架所遇到的问题. 最后以一个新协议的添加作为例子, 感受NS2的仿真开发过程.

嵌入的Tcl
OTcl称为Objected Tcl, 它是在Tcl基础上的一个面向对象的封装. Tcl语言本身比较简单, 在NS2的主页上有一些相关的链接, 可以快速了解它的基本语法, 这里对Tcl的语法并不感兴趣.
Tcl是一种脚本语言, Tool Cammand Language. Tcl语言的吸引人之处在于它简单同时支持嵌入式应用. 大家都用过Ms Word或Emacs, 这两种编辑器之所以如此强大, 很大的原因是因为它们内嵌了VB或Lisp语言. 这些内嵌的脚本能够定制编译器的环境, 使应用变的非常灵活.
你可能还听说过Windows下的脚本引擎, 把一个脚本引擎嵌入到Windows应用中, 就可以使这个应用具有类似Word的这种能力. 在Unix下类似的脚本语言嵌入很早就有, 上面提到的Emacs就是一个例子. Tcl语言也支持内嵌, 具体做法是把Tcl的库链接到应用程序中去, 从而使应用具有解释Tcl语言的能力. 当然仅仅这么做是不够的, 你还要为这个应用定制开发一些Tcl命令.
(NS2为什么不使用Lisp作为内嵌? 这可能与OTcl语言本身也是MIT的作品有关, 而且是MIT的近期作品. 但是如果采用Lisp的话, 就可以在Emacs做这个仿真了, 呵呵, 想想都要偷笑了, 可惜呀. 毕竟我是个Emacs的拥护者.)
Figure 1是Tcl C与应用程序集成的原理图. Figure 1中黑线表示C的调用, 红线表示Tcl脚本的调用. 为了使应用能够解释Tcl语言, 必须在应用程序的源代码中嵌入Tcl C库, 这个库的主要目的是实现一个Tcl语言的Parser, 并且实现了Tcl语言的一些关键命令, 如set, while, proc等. 应用程序必须还要编写一些针对应用扩展的Tcl命令, 然后注册进Tcl C库, 同时, 应用程序可以使用Tcl_LinkVar使某些C变量与Tcl库中的环境变量绑定起来.

Tcl C库的一些函数接口如下:
Tcl_CreateInterp 创建Tcl的Parser;
Tcl_CreateCommand/Tcl_CreateObjCommand 注册应用相关的命令;
Tcl_Eval 执行一条Tcl命令;
Tcl_LinkVar 将应用程序中的变量与Tcl库环境中的变量绑定;
Tcl_GetVar/Tcl_SetVar 设定/获取Tcl库环境中的变量;
下面是一个取自文献2的例子: /*
* Example 47-1
* The initialization procedure for a loadable package.
*/
/*
* random.c
*/
#include
/*
* Declarations for application-specific command procedures
*/
int RandomCmd(ClientData clientData,
Tcl_Interp *interp,
int argc, char *argv[]);
int RandomObjCmd(ClientData clientData,
Tcl_Interp *interp,
int objc, Tcl_Obj *CONST objv[]);
/*
* Random_Init is called when the package is loaded.
*/
int Random_Init(Tcl_Interp *interp) {
/*
* Initialize the stub table interface, which is
* described in Chapter 46.
*/
if (Tcl_InitStubs(interp, "8.1", 0) == NULL) {
return TCL_ERROR;
}
/*
* Register two variations of random.
* The orandom command uses the object interface.
*/
Tcl_CreateCommand(interp, "random", RandomCmd,
(ClientData)NULL, (Tcl_CmdDeleteProc *)NULL);
Tcl_CreateObjCommand(interp, "orandom", RandomObjCmd,
(ClientData)NULL, (Tcl_CmdDeleteProc *)NULL);
/*
* Declare that we implement the random package
* so scripts that do "package require random"
* can load the library automatically.
*/
Tcl_PkgProvide(interp, "random", "1.1");
return TCL_OK;
}
这个例子完整的体现了如何初始化Tcl C库, 如何向Tcl库中注册命令. 这里不打算继续讨论Tcl C库的详细问题了. 毕竟, 我们的目的是为了理解Tcl的嵌入能力, 上面的原理图已经足够.
NS2采用的是OTcl来实现它的脚本语言内嵌, 原因是NS2还有一套C++类库. 这个C++类库实现了网络仿真的各个元素, 仅仅是Tcl来操纵这套类库有些困难. 这关系到NS2的实现, 我们在后面谈.

OTcl的语法设计
这一部分的内容主要取自文献1, 放在这里的目的有两个: 一是为了了解OTcl的面向对象扩展, 从而能够方便的理解一些NS2的代码; 二是理解OTcl的实现原理.
文献1中主要介绍的是对Tcl语言本身的面向对象扩展, 它没有讲述实现如何操纵一个C++的对象. 从代码上来看OTcl本身好象并没有实现对C++对象的操纵.
OTcl和tclCL的相关文献都比较少, 这也许就是对NS2诟病较多的原因. OTcl是MIT的一个流媒体项目VuSystem的辅产品, 它并不是最早提出的Object概念的Tcl, 文献1认为OTcl的特点是Dynamic, 能够动态地创建一个对象.
OTcl的语法扩展
对OTcl的语法描述, 更详细的见网页45678.
OTcl的语言设计采用了称为"Object Command"的方法. 每个命令可能被解释成对象, 而子命令被解释成传递给对象的消息. 文献1中认为这样做可以比较方便的实现Tk的消息机制达到某种一致, 同时对象作为Tcl语言中array, list, proc等要素的补充, 这种扩展显得比较自然.
下面是OTcl一个一段示例代码, 我们可以看到对象astack的子命令set, proc等作为消息传递给对象. 而且要注意它确实是动态的, 在代码解释过程中动态的添加属性和方法. Object astack
astack set things {}
astack proc put {thing} {
$self instvar things
set things [concat [list $thing] $things]
return $thing
}
astack proc get {} {
$self instvar things
set top [lindex $things 0]
set things [lrang $things 1 end]
return $top
}
astack put toast ==> toast
astack get ==> toast
astack destroy ==> {}
注意上面的instvar方法, 代码的前面用set定义了一个things的变量, 在方法内要操纵它必须使用instvar来声明, 有些怪, 是不是? 否则things将是一个方法内的局部变量. 下表是OTcl对象的方法:
name
description
class
创建一个对象
destroy
销毁一个对象
proc
定义Tcl对象方法
set
定义Tcl对象变量
instvar
绑定实例变量
info procs
列出Tcl对象的所有方法
info args
列出Tcl对象方法的参数格式
info body
列出Tcl对象方法的函数主体
info commands
列出Tcl/C的命令
info vars
列出Tcl变量
inof class
获取class名
在文献1中提到上面方法的语境(context)如下表. 这个语境感觉好象是专门用来显示的指出对象方法的作用域. --- 如果是这样的话, OTcl的名字空间管理好象有些问题.
name
description
type
self
对象名
变量
proc
方法名
变量
class
定义的类型方法
变量
next
下一个影子方法
方法
$self类似于C++类中的this指针, $proc给出方法名, $next是指父类的同名方法, 就是C++中的函数重载, 这关系到OTcl对象的多继承机制.
OTcl的语法的进一步解释45678
在OTcl中, 类(Class)和对象(objects)是区分开来的. 类表示一种类型, 对象是类的实例. 在OTcl中, 类可以看成是一种特殊的对象. 类标志对象的类名, 类中可以放置所有对象共享的变量, 类似于C++中的静态变量.
OTcl的基类称为Object, 注意它可不是前面所说的对象. 所有的类都是从Object派生而来.
OTcl的属性都是C++意义上的public的.
instproc用来定义类的方法, 而proc用来定义对象方法. 后者定义的方法只能用于该对象.
unset用来undefine一个变量.
OTcl中, 类的instproc函数init相当于C++中的构造函数.
superclass用于继承.

OTcl的继承机制
文献1对OTcl的继承机制描述的很清晰. OTcl支持类的多继承, 唯一的要求是继承关系满足DAG(有向无环图). OTcl的类继承可以简单地通过下面这个例子来理解.
Class Safety
Safety instproc init {} {
$self next
$self set count 0
}
Safety instproc put {thing} {
$self instvar count
incr count
$self next $thing
}
Safety instproc get {} {
$self instvar count
if {$count == 0} then { return {empty!} }
incr count -1
$self next
}
Class Stack
Stack instproc init {} {
$self next
$self set things {}
}
Stack instproc put {thing} {
$self instvar things
set things [concat [list $thing] $things]
return $thing
}
Stack instproc get {} {
$self instvar things
set top [lindex $things 0]
set things [lrange $things 1 end]
return $top
}
Class SafeStack -superclass {Safety Stack}
SafeStack s
s put toast ==> toast
s get ==> toast
s get ==> empty!
s destroy ==> {}
上面的例子中, SafeStack从两个类派生而来Safety, Stack. OTcl使用"方法分发(Method Dispatch)"来描述如何从子类访问父类的重载方法. 如Figure 3所示.
从Figure 3可以了解到类是如何通过next方法来访问父类的重载方法的.
OTcl的C Api
OTcl给出了一个简洁的C Api接口, 通过这个接口我们可以在应用中访问OTcl中的对象. 主要接口描述见7和otcl.h文件. 从这些接口我们可以了解到OTcl本身并没有提供操纵C++类的方法, 这留给了tclCL来完成这一工作.
检查OTcl的Makefile, 可以看到它有三个目标: libotcl.a, owish, otclsh. 后两个都是shell程序, libotcl.a是我们关心的, 它就是OTcl的库, 当它被链接到应用程序中后, 应用程序就有了OTcl脚本内嵌的功能. libotcl.a: otcl.c
rm -f libotcl.a otcl.o
$(CC) -c $(CFLAGS) $(DEFINES) $(INCLUDES) otcl.c
ar cq libotcl.a otcl.o
$(RANLIB) libotcl.a
可以看到libotcl.a只由一个文件生成otcl.c, 这个c文件有两千多行, 好在otcl.h头文件很清晰. otcl.h一开始就声明了两个结构, 然后开始声明一些函数, 这些函数的名字很self meaning.
这里不打算继续分析otcl.c的源码了, 可以从otcl.h和OTcl的主页上了解一下C api的相关内容. 下面开始在tclcl中寻找我们感兴趣的内容.

NS2的应用场景与设计
有了上面对Tcl/OTcl的了解, 我们开始探询NS2仿真系统的设计原理. NS2的目标是仿真实际网络的各个元素, 对这些网络元素的描述有相关的资源可以获取. 如TCP协议的实现, 就可以从FreeBSD上获取, 由于TCP协议的实现版本不同, 就有了Reno发布等. 对链路的仿真, 主要是通过队列, 延迟等.
如何仿真这些网络元素是一回事, 对于NS2的系统架构是另一回事. 如此多的网络元素, 它们有可能功能重叠, 特定的仿真对象有不同的要求. 要满足这种灵活性, 一个可行的方案就是采用脚本定制仿真环境. 从而导致NS2有内嵌脚本语言的要求.
另一方面, 这些网络元素是分门别类的, 再有从实现效率上考虑, 使NS2采用了C++来对这些网络仿真对象建模. 这样就对应的要求脚本语言能够有与C++对象交互的能力. --- 这个要求可不低. 而MIT正好又有一种新开发的面向对象的脚本语言OTcl, 这些都促成了NS2采用OTcl.
NS2的应用场景是这样的: 用户在一个Tcl脚本中给出对仿真环境的描述, 键入ns xx.tcl启动NS2, NS2先完成一些初始化工作, 然后按照脚本的描述实例化各个仿真元素, 并把这些仿真元素连接起来, 在启动事件发生器, 触发网络元素动作, 中间有一些记录工作, 把这些仿真信息保存在磁盘上留待继续分析.
现在我们考虑这样一个系统如何设计. OTcl本身有对象的机制, Tcl脚本可以描述我们要仿真的对象. 但是我们遇到的问题是如何从OTcl来操纵NS2的C++对象, 这包括:
动态创建一个新的C++对象,
访问这个C++对象的属性,
调用该C++对象的方法.

C++对象的创建与删除
首先, 我们首先需要一种机制, 能够从一个描述类的字符串来动态的创建一个C++对象. 如果退到Tcl的方式, 我们就必须为每一个类写这样一种Tcl命令接口, 这不是一个好的解决方案, 它最大的问题是丧失了C++的继承关系. NS2的解决方案称为"Split Model", 它在OTcl上建立Tcl下的类, 与C++类分级保持一致, 每个C++类都对应一个OTcl的类, 但是问题还没有完全解决. 你在OTcl上初始化了一个对象, 必须要同时初始化一个对应的C++的对象.
为了解决这个问题, NS2在C++上使用了TclClass类来封装注册机制. 每个C++类都有一个对应的从TclClass派生而来的对象, 注意这里是对象, 是一个实例, NS2一启动就会实例化它. 该对象的主要目的是封装注册C++类的动态创建函数, 注册信息维护在一个hash表中, 该hash表是tclCL包的一部分, hash键是描述类的一个字符串计算而来.
接下来的问题是, 如何调用这个C++类的创建函数. NS2的方法很技巧, 前面所说的OTcl继承关系是一个关键, OTcl的对象的初始化函数都是init, 一个派生的OTcl对象首先是调用它的父类的init函数. 如前面的代码: Stack instproc init {} {
$self next
$self set things {}
}
这样, 一个OTcl的初始化肯定要调用到OTcl的Object类的init, 如果这个Object能够在此时初始化这个C++对象将是再理想不过. 这样一来, C++对象的初始化就对用户来说不可见了, 他只看到的是一个OTcl对象被初始化. NS2使用了TclObject而不是Object来派生所有的OTcl对象, 对应的, C++仿真对象也从一个C++的TclObject类派生. 由OTcl的根类TclObject来搜索C++类名hash表, 完成C++对象的创建工作. 还有一个小问题, init必须带入C++类名的字符串作为参数, 否则就没法查hash表了.
OTcl对象/C++对象的删除也是类似的道理. 至此, 第1个问题解决.

访问C++对象的属性
下一个问题是要从OTcl上操纵C++对象的属性. OTcl对象的属性并不一定要完全照抄C++对象的属性, OTcl对象属性的设计原则是能够方便的完成对C++属性的设置即可. 所以一般来说, OTcl对象属性集合要小于C++对象属性集合.
在分析如何访问C++对象属性之前, 先澄清一些OTcl名字空间的概念. OTcl是一个脚本程序, 它传统的继承了Unix下环境变量的概念, 变量的名字空间是扁平的. 而引入了对象机制后, 名字空间就有些复杂了. 显然一个OTcl对象的属性的名字与环境变量下的名字有可能重叠. 在OTcl中, 这称为名字的"context"语境.
对比C++对象的名字, 它是确定的, 这是因为有编译器的帮助, 编译器在编译一个C++源代码的时候它可以根据上下文来判断这里变量指的是什么. 而在OTcl的环境中, 也需要类似的机制, 由OTcl的Parser动态的确定一个名字的含义.
显然要确定一个名字的含义, 可以通过对象名来帮助作到这一点. 在OTcl中还有一个对象的hash表, 对象创建后要注册到这个hash表中. 对一个名字解释, 首先是要搜索对象hash表, 再搜索该对象的class及其父类, 参照前面的next指针.
在下面的代码中, $self instvar count这条语句就是切换context的. 如果不切换context, 我们将不知道是环境变量名还是其他的对象的属性. Safety instproc put {thing} {
$self instvar count
incr count
$self next $thing
}
对一个C++对象属性的访问, 有读/写两种操作. 由于存在OTcl/C++两个对象, 有可能它们的属性并不一致. 但是要注意到OTcl的对象属性是给用户看的, 只要保证在读/写OTcl对象属性的时候能够作到与C++对象一致就行了, 完全保持二者的一致性是没有必要的. 有了这样的要求, 下面的方式才是可行的.
NS2采用了一种trap机制来捕获对OTcl对象属性的访问. 具体来说, 在tclCL中是以InstVar类对象来封装这种Trap机制. Trap的位置安装在语境切换的时刻, 因为只有在语境切换后, 才有可能对该
OTcl对象的属性进行访问.

对C++对象非static属性的访问
现在来考虑一下实现trap或者说C++对象属性绑定的细节问题. 首先, 绑定的是一个C++对象的属性(对绑定一个C++类的static属性问题在后面谈), 这意味着要知道该C++对象属性的位置, 所以C++对象属性的位置是绑定的一个必要条件.
其次, 如何设计这个Trap? 假设我们要对某个OTcl的变量进行写操作, 一般的操作是利用Tcl的Parser直接写, 但是这里我们要保持与某个位置上的信息同步, 就还需要向这个位置写相应的信息. 要解决问题, 可以修改Tcl的Parser, 让它写同步位置即可. --- 这就是InstVar的思路, 我们可以在语境切换的时刻安装一个定制的Parser, 让这个定制的Parser来向C++对象属性的位置写, 问题就解决了.
剩下的问题是, 何时安装这样一个定制Parser? 原则上任何时候都是可以的, 只要你知道这个C++对象属性的位置. 但是一个方便的做法是在该C++对象构造函数内做. 当调用这个binding函数的时候, 它会在OTcl对象属性位置上做一个标记, 表示有绑定的属性. 当进行语境切换的时候, 如果在该OTcl对象的属性位置上有绑定标志, 则OTcl动态安装定制的Parser, 这个定制的Parser就是tclCL的InstVar的一个成员函数.
由于OTcl是脚本语言, 是若类型的语言, 它用字符串表示所有的变量, 只有当它在eval的时候才会知道它具体是char/int/real等. 所有InstVar有几个派生类, 原因是这个定制的Parser要向C++属性写不同类型的信息.
给个例子 ASRMAgent::ASRMAgent() {
bind("pdistance_", &pdistance_); \* real variable */
bind("requestor_", &requestor_); \* integer variable */
bind_time("lastSent_", &lastSessSent_); \* time variable */
bind_bw("ctrlLimit_", &ctrlBWLimit_); \* bandwidth variable */
bind_bool("running_", &running_); \* boolean variable */
}
对C++对象static属性的访问
C++对象的静态属性是放在局部堆上的, 而且是该类的所有对象共享的. 如果在C++对象的构造函数中绑定这个静态属性, 显然有效率上的问题, 在同时存在多个该类的C++对象时, 就会多次绑定. 更严重的是, 它绑定到的对应的是OTcl对象上, 这样在OTcl对象上该static属性就不同一了.
注意到NS2启动时, C++类对应的TclClass把C++类要注册到OTcl的类hash表上, 这个时刻是做对C++类工作的绝好机会.
分析OTcl的类/对象机制, 可以看到OTcl无法象C++对象一样有静态变量, 这算是OTcl设计的一个缺陷? 用OTcl类初始化一个OTcl对象, 意味着完整拷贝OTcl的信息. 对次, NS2采用了一种变通的方法. 它首先在OTcl类上添加了一个属性, 以后用该类初始化OTcl对象时, 所有对象都有这样一个属性. 然后在该属性上注册一个定制的Parser到这个属性上, 这个Parser直接访问了C++对象静态变量. ---这样就在OTcl对象上作出了一个静态变量. 见下面的从文献3中摘录的代码.
假设C++类的static变量为 class Packet {
......
static int hdrlen_;
};
Packet的TclClass中定义如下: class PacketHeaderClass : public TclClass {
protected:
PacketHeaderClass(const char* classname, int hdrsize);
TclObject* create(int argc, const char*const* argv);
\* These two implements OTcl class access methods */
virtual void bind();
virtual int method(int argc, const char*const* argv);
};
void PacketHeaderClass::bind()
{
\* Call to base class bind() must precede add_method() */
TclClass::bind();
add_method("hdrlen");
}
int PacketHeaderClass::method(int ac, const char*const* av)
{
Tcl& tcl = Tcl::instance();
\* Notice this argument translation; we can then handle them as if in TclObject::command() */
int argc = ac - 2;
const char*const* argv = av + 2;
if (argc == 2) {
if (strcmp(argv[1], "hdrlen") == 0) {
tcl.resultf("%d", Packet::hdrlen_);
return (TCL_OK);
}
} else if (argc == 3) {
if (strcmp(argv[1], "hdrlen") == 0) {
Packet::hdrlen_ = atoi(argv[2]);
return (TCL_OK);
}
}
return TclClass::method(ac, av);
}
OTcl脚本如下, 这个脚本模拟了读写两种操作. PacketHeader hdrlen 120
set i [PacketHeader hdrlen]

几点感想
定制Parser在上面的代码中体现无疑.
上面在对C++对象非static属性访问的代码中bind函数值得回味. 它把一个private/protected的属性给暴露出去了, 而C++编译器却照样编译通过, 有意思.
调用C++对象的方法
在NS2中, 很少有OTcl本身再实现一个对象的方法的, 因为从效率的角度考虑, 这样做会得不偿失. 一般的情况都是直接调用C++对象的方法来处理. 从实现上来看, 要调用一个对象的方法并不困难, 只要在合适的语境中, 给出参数直接调用就可以了. 所以NS2中实现对C++对象的方法的引用至多也就是定制Tcl的Parser.
注册顶级命令
OTcl继承了Tcl的某些特性, 可以通过TclCommand类定制一个顶级命令注册到OTcl的Parser中, 不过这种方法不值得推荐. 下面的例子3给出了实现方法:
要在OTcl中注册的顶级命令是hi: % hi this is ns [ns-version]
hello world, this is ns 2.0a12
下面是实现代码, 构造函数直接以TclCommand的构造函数注册"hi"命令, command()函数是命令的实现部分. class say_hello : public TclCommand {
public:
say_hello();
int command(int argc, const char*const* argv);
};
say_hello() : TclCommand("hi") {}
#include \* because we are using stream I/O */

int say_hello::command(int argc, const char*const* argv) {
cout << "hello world:"; for (int i = 1; i <> method?*/
virtual int command(int argc, const char*const* argv);
virtual void trace(TracedVar*);
...
可以看到, command()是一个虚函数, 这么做的目的是为了保证所有的command()函数都一样. 再看看3中给的一个例子: 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));
}
注意到最后一行return (SRMAgent::command(argc, argv)); --- 问题清楚了, command()函数是在C++对象一侧上溯到它的父类的, 这样做显然效率要高一些. 因此, ccommand()应该是在C++对象初始化的时候, 被注册到OTcl对象的cmd{}上.
最后交待一下OTcl如何调用cmd{}. 有两种方式, 一种是显示的调用cmd{} $srmObject cmd distance?
另一种是隐式的调用: $srmObject distance?
在隐式调用方式下, Tcl的Parser先检查有OTcl对象没有distance?这样的命令, 这里显然没有. 然后Parser会把解释权传递给OTcl对象的unknown{}函数, unknown{}函数会使用上面的显示调用的方式来调用C++对象的command()函数.
在OTcl对象中实现命令的例子
虽然在OTcl对象中实现方法的例子不常见, 文献3还是给出了一个例子: Agent/SRM/Adaptive instproc distance? addr {
$self instvar distanceCache_
if ![info exists distanceCache_($addr)] {
set distanceCache_($addr) [$self cmd distance? $addr]
}
set distanceCache_($addr)
}
这个例子说明了在OTcl对象中中重载C++对象的命令.
到现在为止, 我们已经大致了解了NS2的基本架构和设计原理. 还有一些细节问题留到下一节对tclCL模块的类说明.
tclcl
tclCL是在OTcl基础上的封装, 它们与Tcl之间的关系如图Figure 2. tclCL实际上搭建了NS2的框架, NS2的类库都是建立在tclCL基础上的. 在文献3第三章简单介绍了tclCL的六个类: Tcl, TclObject, TclClass, TclCommand, EmbeddedTcl, InstVar.

其中, Tcl类可以看成是一个Tcl的C++接口类, 它提供C++访问Tcl库的接口. TclObject是Tcl/C++两个面向对象语言的类库的基类, 在最新的tclcl中, 采用了SplitObject的术语. TclClass注册编译分级, 保持了编译分级的层次结构, 同时给OTcl对象提供了创建C++对象的方法. TclCommand用于定义简单的全局解释命令. EmbeddedTcl是定制的Tcl命令. InstVar类包含了从Tcl访问C++类成员变量的方法.
这里不打算对tclcl的六个类再详细介绍了, 这篇文章到此为止已经基本达到它的目的. 下面的内容只挑选我认为是容易遗漏的地方. 文献3作为开发NS2手头必备的工具应该详细研读.
Class Tcl
Tcl类提供如下的方法
获取Tcl实例句柄
Tcl类只有一个实例, 该实例在NS2启动时初始化. 获取Tcl类的实例方法是: Tcl& tcl = Tcl::instance();
解析执行Tcl脚本命令
返回结果给Parser, 设置Tcl的环境变量
Tcl类的结果是指tcl_->result, 与脚本执行的退出码不同. 如下的代码 if (strcmp(argv[1], "now") == 0) {
tcl.resultf("%.17g", clock());
return TCL_OK;
}
tcl.result("Invalid operation specified");
return TCL_ERROR;
报告错误, 以一种统一的方式退出执行脚本
有两种错误退出方式, 一种是返回TCL_ERROR的退出码, 另一种是以tcl.error()函数退出, 两者稍有区别. 前者可以在解释器中trap这个错误, 然后打出出错的调用栈桢; 后者则不行.
存储并搜索TclObject对象
Tcl类中有一个C++对象的hash表, hash键是对象名. 这里值得注意的是C++对象的hash表而不是OTcl对象的hash表. 有些奇怪, 难道OTcl对象的hash表在OTcl模块中?
插入定制的Parser
tcl.interp(void)函数是tcl的Parser句柄, 可以修改它, 加入定制的Parser.
Class TclObject
在文献3中, OTcl对象称为解释分级对象, C++对象称为编译分级对象. TclObject并不包括Simulator, Node, Link, rtObject等对象. 对用户来说, 如果只使用Tcl配置脚本的话, 一般只看得到OTcl对象的创建, 而C++对象的创建正如我们前面分析的, 对用户是不可见的.
对象的创建和销毁
OTcl对象包括TclObject, Simulator等都使用在~/tclcl/tcl-object.tcl文件中定义的new{}和delete{}函数来初始化和销毁. 这一部分的内容前面已经分析的比较清楚.
每个OTcl对象在创建的时候会获取一个id返回给用户. OTcl的基类TclObject使用create-shadow{}函数来创建C++对象. 在C++对象的构造函数中一般都会调用属性绑定函数, 进行C++/OTcl属性绑定. C++对象创建后, 被插入到Tcl类对象的hash表中. 然后用C++对象的command()函数在OTcl对象中注册cmd{}函数.
下面的这张图比较清楚的反映了对象创建的过程.

OTcl中TclObject对象的函数create-shadow{}是TclClass注册的一个命令. 虚线表示C++编译器自动的调用父类的初始化函数.
变量绑定
OTcl/C++对象的属性初始化在~ns/tcl/lib/ns-default.tcl中做.
变量跟踪
命令方法
Class TclClass
Class TclCommand
Class EmbeddedTcl
Class InstVar
两个例子
本来打算写一两完整的例子, 展示如何向NS2中添加一个新的模块, 但我发现网上有两个比较好的资源可以借用910, 这里就免了.

结束语
这篇文章的后面两节实在没时间写下去了, 就省略, 省略, 再省略, ...
等我以后有时间在续吧.
References
[1] Extending Tcl for Dynamic Object-Oriented Programming
[2] Practical Programming in Tcl and Tk
[3] The ns Manual
[4] http://bmrc.berkeley.edu/research/cmt/cmtdoc/otcl/tutorial.html
[5] http://bmrc.berkeley.edu/research/cmt/cmtdoc/otcl/object.html
[6] http://bmrc.berkeley.edu/research/cmt/cmtdoc/otcl/class.html
[7] http://bmrc.berkeley.edu/research/cmt/cmtdoc/otcl/capi.html
[8] http://bmrc.berkeley.edu/research/cmt/cmtdoc/otcl/autoload.html
[9] http://nile.wpi.edu/NS/linkage.html
[10] http://140.116.72.80/~smallko/ns2/module.htm