-
目标:
建立一个影像媒体应用数据流,可以根据当前网络的拥塞状况,可以自适应的(分为 0-4, 共五个级别)调整发送的数据量的大小。
-
应用描述:
发送方与接收方,共同定义了五组(0-4) 方案,每一种方案有不同的编码与传输策略。在每一组方案和不考虑编码方案的下,传输的速率是不变的,且每个数据包的大小是固定的。
工作方式如下:(发送方为 A,接收方为 B)
例如:初始时,发送方 A 以组 0 方案进行发送数据,当接收方 B 认为网络的拥塞状况严重时,发送给 A 一个数据包,要求以组 2 方案发送数据,即是降低一倍。如果 B 认为网络又拥塞状况改善时,发给 A 一个数据包,要求以组 1 方案发送数据,即是提高一档。每隔一段时间,B 会发送 A 一个数据包,要求 A 以某个方案发送影像数据。
-
问题分析:
因为在实际的应用中,UDP 数据流是和应用密切相关的。我们应当让 UDP 端可以应用中 接收数据,并把数据打包,然后进行发送。而在 NS 中缺省的 UDP 只使用仅有包头的数据 包。这一点是不满足我们的要求的。所以,要对 UDP 部分进行一定的修改,让其有这个机制能够处理这个问题。
而且,这个实验对于以后对 IP 路由排队研究的深入做一定的基础学习。这样的话,我们就要在 IP 头部分记录数据的类型。
-
设计与实现:
对于这个例子,我们修改一个 CBR 的例子使其具有 0-4 组方案的传输。
1、先构造一个 C++类,MmApp,从 Application 继承而来。应用层的发送和接收均在此类 中完成
2、在 OTCL 空间中的映射为 Application/MmApp
3、在构造一个 C++类名 UdpMmAgent 从 UdpAgent 中继承,传输层的发送和接收在此类中完成。
4、在 OTCL 空间中的映射为 Agent/UDP/UDPmm
-
这篇所需的文件以及文件的位置:
packet.h in “ns-2.29/common/packet.h”
agent.h in “ns-2.29/common/agent.h”
app.h in “ns-2.29/apps/app.h”
Makefile in “ns-2.29/Makefile”
the locations of OTcl files :
ns-packet.tcl in “ns-2.29/tcl/lib/ns-packet.tcl”
ns-default.tcl in “ns-2.29/tcl/lib/ns-default.tcl”
-
操作步骤:
-
准备核心代码:进入ns-2.29目录中,分别新建mm-app.cc, mm-app.h, udp-mm.cc, udp-mm.h四个文件,其代码附在文章后面。(这四个文件,读者应该认真研读,因为从这可以看应用层和代理的工作过程)
-
在packet.h文件中,增加代码,其目的是在NS中注册,把“Multimedia”加入头堆栈中。
// Bell Labs Traffic Trace Type (PackMime OL)
static const packet_t PT_BLTRACE = 60;
// Multimedia packet
PT_Multimedia
// insert new packet types here
PT_NTYPE // This MUST be the LAST one
// Bell Labs (PackMime OL)
name_[PT_BLTRACE]="BellLabsTrace";
name_[PT_Multimedia]="Multimedia";
name_[PT_NTYPE]= "undefined";
3.将下面代码加入到“ns-packet.tcl”文件中,作用与上面是一样的:
HDLC # High Level Data Link Control
Multimedia #Multimedia
} {
add-packet-header $prot
}
4.修改agent.h文件:
在Class Agent 增加两个成员函数 supportMM( )和 enableMM( )来检查 Agent 是否支持 MM。在 class MmApp 中的 command 成员函数中,定义attach agent 的OTCL 命令。这个命令执行前,会调用 class Agent 中的成员函数 supportMM( )和 enableMM( )来检查 Agent 是否支持 MM。即使这两个成员函数在类udpMmAgent中已经定义了,但是如果在它的父类Agent中没有定义,会出现编译错误,所以要在Agent类中也要定义。
virtual int supportMM() { return 0; }
virtual void enableMM() {};
virtual void sendmsg(int nbytes, const char *flags = 0);
virtual void send(int nbytes) { sendmsg(nbytes); }
5.修改app.h文件:
需要在Application类中增加一个成员函数 virtual void recv_msg(int nbytes,const char *msg=0){};这个函数在早期的 NS 中是有的。但是在ns-2.1b8a后的版本又被去掉了。这个要根据你具体使用的 NS 而定。
class Application : public Process {
public:
Application();
virtual void send(int nbytes);
virtual void recv(int nbytes);
virtual void recv_msg(int nbytes,const char *msg=0){};
virtual void resume();
6.在ns-default.tcl中为新参数设置指定的值:
Application/Traffic/CBR set rate_ 448Kb ;# corresponds to interval of 3.75ms
Application/Traffic/CBR set packetSize_ 210
Application/Traffic/CBR set random_ 0
Application/Traffic/CBR set maxpkts_ 268435456; # 0x10000000
Application/Telnet set interval_ 1.0
Application/MmApp set rate0_ 0.3mb
Application/MmApp set rate1_ 0.6mb
Application/MmApp set rate2_ 0.9mb
Application/MmApp set rate3_ 1.2mb
Application/MmApp set rate4_ 1.5mb
Application/MmApp set pktsize_ 1000
Application/MmApp set random_ false
7.修改Makefile文件,增加对象文件mm-app.o和udp-mm.o:
wpan/p802_15_4nam.o wpan/p802_15_4phy.o \
wpan/p802_15_4sscs.o wpan/p802_15_4timer.o \
wpan/p802_15_4trace.o wpan/p802_15_4transac.o \
apps/pbc.o \
ex-linkage.o \
mm-app.o \
udp-mm.o \
$(OBJ_STL)
8.重新编译:在重新编译以前,先执行make clean和make depend命令,否则新的应用有可能不传达任何包。
-
ns-allinone-2.33/ns-2.33
-
make clean
-
make depend
-
make
9.测试模拟.
下面来具体分析一下udp-mm.(h cc)和mm-app.(h cc)文件
udp-mm.h
//Author:Vivian
//File:udp-mm.h
//Written:08/25/10
//
#ifndef ns_udp_mm_h
#define ns_udp_mm_h
#include "udp.h"
#include "ip.h"
//虚拟一个多媒体应用的行为,定义多媒体报头结构
struct hdr_mm {
int ack; //判断是否为ACK包
int seq; //mm包的序号
int nbytes; //mm包的大小
double time; //当前时间
int scale; //5种不同的发送速率scale(0-4)
//定义访问hdr_mm报头函数
static int offset_;
inline static int& offset() { return offset_; }
inline static hdr_mm* access(const Packet* p) {
return (hdr_mm*) p->access(offset_);
}
};
//mm报的重组
struct asm_mm {
int seq; //mm报序号
int rbytes; //当前收到的bytes
int tbytes; //收到的所有mm报的总的大小
};
//UdpMmAgent类的定义
class UdpMmAgent : public UdpAgent {
public:
UdpMmAgent();
UdpMmAgent(packet_t); //两个构造函数
virtual int supportMM() {return 1;} //判断是否为mm报
virtual void enableMM() {support_mm_ = 1;}
virtual void sendmsg(int nbytes,const char* flags = 0);
void recv(Packet*,Handler*);
protected:
int support_mm_; //如果是mm报,则赋值1
private:
asm_mm asm_info; //报重组信息
};
#endif
这个头文件比较简单,定义了一个multimedia的新的报头格式,并定义了一个偏移地址用于访问hdr_mm。而UdpMmAgent类继承了UdpAgent 。里面有supportMM()意为判断是否为MM包,如果支持则执行enableMM()。sendmsg()函数用来对数据进行判断、分割和发送。recv()函数用来接收从底层上来的包。这里UdpMmAgent扩展了原始的UdpAgent 的功能。添加了重组的方法。
udp-mm.cc
1、static class MultimediaHeaderClass : public PacketHeaderClass {
public:
MultimediaHeaderClass() : PacketHeaderClass("PacketHeader/Multimedia",sizeof(hdr_mm)) {
bind_offset(&hdr_mm::offset_);
}
} class_mmhdr;
static class UdpMmAgentClass : public TclClass {
public:
UdpMmAgentClass() : TclClass("Agent/UDP/UDPmm") {}
TclObject* create(int,const char*const*) {
return (new UdpMmAgent());
}
}class_udpmm_agent;
//构造函数
UdpMmAgent::UdpMmAgent() : UdpAgent()
{
support_mm_ = 0;
asm_info.seq = -1;
}
//构造函数,含参数
UdpMmAgent::UdpMmAgent(packet_t type) : UdpAgent(type)
{
support_mm_ = 0;
asm_info.seq = -1;
}
片段1主要定义了对 MultimediaHeaderClass到PacketHeaderClass 的映射类,并指定报头名称为Multimedia。注意其中一定要对hdr_mm报头的便宜地址进行绑定。之后定义了对UdpMmAgentClass到TclClass的映射类,并制定名称为UDPmm。
2、
//添加对多媒体应用的支持到UdpAgent::sendmsg
void UdpMmAgent::sendmsg(int nbytes,const char* flags)
{
Packet* p;
int n,remain;
if(size_) {
n = (nbytes/size_ + (nbytes%size_ ? 1 : 0));
remain = nbytes%size_;
}
else
printf("Error: UDPmm size=0\n");
if (nbytes == -1) {
printf("Error: sendmsg() for UDPmm should not be -1\n");
return;
}
double local_time = Scheduler::instance().clock();
while (n-- > 0) {
p = allocpkt();
if (n==0 && remain>0) hdr_cmn::access(p)->size() = remain;
else hdr_cmn::access(p)->size() = size_;
hdr_rtp* rh = hdr_rtp::access(p);
rh->flags() = 0;
rh-> seqno() = ++seqno_;
hdr_cmn::access(p)->timestamp() = (u_int32_t)(SAMPLERATE* local_time);
//to eliminate recv to use MM fields for non MM packets 对于非MM包,清除MM参数
hdr_mm* mh = hdr_mm::access(p);
mh->ack = 0;
mh->seq = 0;
mh->nbytes = 0;
mh->time = 0;
mh->scale = 0;
//mm udp packets are distinguished by setting the ip
//priority bit to 15
if(support_mm_) {
hdr_ip* ih = hdr_ip::access(p);
ih->prio_ = 15;
if(flags) //MM Seq Num is passed as flags
memcpy(mh, flags, sizeof(hdr_mm)); //把flags首地址开始,长度为hdr_mm的内容复制到mh中
}
if (flags && (0 == strcmp(flags,"NEW_BURST")))
rh->flags() |= RTP_M;
target_->recv(p);
}
idle();
}
这里和原始的UDPAgent中sendmsg十分相似,只不过多了
if(size_) {
n = (nbytes/size_ + (nbytes%size_ ? 1 : 0));
remain = nbytes%size_;
}
这段函数实现了对接收到的数据包进行分割和发送。
3、
void UdpMmAgent::recv(Packet* p,Handler*)
{
hdr_ip* ih = hdr_ip::access(p);
int bytes_to_deliver = hdr_cmn::access(p)->size();
//如果是一个MM包
if(ih->prio_ == 15) {
if(app_) { //如果MM应用存在,当MM 包被分割后进行重组
hdr_mm* mh = hdr_mm::access(p);
if(mh->seq == asm_info.seq)
asm_info.rbytes += hdr_cmn::access(p)->size();
else {
asm_info.seq = mh->seq;
asm_info.tbytes = mh->nbytes;
asm_info.rbytes = hdr_cmn::access(p)->size();
}
//如果完全重组好了,将包传递到应用层
if(asm_info.tbytes == asm_info.rbytes) {
hdr_mm mh_buf;
memcpy(&mh_buf,mh,sizeof(hdr_mm));
app_->recv_msg(mh_buf.nbytes,(char*) &mh_buf);
}
}
Packet::free(p);
}
//如果是个普通的包
else {
if (app_) app_->recv(bytes_to_deliver);
Packet::free(p);
}
}
这段函数实现对接收到的MM包进行重组。