NS3中文教程5:Tweaking NS3
5 Tweaking NS3 (NS3调整?)
翻译: 杨飞 |
|
校稿: Xiaochuan Shen |
|
编辑: ProbibidoAmor |
5.1 日志模块的使用
在运行first.cc脚本时,我们已经简单的了解了日志模块。现在,我们将更深入的了解日志子系统是为哪些用户案例设计的。
5.1.1 日志概述
很多大型系统支持某种的消息日志功能,ns3也不例外。在某些情况下,只是错误消息日志被记录到操作控制台(在基于Unix-系统中通常是标准错误输出)。在其他系统中,警告消息可能跟详细的信息消息一起被输出。某些情况下,日志功能被用来输出令人费解的调试信息。
在NS-3中,我们认为这些不同详尽级别的日志都是非常有用的。Ns3还提供了一个可供选择的、多级别的方法来记录日志。日志可以完全被禁用,或仅对部分组件可用,或全局可用。并且ns3提供了不同详尽程度的日志级别供选。NS-3日志模块提供了直观的、相对简单的使用方法来帮助用户获得仿真过程的所需信息。
应当了解我们也提供了一个一般性的记录机制,tracing,来获得仿真结果之外的数据(详情参见本教程的tracing系统的使用章节)。日志应当作为快速获得你的脚本和模型的调试信息、警告信息、错误信息、或是其他信息的首要选择。
在现有的系统中,有7个详尽程度递增的日志级别,他们分别是:
· NS_LOG_ERROR — Log error messages;
· NS_LOG_WARN — Log warning messages;
· NS_LOG_DEBUG — Log relatively rare, ad-hoc debugging messages;
· NS_LOG_INFO — Log informational messages about program progress;
· NS_LOG_FUNCTION — Log a message describing each function called;
· NS_LOG_LOGIC – Log messages describing logical flow within a function;
· NS_LOG_ALL — Log everything.
· NS_LOG_ERROR — 记录错误信息;
· NS_LOG_WARN — 记录警告信息;
· NS_LOG_DEBUG — 记录相对不常见的调试信息;
· NS_LOG_INFO — 记录程序进展信息;
· NS_LOG_FUNCTION — 记录描述每个调用函数信息;
· NS_LOG_LOGIC – 记录一个函数内描述逻辑流程的信息;
· NS_LOG_ALL — 记录所有信息.
我们也提供了一种一直被使用的无条件日志级别,它是跟日志详尽级别或是组件选择无关的。
· NS_LOG_UNCOND – 无条件记录相关消息.
每一个级别能够被单独地被调用或逐级递增的被调用。日志的配置可以使用一个shell环境变量(NS_LOG),或是使用日志系统函数进行。正如在本教程之前部分看到的,日志系统有Doxygen文档,如果你还没有阅读日志模型文档现在是个好的时机。
既然你已经很详细地阅读了文档,我们使用日志来获得之前建立的first.cc脚本的一些有用信息。
5.1.2 启用日志
我们将使用NS_LOG环境变量来打开一些日志功能。首先,需要耐心点,像之那样运行脚本,
./war --run scratch/myfirst
你应当看到熟悉的、第一个ns-3例子程序的结果
Waf: Entering directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
'build' finished successfully (0.413s)
Sent 1024 bytes to 10.1.1.2
Received 1024 bytes from 10.1.1.1
Received 1024 bytes from 10.1.1.2
上面看到的"Sent"和"Received"消息实际上是来自UdpEchoClientApplication 和 UdpEchoServerApplication的日志消息。我们通过NS_LOG环境变量设置日志级别让客户端程序输出更多信息。
假设你在使用一个类sh的shell。此类shell使用“VARIABLE=value”的语法格式设置环境变量。如果使用类csh的shell,必须将例句改成"setenv VARIABLE value"语法格式的语句。
现在,scratch/myfirst.cc中UDP回显客户端应用在使用下面的代码行进行响应,
LogComponentEnable("UdpEchoClientApplication", LOG_LEVEL_INFO);
这行代码设置日志为LOG_LEVEL_INFO级别。当我们传递一个日志级别标志时,我们实际上打开了这个日志级别和它之下的所有级别。本例中,我们打开了NS_LOG_INFO, NS_LOG_DEBUG, NS_LOG_WARN 和 NS_LOG_ERROR级别。我们可以通过设置NS_LOG环境变量在不改变脚本或重新编译的情况下来增加日志级别,获得更多信息,
export NS_LOG=UdpEchoClientApplication=level_all
这个设置shell环境变量NS_LOG为字符串:
UdpEchoClientApplication=level_all
等号左边是我们想要设置日志级别的组件的名字,等号右边是我们想要使用的日志级别。本例中,我们要为应用打开所有的调试信息级别。我们把NS_LOG设为这个样子,NS-3日志系统将识别出日志级别改变,输出下面的结果:
Waf: Entering directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build
Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
'build' finished successfully (0.404s)
UdpEchoClientApplication:UdpEchoClient()
UdpEchoClientApplication:SetDataSize(1024)
UdpEchoClientApplication:StartApplication()
UdpEchoClientApplication:ScheduleTransmit()
UdpEchoClientApplication:Send()
Sent 1024 bytes to 10.1.1.2
Received 1024 bytes from 10.1.1.1
UdpEchoClientApplication:HandleRead(0x6241e0, 0x624a20)
Received 1024 bytes from 10.1.1.2
UdpEchoClientApplication:StopApplication()
UdpEchoClientApplication:DoDispose()
UdpEchoClientApplication:~UdpEchoClient()
这些额外的调试信息是来自NS_LOG_FUNTION级别的日志。这些信息显示了在脚本运行期间程序中每个函数调用过程。注意,ns-3中模型对日志的支持并不是必须的。有关被记录信息的多少是由模型的开发者决定的。在本例中,有很多日志输出
你可以看到回显客户端程序中调用函数的日志。如果仔细看,会注意到字符串UdpEchoClientApplication和方法名之间是单冒号,而不是你预期的C++域操作符(::)(双冒号)。这里是有意这样做的。
名字UdpEchoClientApplication并非一个类名,而是日志组件名。当一个类仅由一个源文件代表时,这个位置的显示通常是这个类的名字。这里用一个冒号来替代两个冒号,来提醒用户区分日志组件名和类名的细微差别:这个位置显示的是组件名,而并不是类名。
在某些情况下,确定哪个方法生成了某条日志消息是很困难的。如果你看过上面的文档,你或许想知道字符串“Received 1024 bytes from 10.1.1.2”来自哪里。我们可以通过在NS_LOG环境变量中设置prefix_func级别来解决。试着下面的语句做,
export 'NS_LOG=UdpEchoClientApplication=level_all|prefix_func'
注意,这里引号是必须的,因为我们用的竖线表示或操作,而在Unix中竖线表示管道连接。
现在如果运行脚本你将看到每条日志都有产生此条日志的组件名做前缀了。
Waf: Entering directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
'build' finished successfully (0.417s)
UdpEchoClientApplication:UdpEchoClient()
UdpEchoClientApplication:SetDataSize(1024)
UdpEchoClientApplication:StartApplication()
UdpEchoClientApplication:ScheduleTransmit()
UdpEchoClientApplication:Send()
UdpEchoClientApplication:Send(): Sent 1024 bytes to 10.1.1.2
Received 1024 bytes from 10.1.1.1
UdpEchoClientApplication:HandleRead(0x6241e0, 0x624a20)
UdpEchoClientApplication:HandleRead(): Received 1024 bytes from 10.1.1.2
UdpEchoClientApplication:StopApplication()
UdpEchoClientApplication:DoDispose()
UdpEchoClientApplication:~UdpEchoClient()
你现在看到来自UDP回显客户端程序的消息这样被识别了。消息“Received 1024 bytes from 10.1.1.2”明显来自回显客户端程序。剩下的消息一定是来自UDP回显服务器程序。我们可以通过在NS_LOG环境变量中键入一个单冒号隔开的组件列表来启用回显服务器应用组件。
export 'NS_LOG=UdpEchoClientApplication=level_all|prefix_func:
UdpEchoServerApplication=level_all|prefix_func'
警告:你必须删除单冒号后的换行符,在例子文本中仅仅只是为了编排格式。
现在,如果你运行脚本,你将看到来自回显客户端和服务器的所有日志消息。这对于调试问题很有用。
Waf: Entering directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
'build' finished successfully (0.406s)
UdpEchoServerApplication:UdpEchoServer()
UdpEchoClientApplication:UdpEchoClient()
UdpEchoClientApplication:SetDataSize(1024)
UdpEchoServerApplication:StartApplication()
UdpEchoClientApplication:StartApplication()
UdpEchoClientApplication:ScheduleTransmit()
UdpEchoClientApplication:Send()
UdpEchoClientApplication:Send(): Sent 1024 bytes to 10.1.1.2
UdpEchoServerApplication:HandleRead(): Received 1024 bytes from 10.1.1.1
UdpEchoServerApplication:HandleRead(): Echoing packet
UdpEchoClientApplication:HandleRead(0x624920, 0x625160)
UdpEchoClientApplication:HandleRead(): Received 1024 bytes from 10.1.1.2
UdpEchoServerApplication:StopApplication()
UdpEchoClientApplication:StopApplication()
UdpEchoClientApplication:DoDispose()
UdpEchoServerApplication:DoDispose()
UdpEchoClientApplication:~UdpEchoClient()
UdpEchoServerApplication:~UdpEchoServer()
有时能够看到日志生成的仿真时间也是很有用的。可以通过使用prefix_time位来实现。
export 'NS_LOG=UdpEchoClientApplication=level_all|prefix_func|prefix_time:
UdpEchoServerApplication=level_all|prefix_func|prefix_time'
输入时你必须先移除上面的换行符。如果你现在运行此脚本,你将看到下面的结果:
Waf: Entering directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
'build' finished successfully (0.418s)
0s UdpEchoServerApplication:UdpEchoServer()
0s UdpEchoClientApplication:UdpEchoClient()
0s UdpEchoClientApplication:SetDataSize(1024)
1s UdpEchoServerApplication:StartApplication()
2s UdpEchoClientApplication:StartApplication()
2s UdpEchoClientApplication:ScheduleTransmit()
2s UdpEchoClientApplication:Send()
2s UdpEchoClientApplication:Send(): Sent 1024 bytes to 10.1.1.2
2.00369s UdpEchoServerApplication:HandleRead(): Received 1024 bytes from 10.1.1.1
2.00369s UdpEchoServerApplication:HandleRead(): Echoing packet
2.00737s UdpEchoClientApplication:HandleRead(0x624290, 0x624ad0)
2.00737s UdpEchoClientApplication:HandleRead(): Received 1024 bytes from 10.1.1.2
10s UdpEchoServerApplication:StopApplication()
10s UdpEchoClientApplication:StopApplication()
UdpEchoClientApplication:DoDispose()
UdpEchoServerApplication:DoDispose()
UdpEchoClientApplication:~UdpEchoClient()
UdpEchoServerApplication:~UdpEchoServer()
可以看到UdpEchoServer的构造函数在仿真的第0秒被调用。事实上这实在仿真开始之前就完成了,但时间显示的是0秒。UdpEchoClient的构造函数也是一样。
回忆在scratch/first.cc脚本中,1秒时启动回显服务器应用。可以看到服务器的StartApplication实际上是在1秒时被调用。同样,客户端响应程序正如我们所预料的在仿真2秒时开始。
我们现在可以看到仿真的进度了,我们可以看到从ScheduleTransmit函数在客户端中调用send函数,到回显服务器中调用HandleRead函数的整个过程了。注意到通过点到点连接发送包消耗时间是3.69毫秒。查看回显服务器日志记录了一条消息告诉你已经响应了数据包。在另一个通道延迟后,可以看到响应客户端用它的HandleRead方法收到响应包。
在仿真过程中发生了很多你所没有看到的事情。现在可以很容易的打开系统的日志组件,察看整个过程了。现在试着设置NS_LOG变量为,
export 'NS_LOG=*=level_all|prefix_func|prefix_time'
上面的星号是日志组件通配符。将打开在仿真过程中使用的所有组件的日志功能。这里不列出结果了。可以将这些信息重定向到一个文件,并且用自己喜欢的编辑器打开查看。
./waf --run scratch/myfirst > log.out 2>&1.
当我碰到一个问题或是不知道那里出错了,我使用最详细的日志功能。可以很简单的跟踪程序,而无需设置断点并且在调试器中一步步运行代码。我可以用我喜欢的编辑器来打开查看日至,寻找问题所在。当我对错误有了大致了解之后,我使用调试器对问题有个非常详细的检查。当你脚本做了完全非预期的事,这种输出将是非常有用的。如果你使用调试器单步运行,或许你会错过偏差的部分。日志使得这些偏差非常明显。
5.1.3 为你的代码增加日志功能
可以通过几个宏调用日志组件给仿真增加新的日志功能。我们可以在scratch目录中的myfirst.cc中做。
也许你还记得在脚本中我们已经定义过一个日志组件:
NS_LOG_COMPONENT_DEFINE ("FirstScriptExample");
我们已经了解了通过设置NS_LOG环境变量来给组件启用日志功能。我们可以给脚本增加一些日志功能。用来增加信息级别的日志消息的宏是NS_LOG_INFO 。现在我们来增加一行显示本脚本在创建拓扑的语句。此操作是在以下代码段完成的。
Open scratch/myfirst.cc in your favorite editor and add the line,
用你钟爱的编辑器打开scratch/myfirst.cc文件并且在NodeContainer nodes; nodes.Create(2);之前加上一行,
NS_LOG_INFO ("Creating Topology");
现在用waf编译脚本并且清除NS_LOG环境变量来关掉我们之前启用的日志文件:
./waf
export NS_LOG=
现在,运行脚本,
./waf --run scratch/myfirst
你将不会看到新的日志消息,因为与它相关的日志组件(FirstScriptExample)没有被启用。为了看到你的消息,必须使用大于或等于NS_LOG_INFO 的日志级别来启用FirstScriptExample日志组件。如果只是想要看某个级别的日志,你可以通过下面的语句来启用它,
export NS_LOG=FirstScriptExample=info
如果现在运行脚本你将看到新建的"Creating Topology"日志消息,
Waf: Entering directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
'build' finished successfully (0.404s)
Creating Topology
Sent 1024 bytes to 10.1.1.2
Received 1024 bytes from 10.1.1.1
Received 1024 bytes from 10.1.1.2
5.2 使用命令行参数
5.2.1 重载默认属性
另一个不用编译就可改变ns-3脚本行为的方法就是通过使用命令行参数。我们提供了一种分析命令行属性和基于这些属性自动设置的本地和全局变量的机制。
使用命令行属性系统的第一步就是声明命令行分析器。可以用下面的代码来非常简单的完成。
int
main (int argc, char *argv[])
{
...
CommandLine cmd;
cmd.Parse (argc, argv);
...
}
这两行简单的代码实际上是很有用的。它提供了向ns-3全局变量和属性传递参数的途径。在scratch/myfirst.cc的main函数开始加上这两行代码。编译并运行脚本,同时可以根据下面这两行向脚本寻求帮助。
./waf --run "scratch/myfirst --PrintHelp"
这个命令是让Waf运行scratch/myfirst脚本并且通过命令行参数--PrintHelp。引号需要用来确定那个程序得到那个参数。命令行分析器将查看--PrintHelp参数并返回一下内容,
Waf: Entering directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
'build' finished successfully (0.413s)
TcpL4Protocol:TcpStateMachine()
CommandLine:HandleArgument(): Handle arg name=PrintHelp value=
--PrintHelp: Print this help message.
--PrintGroups: Print the list of groups.
--PrintTypeIds: Print all TypeIds.
--PrintGroup=[group]: Print all TypeIds of group.
--PrintAttributes=[typeid]: Print all attributes of typeid.
--PrintGlobals: Print the list of globals.
下面来关注--PrintAtt ributes选项。在我们查看first.cc脚本时,已经提到过ns-3属性系统。
PointToPointHelper pointToPoint;
pointToPoint.SetDeviceAttribute ("DataRate", StringValue ("5Mbps"));
pointToPoint.SetChannelAttribute ("Delay", StringValue ("2ms"));
我们提到DataRate事实上是PointToPointNetDevice的一个属性。现在使用命令行参数分析器来看一下PointToPointNetDevice的属性。帮助列表需要我们提供一个TypeId。本例中,这个TypeId是ns3::PointToPointNetDevice。然后我们可以键入,
./waf --run "scratch/myfirst --PrintAttributes=ns3::PointToPointNetDevice"
系统将显示出网络设备这种所有属性。你将看到,
--ns3::PointToPointNetDevice::DataRate=[32768bps]:
The default data rate for point to point links
当系统中的PointToPointNetDevice被创立时,DataRate将是一个默认值。我们的脚本用PointToPointHelper重载了这个默认值。现在让我们删除Scratch目录中myfirst.cc的SetDeviceAtt ribute和the SetChannelAtt ribute调用来使用端到端设备使用默认值。
你的脚本现在只需要声明PointToPointHelper而并不做任何设置,
NodeContainer nodes;
nodes.Create (2);
PointToPointHelper pointToPoint;
NetDeviceContainer devices;
devices = pointToPoint.Install (nodes);
使用Waf(./waf)运行你的脚本,启用UDP回显服务器应用的一些日志并且打开时间前缀。
export 'NS_LOG=UdpEchoServerApplication=level_all|prefix_time'
运行脚本,可以看到下面的结果,
Waf: Entering directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
'build' finished successfully (0.405s)
0s UdpEchoServerApplication:UdpEchoServer()
1s UdpEchoServerApplication:StartApplication()
Sent 1024 bytes to 10.1.1.2
2.25732s Received 1024 bytes from 10.1.1.1
2.25732s Echoing packet
Received 1024 bytes from 10.1.1.2
10s UdpEchoServerApplication:StopApplication()
UdpEchoServerApplication:DoDispose()
UdpEchoServerApplication:~UdpEchoServer()
回忆上次响应服务器收到数据包的仿真时间,是2.00369秒,
2.00369s UdpEchoServerApplication:HandleRead(): Received 1024 bytes from 10.1.1.1
现在在2.25732秒收到数据包。这是因为我们将PointToPointNetDevice的数据传输率从每秒5Mbits降到了每秒32768bits。
如果我们采用命令行来改用一个新的DataRate,能够加速我们的仿真速度。我们可以根据帮助文档中指明的规则,按照下面的方式来做:
./waf --run "scratch/myfirst --ns3::PointToPointNetDevice::DataRate=5Mbps"
这个将DataRate属性的默认值设置回每秒5Mbits。你对结果是不是感到很惊讶?为了设置回脚本最初的行为,我们必须将信道设成光速的延迟。我们可以让命令行系统按照我们刚为网络设备做的那样显示出信道属性:
./waf --run "scratch/myfirst --PrintAttributes=ns3::PointToPointChannel"
我们发现信道的Delay属性是通过下面这种方法设置的,
--ns3::PointToPointChannel::Delay=[0ns]:
Transmission delay through the channel
我们可以通过命令行来设置这两个默认值。
./waf --run "scratch/myfirst
--ns3::PointToPointNetDevice::DataRate=5Mbps
--ns3::PointToPointChannel::Delay=2ms"
这样在脚本中精确地设置好了DataRate和Delay,我们会重新得到以前的时间:
Waf: Entering directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
'build' finished successfully (0.417s)
0s UdpEchoServerApplication:UdpEchoServer()
1s UdpEchoServerApplication:StartApplication()
Sent 1024 bytes to 10.1.1.2
2.00369s Received 1024 bytes from 10.1.1.1
2.00369s Echoing packet
Received 1024 bytes from 10.1.1.2
10s UdpEchoServerApplication:StopApplication()
UdpEchoServerApplication:DoDispose()
UdpEchoServerApplication:~UdpEchoServer()
这样,仿真恢复到了在2.00369秒服务器端收到数据包。我们可以用这样的方法来设置脚本中的任何属性。特别的我们可以将UdpEchoClient接收到的MaxPackets属性设置为1以外的其他值。
下面请读者尝试一下。记住必须要注释掉我们重载默认属性的地方,并且设置的MaxPackets属性。还必须重新编译脚本。你必须使用命令行的帮助功能来找到设置新的默认属性值得语法格式。一旦设置好,就可以从命令行控制回显数据包数目。作为例子,我们这里给出了命令行的设置。
./waf --run "scratch/myfirst
--ns3::PointToPointNetDevice::DataRate=5Mbps
--ns3::PointToPointChannel::Delay=2ms
--ns3::UdpEchoClient::MaxPackets=2"
5.2.2 钩挂自己的参数值
你可以在命令行系统中增加自己的钩子。可以通过使用命令分析器中的AddValue方法非常简单的实现。
我们用与以前一个完全不同的方法即钩挂的方法来指定回显数据包数。首先我们增加一个局部变量nPacket。我们将其默认值初始化为1,以便和以前的情形保持一致。为了允许命令行分析器来改变这个值,我们需要将这个值钩挂到分析器上。我们需要增加一个AddValue调用。现在修改scratch/myfirst.cc脚本,将以下面的代码放在开始处,
int
main (int argc, char *argv[])
{
uint32_t nPackets = 1;
CommandLine cmd;
cmd.AddValue("nPackets", "Number of packets to echo", nPackets);
cmd.Parse (argc, argv);
...
向下拉到我们设置MaxPackets属性的地方并把它来从1改到nPacket,如下所示,
echoClient.SetAttribute ("MaxPackets", UintegerValue (nPackets));
现在可以运行脚本,并且采用了--PrintHelp参数,你需要在帮助列表中查看新User参数。
尝试,
./waf --run "scratch/myfirst --PrintHelp"
Waf: Entering directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
'build' finished successfully (0.403s)
--PrintHelp: Print this help message.
--PrintGroups: Print the list of groups.
--PrintTypeIds: Print all TypeIds.
--PrintGroup=[group]: Print all TypeIds of group.
--PrintAttributes=[typeid]: Print all attributes of typeid.
--PrintGlobals: Print the list of globals.
User Arguments:
--nPackets: Number of packets to echo
如果你想指明回显数据包个数,可以在命令行设置nPackets属性,
./waf --run "scratch/myfirst --nPackets=2"
你应该会看到
Waf: Entering directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
'build' finished successfully (0.404s)
0s UdpEchoServerApplication:UdpEchoServer()
1s UdpEchoServerApplication:StartApplication()
Sent 1024 bytes to 10.1.1.2
2.25732s Received 1024 bytes from 10.1.1.1
2.25732s Echoing packet
Received 1024 bytes from 10.1.1.2
Sent 1024 bytes to 10.1.1.2
3.25732s Received 1024 bytes from 10.1.1.1
3.25732s Echoing packet
Received 1024 bytes from 10.1.1.2
10s UdpEchoServerApplication:StopApplication()
UdpEchoServerApplication:DoDispose()
UdpEchoServerApplication:~UdpEchoServer()
从日志输出中我们可以看到已经回显了两个数据包。一切都非常简单吧!
通过以上的介绍我们可以看出:如果你是ns-3用户,你就可以使用命令行参数系统来控制全局变量和对象属性。如果你是一个模型作者,你可以为你的对象增加新属性,而模型的用户可以通过命令行系统来设置这些新属性的值。如果你是脚本作者,你可以为你的脚本增加新变量,同时轻易的将它们钩挂在命令行系统下。
5.3 使用Tracing系统
整个仿真的目的是为了进一步研究生成结果,ns-3 的tracing系统就是为了这个目的而制的。ns-3是一个c++程序,c++的标准输入输出设施在ns3种都可以使用:
#include <iostream>
...
int main ()
{
...
std::cout << "The value of x is " << x << std::endl;
...
}
你甚至可以使用日志模块来为你的解决方案增加小的结构。但这种方法会产生很多已知的问题,因此我们提供了一种生成事件tracing的子系统来解决我们认为重要的问题。
The basic goals of the ns-3 tracing system are:
Ns-3 tracing系统的基本目标是:
· 对于基本的任务,tracing系统允许用户为常用的tracing发送端生成标准的tracing,可以定制哪些个对象生成tracing;
· 中间用户必须能够能够通过扩展tracing系统来修改生成的输出格式,或是在不修改仿真器核心的情况下插入新的tracing发送端;
· 高级用户可以修改仿真器的核心来增加新的tracing发送端和接收端。
Ns-3 tracing系统是建立在独立的tracing发送端和tracing接收端的概念上的,并且有统一的机制来连接发送端和接收端。Trace发送端是可以在仿真过程中产生信号事件并且提供有关数据访问通道。例如,一个trace发送端可以提供一个数据包被一个网络设备接收的时间并且根据接收端的要求提供此数据包的内容。
Trace发送端自身是没有用的,必须要跟接收端提供有用信息的代码段“相连”。Trace接收端是trace发送端提供数据和事件的使用者。例如,可以创建一个输出接收数据包有用部分的trace接收端(当连接到之前例子的trace发送端)。
这种发送端和接收端工作的基本原理是允许用户给已有的tracing发送端关联上新类型的接收端,而不需要编辑和重新编译仿真器的核心。因此,以上面为例,一个用户可以在脚本中定义新的tracing接收端,并且可以将其关联到仿真核心中定义的tracing接收端。
本教程中,我们将展示之前定义好的发送端和接收端,并且示范用户怎样轻易的定制它们。如需扩展tracing命名空间,和创建新tracing发送端的高级的tracing配置,请查看ns3手册。
5.3.1 ASCII 码Tracing
ns-3提供了封装底层tracing系统的帮助功能,用来提供配置简单数据包trace得更多细节。如果你开启了这个功能,将在ASCII文件中输出结果,这就是为什么这种tracing叫ASCII tracing。对于熟悉ns-2的用户,这种trace类型是跟ns2生成的out.tr相类似的。
我们开始并为我们的脚本增加一些ASCII tracing结果。
首先需要做的是在脚本的GNU GPL说明之后加上下面的包含语句:
#include <fstream>
然后,在Simulator::Run()前增加下面的代码:
std::ofstream ascii;
ascii.open ("myfirst.tr");
PointToPointHelper::EnableAsciiAll (ascii);
前两行只是很平常的C++代码,用来打开一个将被写入名为myfirst.tr文件中的数据流。如果对这段代码不熟悉,可以查看你所钟爱的C++教程。代码段中的最后一行代码告诉ns-3在仿真中为所有点到点设备启用ASCII tracing功能;并且你想要用ASCII格式来写出数据流中数据包的移动信息。类似于ns-2,trace事件等同于流行的trace“+”、“-”、“d”和“r”事件。
可以编译脚本并用下面的命令行运行:
./waf --run scratch/myfirst
跟你之前看到很多次的一样,你将看到来自Waf的信息和编译成功信息。
脚本运行时,程序将创建一个名为myfirst.tr的文件。由于Waf的工作方式,所以这个文件将不在本地目录下创建,而是在ns3根目录中创建。如果你想改变trace文件的保存目录,你可以使用Waf的--cwd选项来指定。而我们还没有这么做,因此我们需要转到ns3的根目录,并且用你喜欢的编辑器来查看ASCII trace文件myfirst.tr。
5.3.1.1 分析Ascii Traces
在一个极其密集的文档中有很多信息,但是需要注意的第一件事是在这个文件中有很多分立的行。除非你大大加宽你的窗口,否则是很难看清楚的。
每一行对应了一个trace事件。本例中我们在查看每个点到点设备的传输队列的trace事件。传输队列是任一个目的地为点到点信道的数据包的必经队列。注意trace文件的每行以一个单独的字符开始(后面带有空格)。这个字符具有如下含义:
· +: An enqueue operation occurred on the device queue;
· -: A dequeue operation occurred on the device queue;
· d: A packet was dropped, typically because the queue was full;
· r: A packet was received by the net device.
· +:设备队列中的入队操作;
· -:设备队列中的出队操作;
· d:数据包被丢弃,通常因为队列已满;
· r:网络设备接收到数据包。
我们来更详细的看一下trace文件的第一行。为了看得更清晰,我把这一行分成了不同的部分,并在左边标出序号:
00 +
01 2
02 /NodeList/0/DeviceList/0/$ns3::PointToPointNetDevice/TxQueue/Enqueue
03 ns3::PppHeader (
04 Point-to-Point Protocol: IP (0x0021))
05 ns3::Ipv4Header (
06 tos 0x0 ttl 64 id 0 protocol 17 offset 0 flags [none]
07 length: 1052 10.1.1.1 > 10.1.1.2)
08 ns3::UdpHeader (
09 length: 1032 49153 > 9)
10 Payload (size=1024)
Trace的下一行(序号02)告诉我们trace发送端发起这个事件(以tracing命名空间表示)。你可以认为tracing命名空间有点像一个文件系统命名空间。命名空间的根为NodeList。这个NodeList是NS-3核心代码管理的一个容器,此容器包含有一个脚本中创建的所有的节点。。正如一个文件系统在根下有目录,在NodeList下有节点数。字符串/NodeList/0是指NodeList中第0个节点,我们通常认为是"node 0".每个节点中有一个已经安装好的设备列表。这个列表是在命名空间的下一个出现的。可以看到trace事件来自节点中安装的第0个设备DeviceList/0。
下一个字符串,$ns3::PointToPointNetDevice告诉我们第0个节点的设备列表的第0个位置的设备类型。回忆序号00处的+操作表示设备的传输队列发生了入队操作,这个在"trace path"TxQueue/Enqueue的最后部分反映出来了。
Trace中剩下的几行是很直观的。序号03-04处表明数据包封装成点到点协议。序号05-07处显示数据包IP版本,发送端IP地址10.1.1.1,接收端IP地址为10.1.1.2。序号08-09出显示数据包的UDP头,最后序号10处表明数据包数据量为1024bytes。
在trace文件中的下一行显示了这个数据包在这个节点中从传输队列中被移除。
Trace文件的第三行显示了数据包正在被回显服务器所在的节点的网络设备接收。trace如下。
00 r
01 2.25732
02 /NodeList/1/DeviceList/0/$ns3::PointToPointNetDevice/MacRx
03 ns3::Ipv4Header (
04 tos 0x0 ttl 64 id 0 protocol 17 offset 0 flags [none]
05 length: 1052 10.1.1.1 > 10.1.1.2)
06 ns3::UdpHeader (
07 length: 1032 49153 > 9)
08 Payload (size=1024)
注意,trace操作现在是r并且仿真时间已经增加到2.25732秒。如果你一直按照本教程来操作,你已经把网络设备的DataRate,和信道Delay设置成默认值。那么对于这个值,你应该觉得眼熟,因为上一章中已经见过这个值。
在第2行中,Trace发送端命名空间条目已经改变,来显示这个事件是来自节点1(/NodeList/1),即数据包的接收trace端(/MacRx)。通过查看文件中其他的traces,你可以很容易的跟踪数据包。
5.3.2 PCAP 格式Tracing
ns-3设备控制器也可以被用来创建.pcap格式的trace文件。缩写pcap(通常为小写)表示packet capture,事实上是包含有定义一个.pcap文件格式的API 。可以读取并且显示这种格式的最流行的程序是Wireshark(以前被称为Ethereal)。然而,有很多其他的分析器也使用这个包格式。我们鼓励读者了解其他的分析pcap追踪文件的工具。在本教程中,我们用tcpdump来查看pcap trace。
用来启用pcap tracing的代码只有一行,
PointToPointHelper::EnablePcapAll ("myfirst");
在scratch/myfirst.cc中我们刚增加的ASCII追踪代码后面插入这行代码。注意我们刚使用的是字符串''myfirst"而不是"myfirst.pcap"。这是因为这里传递的参数是个前缀,而不是完整的文件名。在仿真过程中,helper将为任何一个点到点设备创建一个追踪文件。文件名将包含预设前缀,节点名,设备名,和".pcap"后缀。
在我们的例子脚本中,我们最终将看到名为"myfirst-0-0.pcap"和"myfirst-1-0.pcap"。这分别是为节点0设备0,和节点1设备0创建的pcap trace文件。
一旦你已经增加了启用pcap tracing的代码行,你可以以下面的方式来运行脚本:
./waf --run scratch/myfirst
如果你查看ns3根目录,你将看到三个日志文件:myfirst.tr是我们之前看到过的ASCII trace文件。Myfirst-0-0.pcap和myfirst-1-0.pcap是我们刚生成的新的pcap文件。
5.3.2.1 用tcpdump读取结果
此处最简单的做法就是使用tcpdump来查看pcap文件,
tcpdump -nn -tt -r myfirst-0-0.pcap
reading from file myfirst-0-0.pcap, link-type PPP (PPP)
2.000000 IP 10.1.1.1.49153 > 10.1.1.2.9: UDP, length 1024
2.514648 IP 10.1.1.2.9 > 10.1.1.1.49153: UDP, length 1024
tcpdump -nn -tt -r myfirst-1-0.pcap
reading from file myfirst-1-0.pcap, link-type PPP (PPP)
2.257324 IP 10.1.1.1.49153 > 10.1.1.2.9: UDP, length 1024
2.257324 IP 10.1.1.2.9 > 10.1.1.1.49153: UDP, length 1024
在myfirst-0-0.pcap(客户端设备)文件中可以看到回显数据包在第二秒被发送。如果查看第二个文件(myfirst-1-0.pcap)可以看到此包在2.257324秒被收到。在第二个文件中可以看到在2.257324秒这个包被回应,最终,在客户端2.514648秒回应的包被收到。
5.3.2.2 用Wireshark读取结果
Wireshark是一个可以用来显示trace文件的用户图形接口。如果你安装了Wireshark,可以打开和查看每一个trace文件,就像这些trace是使用包嗅探工具在真正的网络上抓下来一样。