分组头的管理
1、在NS的模拟网络中,分组(Packet)是 对象间交互的基本单元。分组是一系列分组头和一个可选的数据空间组成。分组头的结构在Simulator对象创建时就被初始化了,同时每个分组头相对于分组的起始地址的偏移量也被记录下来。在缺省情况下,大多数NS内建的分组头都是使能的(包括common头、IP头、TCP头、RTP头、trace头等等)。这意味着在缺省的情况下无论某个分组头是否会被使用,它都会被初始化。在通常情况下,分组只含有一系列的分组头,而指向数据空间的指针为null。
用户可以为新的协议定义自己的分组头,也可以通过增加域的方式扩展现有的分组头,添加新分组头的方法,概括来说就是在C++中定义包含所需的域的structure,再定义一个static类来提供到otcl的连接,然后修改一些模拟的初始化代码来指定新分组头在分组中的字节偏移量。
2、协议和分组头
一般情况下,我们需要为新协议定义该协议自己的分组头,这样做可以保证新协议的使用不会影响已经存在的分组头。例如RTP头包含一个序列号字段和一个源标识字段,下面代码定义了RTP协议的分组头见~/ns/apps/rtp.(cc,h):
在rtp.h中
struct hdr_rtp {
u_int32_t srcid_; //源标识字段
int seqno_; //序列号字段
//rtp flags indicating significant event(begining of talkspurt)
u_int16_t flags_;
static int offset_; //rtp头在NS分组中的字节偏移量,访问p分组的RTP头,hdr_rtp::access(p)。类PackHeaderManger负责把offset_和分组中这个头的位置绑定起来。
inline static int& offset() { return offset_; }
inline static hdr_rtp* access(const Packet* p) {
return (hdr_rtp*) p->access(offset_);
}
/* per-field member functions */
u_int32_t& srcid() { return (srcid_); }
int& seqno() { return (seqno_); }
u_int16_t& flags() { return (flags_); }
};
hdr_rtp主要作用是计算字段的字节偏移量,在结构中也提供了成员变量函数用来提供外部访问内部变量的接口。
在rtp.cc中
class RTPHeaderClass : public PacketHeaderClass {
public:
//调用PacketHeaderClass的构造函数,会使RTP头的大小被存储,被PackHeaderManger对象使用
RTPHeaderClass() : PacketHeaderClass("PacketHeader/RTP",
sizeof(hdr_rtp)) {
bind_offset(&hdr_rtp::offset_);//PackHeaderManger知道这个头的偏移量存储在那里
}
} class_rtphdr // class_rtphdr是 RTPHeaderClass类的静态对象,提供了到otcl的连接
void RTPAgent::sendpkt()
{
Packet* p = allocpkt(); //构造一个需要发送的新分组,allocpkt()处理所有网络层分组头的字段的赋值工作。
lastpkttime_ = Scheduler::instance().clock();
makepkt(p);
target_->recv(p, (Handler*)0);
}
void RTPAgent::makepkt(Packet* p)
{
hdr_rtp *rh = hdr_rtp::access(p); //返回用于存储头信息的缓冲区的第一个字节的地址,被强制转换为相应的头的指针。
/* Fill in srcid_ and seqno */
rh->seqno() = seqno_++;
rh->srcid() = session_ ? session_->srcid() : 0;
}
3、添加新分组头
假如我们要增加一个叫newhdr的新分组头,需要:
(1) 在C++中创建一个新的structure来定义所需要的字段,按照命名惯例,这个structure应该叫hdr_newhdr,定义offset_字段和访问该字段的方法。
(2) 定义所需要的访问其他字段的成员函数。
(3) 创建一个static类来完成otcl连接(PcketHeader/Newdhr),在它的构造函数里面进行bind_offset()。
(4) 编辑~ns/tcl/lib/ns-packet.tcl来激活新的分组头。
如果需要查找分组头的名字,可以查看文件~ns/tcl/lib/ns-packet.tcl或者在NS中执行下面代码:
Foreach cl [PacketHeader info subclass] {
Puts $cl
}
1、 与分组头相关的类
(1) Packet类:NS中所有的分组都是Packet类的对象,它是Event类的子类,因此分组可以像事件那样被调度。
class Packet : public Event {
private:
unsigned char* bits_; // 指向的字符数组中存储着所有的分组头(BOB),包含BOB的第一个字节的地址,BOB是被激活的各个分组头中定义的所有structure连接一起形成的。BOB在模拟过程保持固定的大小,大小记录在Packet::hdrlen_中,hdrlen_的值在模拟配置阶段由PackHeaderManger类设置,
AppData* data_; // variable size buffer for 'data' 不常使用
static void init(Packet*); // initialize pkt hdr
bool fflag_;
protected:
static Packet* free_; // packet free list,存储着已经回收的曾经使用过的分组,在产生新的分组时,会优先选择使用这些回收的分组所占用的空间。
int ref_count_; // free the pkt until count to 0
public:
Packet* next_; // for queues and the free list
static int hdrlen_;
Packet() : bits_(0), data_(0), ref_count_(0), next_(0) { }
inline unsigned char* bits() { return (bits_); }
inline Packet* copy() const;
inline Packet* refcopy() { ++ref_count_; return this; }
inline int& ref_count() { return (ref_count_); }
static inline Packet* alloc();
static inline Packet* alloc(int);
inline void allocdata(int);
// dirty hack for diffusion data
inline void initdata() { data_ = 0;}
static inline void free(Packet*);
inline unsigned char* access(int off) const {
if (off < 0)
abort();
return (&bits_[off]);
}
// This is used for backward compatibility, i.e., assuming user data
// is PacketData and return its pointer.
inline unsigned char* accessdata() const {
if (data_ == 0)
return 0;
assert(data_->type() == PACKET_DATA);
return (((PacketData*)data_)->data());
}
u_int8_t incoming;
}
(2) p_info类:保存了所有分组类型的名字的文本表示。
这个类相当于“胶水”,用来绑定各个分组类型值和它们的名字,当定义了一个新的分组类型后,它的数字代码应该添加到枚举类型packet_t中,需要注意的是,PT_NTPE必须是这个枚举类型的最后一个元素,同时这个分组类型的名字应该添加到p_info类的构造函数中。
(3) PacketHeaderClass类:任何人一个分组头都是以它为基类,它提供的内部状态变量
可以在分组中定位某个特定的分组。设置hdrlen_和offset_变量,hdrlen_是在构造函数被设置,而offset_是通过调用bind_offset()来设置的。
(4) PackHeaderManger类:管理各个分组头,在模拟配置阶段,可以从otcl中调用它来激活所有内嵌分组头的一部分。在BOB中分配给它们唯一的偏移量。
Packet.tcl中
PacketHeaderManager set hdrlen_ 0
PacketHeaderManager set tab_(Common) 1 //common头总是使能的
proc add-packet-header args {
foreach cl $args {
PacketHeaderManager set tab_(PacketHeader/$cl) 1
}
}
set protolist { //包含了所有需要激活的分组头的名字
# Common:
Common
Flags
IP # IP
…..
}
foreach prot $allhdrs {
add-packet-header $prot //要激活的分组头加入到Otcl类数组tab_中,在tab_中所有被激活的分组头值为1.
}
Simulator instproc create_packetformat { } { //在模拟的配置阶段被调用
PacketHeaderManager instvar tab_
set pm [new PacketHeaderManager] //创建PacketHeaderManager类对象
foreach cl [PacketHeader info subclass] {
if [info exists tab_($cl)] {
set off [$pm allochdr $cl]
$cl offset $off
}
}
$self set packetManager_ $pm
}
PacketHeaderManager instproc allochdr cl { //给出分组头的位置
set size [$cl set hdrlen_]
$self instvar hdrlen_
set NS_ALIGN 8
# round up to nearest NS_ALIGN bytes
# (needed on sparc/solaris)
set incr [expr ($size + ($NS_ALIGN-1)) & ~($NS_ALIGN-1)]
set base $hdrlen_
incr hdrlen_ $incr
return $base
}
(5) hdr_cmn结构
模拟中的每个 分组都包含一个common头,hdr_cmn结构定义了用于跟踪分组流向、度量时间以及长度等常用指标的字段。
struct hdr_cmn {
enum dir_t { DOWN= -1, NONE= 0, UP= 1 };
packet_t ptype_; // packet type (see above)
int size_; // simulated packet size
//size_经常被用来计算沿着一条网络链路递交分组所需要的时间,值是应用数据的长度和IP层、运输层、应用层分组长度之和。
int uid_; // unique id
int error_; // error flag
int errbitcnt_; // # of corrupted bits jahn
int fecsize_;
double ts_; //测量在交换节点处的排队延迟
int iface_; // 完成多播分发树计算,它指出分组是那个link上收到的
dir_t direction_; // direction: 0=none, 1=up, -1=down
// source routing
char src_rt_valid;
double ts_arr_; // Required by Marker of JOBS