NS-3学习笔记(十一):命令行

本章主要介绍NS-3当中的命令行。在NS-3当中,我们经常需要对属性进行设置,除了直接对单个已经创建的对象进行属性操作之外,我们还可以通过配置路径进行批量配置。配置路径可以和命令行参数结合使用,在启动脚本的同时传入命令行参数来进行设置,避免在改变属性设置等的时候要重新编译脚本的尴尬。除此之外,也可以使用命令行来改变程序当中变量的值,也可以避免因修改代码而重新编译脚本。

1. NS3命令行简介

在一个正常的仿真脚本当中,需要对各种情况进行仿真,因此需要不断的改变属性的值,来创建不同的仿真场景。然而改变了属性的值之后,整个仿真脚本就得重新编译,比较繁琐。NS3提供了一种机制,可以使用命令行的参数来改变属性的值,此时就无需重新编译脚本来仿真各种场景。这种机制主要通过命令行类来实验。除了控制已有的NS3对象属性之外,命令行也可以通过参数来动态的改变变量的值。使得我们控制各种变量的时候无需重新编译脚本。

2. 使用命令行类解析命令行参数

要使用命令行类比较简单,只要在脚本当中创建命令行类,并且调用其解析方法,就可以解析命令行参数。例如下面的例子当中:

try-command-line.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include "ns3/core-module.h"

using namespace ns3;

NS_LOG_COMPONENT_DEFINE ("TryCommandLine");

int
main (int argc, char *argv[])
{
LogComponentEnable("TryCommandLine", LOG_LEVEL_LOGIC);

CommandLine cmd;
cmd.Parse(argc, argv);

}

只能创建命令行类,然后将程序的命令行参数传给解析方法即可。此时我们的程序已经可以解析命令行参数。NS3命令行类内置了一些参数,我们可以使用–PrintHelp参数来查看。例如我们输入如下命令运行上面的程序,并添加–PrintHelp参数:

1
$ ./waf --run "try-command-line --PrintHelp"

需要注意的是,如果程序带有命令行参数,那么程序名和传递给程序的命令行参数必须有双引号引起。程序运行后可以得到如下结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Waf: Entering directory `/home/rainsia/Applications/ns-allinone-3.29/ns-3.29/build'
[1922/2017] Compiling scratch/try-command-line.cc
[1977/2017] Linking build/scratch/try-command-line
Waf: Leaving directory `/home/rainsia/Applications/ns-allinone-3.29/ns-3.29/build'
Build commands will be stored in build/compile_commands.json
'build' finished successfully (38.020s)
try-command-line [General Arguments]

General Arguments:
--PrintGlobals: Print the list of globals.
--PrintGroups: Print the list of groups.
--PrintGroup=[group]: Print all TypeIds of group.
--PrintTypeIds: Print all TypeIds.
--PrintAttributes=[typeid]: Print all attributes of typeid.
--PrintHelp: Print this help message.

从程序的运行结果可以看出,NS3命令行类默认可以解析,一些命令行参数,除了“–PrintHelp”之外,还有:

  • –PrintGlobals:打印全局变属性
  • –PrintGroups:打印所有的组
  • –PrintGroup=[group]:打印一个组当中所有的TypeId
  • –PrintTypeIds:打印所有的TypeId
  • –PrintAttributes=[typeid]:打印一个TypeId当中所有的属性
  • –PrintHelp:打印命令行帮助

如果有兴趣这些命令都请自行尝试,我们仅仅以PrintGroups、PrintGroup即PrintAttributes为例讲解命令行参数的使用。

首先,我们可以使用–PrintGroups打印出系统中所有的组(组是在创建TypeId的时候通过SetGroupName()方法设置的,目的是将不同的类分到不同的组当中,仅仅为了方便组织,无实际意义):

close
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
$ ./waf –run "try-command-line –PrintGroups"
Waf: Entering directory /home/rainsia/Applications/ns-allinone-3.29/ns-3.29/build&apos;</span><br><span class="line">Waf: Leaving directory/home/rainsia/Applications/ns-allinone-3.29/ns-3.29/build'
Build commands will be stored in build/compile_commands.json
'build' finished successfully (33.420s)
Registered TypeId groups:

Antenna
Aodv
Applications
Bridge
Buildings
ConfigStore
Core
Csma
Dsdv
Dsr
Energy
FdNetDevice
FlowMonitor
Internet
Internet-Apps
LrWpan
Lte
Mesh
Mobility
Mpi
Network
NixVectorRouting
Olsr
PointToPoint
Propagation
SixLowPan
Spectrum
Stats
TapBridge
TopologyReader
TrafficControl
Uan
VirtualNetDevice
Wave
Wifi
Wimax

随后可以使用–PrintGroup=group来查看组内的类的详情,例如我们查看Internet这个组当中所有的类的TypeId:

close
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
$ ./waf –run "try-command-line –PrintGroup=Internet"
Waf: Entering directory /home/rainsia/Applications/ns-allinone-3.29/ns-3.29/build&apos;</span><br><span class="line">Waf: Leaving directory/home/rainsia/Applications/ns-allinone-3.29/ns-3.29/build'
Build commands will be stored in build/compile_commands.json
'build' finished successfully (33.175s)
TypeIds in group Internet:
ns3::ArpCache
ns3::ArpHeader
ns3::ArpL3Protocol
ns3::GlobalRouter
ns3::Icmpv4DestinationUnreachable
ns3::Icmpv4Echo
ns3::Icmpv4Header
ns3::Icmpv4L4Protocol
ns3::Icmpv4TimeExceeded
ns3::Icmpv6DestinationUnreachable
ns3::Icmpv6Echo
ns3::Icmpv6Header
ns3::Icmpv6L4Protocol
ns3::Icmpv6NA
ns3::Icmpv6NS
ns3::Icmpv6OptionHeader
ns3::Icmpv6OptionLinkLayerAddress
ns3::Icmpv6OptionMtu
ns3::Icmpv6OptionPrefixInformation
ns3::Icmpv6OptionRedirected
ns3::Icmpv6ParameterError
ns3::Icmpv6RA
ns3::Icmpv6RS
ns3::Icmpv6Redirection
ns3::Icmpv6TimeExceeded
ns3::Icmpv6TooBig
ns3::IpL4Protocol
ns3::Ipv4
ns3::Ipv4GlobalRouting
ns3::Ipv4Header
ns3::Ipv4Interface
ns3::Ipv4L3Protocol
ns3::Ipv4ListRouting
ns3::Ipv4PacketFilter
ns3::Ipv4PacketProbe
ns3::Ipv4RawSocketFactory
ns3::Ipv4RawSocketImpl
ns3::Ipv4RoutingProtocol
ns3::Ipv4StaticRouting
ns3::Ipv6
ns3::Ipv6Extension
ns3::Ipv6ExtensionAH
ns3::Ipv6ExtensionAHHeader
ns3::Ipv6ExtensionDemux
ns3::Ipv6ExtensionDestination
ns3::Ipv6ExtensionDestinationHeader
ns3::Ipv6ExtensionESP
ns3::Ipv6ExtensionESPHeader
ns3::Ipv6ExtensionFragment
ns3::Ipv6ExtensionFragmentHeader
ns3::Ipv6ExtensionHeader
ns3::Ipv6ExtensionHopByHop
ns3::Ipv6ExtensionHopByHopHeader
ns3::Ipv6ExtensionLooseRouting
ns3::Ipv6ExtensionLooseRoutingHeader
ns3::Ipv6ExtensionRouting
ns3::Ipv6ExtensionRoutingDemux
ns3::Ipv6ExtensionRoutingHeader
ns3::Ipv6Header
ns3::Ipv6Interface
ns3::Ipv6L3Protocol
ns3::Ipv6ListRouting
ns3::Ipv6Option
ns3::Ipv6OptionDemux
ns3::Ipv6OptionHeader
ns3::Ipv6OptionJumbogram
ns3::Ipv6OptionJumbogramHeader
ns3::Ipv6OptionPad1
ns3::Ipv6OptionPad1Header
ns3::Ipv6OptionPadn
ns3::Ipv6OptionPadnHeader
ns3::Ipv6OptionRouterAlert
ns3::Ipv6OptionRouterAlertHeader
ns3::Ipv6PacketFilter
ns3::Ipv6PacketProbe
ns3::Ipv6PmtuCache
ns3::Ipv6RawSocketFactory
ns3::Ipv6RawSocketImpl
ns3::Ipv6RoutingProtocol
ns3::Ipv6StaticRouting
ns3::LoopbackNetDevice
ns3::NdiscCache
ns3::Rip
ns3::RipHeader
ns3::RipNg
ns3::RipNgHeader
ns3::RipNgRte
ns3::RipRte
ns3::RttEstimator
ns3::RttMeanDeviation
ns3::TcpBic
ns3::TcpClassicRecovery
ns3::TcpCongestionOps
ns3::TcpHeader
ns3::TcpHighSpeed
ns3::TcpHtcp
ns3::TcpHybla
ns3::TcpIllinois
ns3::TcpL4Protocol
ns3::TcpLedbat
ns3::TcpLp
ns3::TcpNewReno
ns3::TcpOption
ns3::TcpOptionEnd
ns3::TcpOptionMSS
ns3::TcpOptionNOP
ns3::TcpOptionSack
ns3::TcpOptionSackPermitted
ns3::TcpOptionTS
ns3::TcpOptionUnknown
ns3::TcpOptionWinScale
ns3::TcpPrrRecovery
ns3::TcpRecoveryOps
ns3::TcpRxBuffer
ns3::TcpScalable
ns3::TcpSocket
ns3::TcpSocketBase
ns3::TcpSocketFactory
ns3::TcpSocketState
ns3::TcpTxBuffer
ns3::TcpVegas
ns3::TcpVeno
ns3::TcpWestwood
ns3::TcpYeah
ns3::UdpHeader
ns3::UdpL4Protocol
ns3::UdpSocket
ns3::UdpSocketFactory
ns3::UdpSocketImpl

可见,所有属于Internet这个组的类全都被列了出来。如果我们对某个类比较感兴趣,可以进一步使用–PrintAttributes=typeid将其详细的属性信息列举出来。例如,我们对TcpSocketBase这个类比较感兴趣,想看看其中有什么属性,可以使用如下命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
$ ./waf --run "try-command-line --PrintAttributes=ns3::TcpSocketBase"
Waf: Entering directory `/home/rainsia/Applications/ns-allinone-3.29/ns-3.29/build'
Waf: Leaving directory `/home/rainsia/Applications/ns-allinone-3.29/ns-3.29/build'
Build commands will be stored in build/compile_commands.json
'build' finished successfully (31.438s)
Attributes for TypeId ns3::TcpSocketBase
--ns3::TcpSocketBase::ClockGranularity=[+1000000.0ns]
Clock Granularity used in RTO calculations
--ns3::TcpSocketBase::EcnMode=[NoEcn]
Determines the mode of ECN
--ns3::TcpSocketBase::IcmpCallback6=[0]
Callback invoked whenever an icmpv6 error is received on this socket.
--ns3::TcpSocketBase::IcmpCallback=[0]
Callback invoked whenever an icmp error is received on this socket.
--ns3::TcpSocketBase::LimitedTransmit=[true]
Enable limited transmit
--ns3::TcpSocketBase::MaxSegLifetime=[120]
Maximum segment lifetime in seconds, use for TIME_WAIT state transition to CLOSED state
--ns3::TcpSocketBase::MaxWindowSize=[65535]
Max size of advertised window
--ns3::TcpSocketBase::MinRto=[+1000000000.0ns]
Minimum retransmit timeout value
--ns3::TcpSocketBase::ReTxThreshold=[3]
Threshold for fast retransmit
--ns3::TcpSocketBase::RxBuffer=[0]
TCP Rx buffer
--ns3::TcpSocketBase::Sack=[true]
Enable or disable Sack option
--ns3::TcpSocketBase::Timestamp=[true]
Enable or disable Timestamp option
--ns3::TcpSocketBase::TxBuffer=[0]
TCP Tx buffer
--ns3::TcpSocketBase::WindowScaling=[true]
Enable or disable Window Scaling option

可见,程序列出了所有TcpSocketBase类支持的属性。每一项属性都展示了其名称、默认值和帮助信息。

需要注意的是,这里列出的属性信息和我们通过API文档查询到的属性信息是一致的。(那还不如去查文档?)但是,自己创建的类、引用了第三方的类或者修改过NS3系统自带的类,自行添加的属性文档上是不可能会有的,只能通过这种方式查询。

3. 使用命令行设置属性的默认值

命令行参数可以覆盖属性的默认值,只需要在命令行参数当中指出具体的类的全名(TypeId中设置的名称)和需要覆盖的属性,然后再对其赋值。程序中就可以不再对属性设置任何的值,而所有以该类创建的所有的对象都将具有设置的默认值。

例如下面的例子当中:

try-command-line-attribute.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include "ns3/core-module.h"

using namespace ns3;

NS_LOG_COMPONENT_DEFINE ("TryCommandLineAttribute");


class MyObject : public Object
{
public:
static TypeId GetTypeId ();

MyObject ();
virtual ~MyObject ();

uint32_t GetSample () const
{
return m_sample;
}

protected:
virtual void DoInitialize ();
virtual void DoDispose ();

private:
uint32_t m_sample {0};
};

NS_OBJECT_ENSURE_REGISTERED(MyObject);

TypeId
MyObject::GetTypeId ()
{
static TypeId tid = TypeId("ns3::MyObject")
.SetParent(Object::GetTypeId())
.AddConstructor<MyObject>()
.AddAttribute ("AttributeA", "An attribute",
UintegerValue(0),
MakeUintegerAccessor (&MyObject::m_sample),
MakeUintegerChecker <uint32_t>())
;
return tid;
}

MyObject::MyObject ()
{
}

MyObject::~MyObject ()
{
}

void
MyObject::DoInitialize ()
{
Object::DoInitialize ();
}

void
MyObject::DoDispose ()
{
Object::DoDispose ();
}

int
main (int argc, char *argv[])
{
LogComponentEnable("TryCommandLineAttribute", LOG_LEVEL_LOGIC);

CommandLine cmd;
cmd.Parse(argc, argv);

Ptr<MyObject> obj = CreateObject<MyObject>();
NS_LOG_UNCOND(obj->GetSample());
}

定义了一个对象MyObject,其中定义了一个属性AttributeA。主函数当中,程序解析了命令行参数,随后创建了一个MyObject类的实例对象obj,并输出了其AttributeA属性背后的成员变量的值。如果我们运行程序的时候,不输入命令行参数,那么属性值将是默认值:

1
2
3
4
5
./waf --run "try-command-line-attribute"Waf: Entering directory `/home/rainsia/Applications/ns-allinone-3.29/ns-3.29/build'
Waf: Leaving directory `/home/rainsia/Applications/ns-allinone-3.29/ns-3.29/build'
Build commands will be stored in build/compile_commands.json
'build' finished successfully (31.193s)
0

然而,我们再不用修改和重新编译程序的情况下,只需要指定命令行参数,即可修改属性的默认值。例如我们使用如下的命令运行程序:

1
2
3
4
5
6
$ ./waf --run "try-command-line-attribute --ns3::MyObject::AttributeA=199"
Waf: Entering directory `/home/rainsia/Applications/ns-allinone-3.29/ns-3.29/build'
Waf: Leaving directory `/home/rainsia/Applications/ns-allinone-3.29/ns-3.29/build'
Build commands will be stored in build/compile_commands.json
'build' finished successfully (32.016s)
199

这可以巧妙地解决每次修改程序的配置都必须重新编译程序的问题。然而这只适用与所有对象都有同样属性值的情况。要修改某一个实例的属性值用这种方式是不行的,只能通过Config::Set()方法使用配置路径修改。

然而这种方式最大的问题在于,每次需要设置属性的时候,都需要写属性的完全的配置路径(例如ns3::MyObject::AttributeA=199),相对来说比较繁琐。因此NS3提供了一种相对简化的写法,可以将较长的配置路径映射成一个较短的名称。只要调用CommandLine实例对象的AddValue()方法:

command-line.cc
1
void AddValue(const std::string & name, const std::string & attributePath);

你可将一个较长的属性的配置路径attributePath映射成为一个较短的名字name。例如下面的例子:

try-command-line-attribute.cc
1
2
3
4
5
6
7
8
9
10
11
12
int 
main (int argc, char *argv[])
{
LogComponentEnable("TryCommandLineAttribute", LOG_LEVEL_LOGIC);

CommandLine cmd;
cmd.AddValue("attrA", "ns3::MyObject::AttributeA");
cmd.Parse(argc, argv);

Ptr<MyObject> obj = CreateObject<MyObject>();
NS_LOG_UNCOND(obj->GetSample());
}

我们将较长的属性配置路径“ns3::MyObject::AttributeA”映射成了一个较短的名字“attrA”。在传递命令行参数的时候,只要使用较短的名字就可以替代比较复杂的属性路径。因此,我们可以使用如下的命令运行程序,达到和之前同样的效果:

1
2
3
4
5
6
7
8
$ ./waf --run "try-command-line-attribute --attrA=199"
Waf: Entering directory `/home/rainsia/Applications/ns-allinone-3.29/ns-3.29/build'
[1934/2019] Compiling scratch/try-command-line-attribute.cc
[1979/2019] Linking build/scratch/try-command-line-attribute
Waf: Leaving directory `/home/rainsia/Applications/ns-allinone-3.29/ns-3.29/build'
Build commands will be stored in build/compile_commands.json
'build' finished successfully (33.696s)
199

当然做了重新映射之后,原来的属性配置路径依然有效。使用这种映射的时候需要注意的是AddValue()方法必须在Parse()方法之前调用才有效。

除了会当中的属性之外,命令行参数也可以设置全局属性的默认值。其设置方法和类的属性默认值一致。例如,下面的例子:

try-command-line-global-attribute.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "ns3/core-module.h"

using namespace ns3;

NS_LOG_COMPONENT_DEFINE ("TryCommandLineGlobalAttribute");

int
main (int argc, char *argv[])
{
LogComponentEnable("TryCommandLineGlobalAttribute", LOG_LEVEL_LOGIC);

GlobalValue uint = GlobalValue ("GlobalAttribute", "Test global attribute",
UintegerValue (10),
MakeUintegerChecker<uint32_t> ());

CommandLine cmd;
cmd.Parse(argc, argv);

UintegerValue value;
uint.GetValue(value);
NS_LOG_UNCOND(value.Get());
}

程序中创建了一个全局属性GlobalAttribute,然后解析命令行参数,如果命令行参数当中指定了全局属性,那么全局属性的默认值将被覆盖。使用如下命令覆盖全剧属性的值:

1
2
3
4
5
6
7
8
$ ./waf --run "try-command-line-global-attribute --GlobalAttribute=188"
Waf: Entering directory `/home/rainsia/Applications/ns-allinone-3.29/ns-3.29/build'
[1942/2021] Compiling scratch/try-command-line-global-attribute.cc
[1981/2021] Linking build/scratch/try-command-line-global-attribute
Waf: Leaving directory `/home/rainsia/Applications/ns-allinone-3.29/ns-3.29/build'
Build commands will be stored in build/compile_commands.json
'build' finished successfully (41.670s)
188

4. 使用命令行设置变量的值

除了可以使用命令行参数来设置类的属性默认值之外,还可以使用命令行参数来改变全局变量或者局部变量的默认值。在设置变量的默认值之前必须通过AddValue()方法将一个名称和变量值进行绑定。

command-line.cc
1
2
template <typename T>
void AddValue (const std::string &name, const std::string &help, T &value);

其第一个参数表示命令行参数的名字,第二个参数表示对参数的解释(帮助文档),第三个参数是需要绑定的变量。然后在命令行当中对参数名字设置值即可。

例如如下程序:

try-command-line-variable.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include "ns3/core-module.h"

using namespace ns3;

NS_LOG_COMPONENT_DEFINE ("TryCommandLine");

uint32_t g_total = 5;

int
main (int argc, char *argv[])
{
LogComponentEnable("TryCommandLine", LOG_LEVEL_LOGIC);

uint32_t local = 2;

CommandLine cmd;
cmd.AddValue("total", "a global variable", g_total);
cmd.AddValue("local", "a local variable", local);
cmd.Parse(argc, argv);

NS_LOG_UNCOND("total: " << g_total);
NS_LOG_UNCOND("local: " << local);
}

其中定义了一个全局变量和一个局部变量,然后将两个变量都绑定到命令行参数当中。如果我们在运行程序时不在命令行指定任何参数,那么程序将直接使用默认值:

1
2
3
4
5
6
7
8
9
$ ./waf --run "try-command-line-variable"
Waf: Entering directory `/home/rainsia/Applications/ns-allinone-3.29/ns-3.29/build'
[1916/2023] Compiling scratch/try-command-line-variable.cc
[1984/2023] Linking build/scratch/try-command-line-variable
Waf: Leaving directory `/home/rainsia/Applications/ns-allinone-3.29/ns-3.29/build'
Build commands will be stored in build/compile_commands.json
'build' finished successfully (37.506s)
total: 5
local: 2

当然,我们也可以在不修改和重新编译程序的情况下改变一个或者两个变量的值:

1
2
3
4
5
6
7
$ ./waf --run "try-command-line-variable --total=100 --local=189"
Waf: Entering directory `/home/rainsia/Applications/ns-allinone-3.29/ns-3.29/build'
Waf: Leaving directory `/home/rainsia/Applications/ns-allinone-3.29/ns-3.29/build'
Build commands will be stored in build/compile_commands.json
'build' finished successfully (33.570s)
total: 100
local: 189

使用命令行参数设置数值类型变量的时候,直接设置其数值即可。然而在使用命令行参数设置布尔型变量的值的时候有几个特殊的值可以使用。

  • 表示为真:1、t、true
  • 表示为假:0、f、false
  • 表示取非(t变f、f变t):只有参数名,不写数值

我们使用下面的例子来演示布尔类型的变量的命令行参数设置方法:

try-command-line-bool-variable.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include "ns3/core-module.h"

using namespace ns3;

NS_LOG_COMPONENT_DEFINE ("TryCommandLineBoolVariable");

bool g_total = true;

int
main (int argc, char *argv[])
{
LogComponentEnable("TryCommandLineBoolVariable", LOG_LEVEL_LOGIC);

bool local = false;
bool toggle1 = true;
bool toggle2 = true;

CommandLine cmd;
cmd.AddValue("total", "a global variable", g_total);
cmd.AddValue("local", "a local variable", local);
cmd.AddValue("toggle1", "a boolean variable to try toggle", toggle1);
cmd.AddValue("toggle2", "a boolean variable to try toggle", toggle2);

cmd.Parse(argc, argv);

NS_LOG_UNCOND("total: " << std::boolalpha << g_total);
NS_LOG_UNCOND("local: " << local);
NS_LOG_UNCOND("toggle1: " << toggle1);
NS_LOG_UNCOND("toggle2: " << toggle2);
}

和上面的程序非常类似,我们定义了一个全局变量和几个局部变量。不同的是,这次我们使用的是bool类型。我们使用以下命令启动程序:

1
2
3
4
5
6
7
8
9
10
11
$ ./waf --run "try-command-line-bool-variable --total=f --local=0 --local=true --toggle1=0 --toggle1 --toggle2 --toggle2 --toggle2"
Waf: Entering directory `/home/rainsia/Applications/ns-allinone-3.29/ns-3.29/build'
[1942/2025] Compiling scratch/try-command-line-bool-variable.cc
[1993/2025] Linking build/scratch/try-command-line-bool-variable
Waf: Leaving directory `/home/rainsia/Applications/ns-allinone-3.29/ns-3.29/build'
Build commands will be stored in build/compile_commands.json
'build' finished successfully (35.201s)
total: false
local: true
toggle1: true
toggle2: false

我们来解析一下命令行参数和输出:

  • 对于total变量,程序中初始值为true,在命令行中赋值为f(代表false),因此最终输出false。
  • 对于local变量,程序中初始值为false,在命令行中赋值两次,第一次为0(代表false),第二次为true,第二次赋值将覆盖第一次的,因此最终输出结果为true。对于连续赋值,只要注意最后一次赋值的结果即可
  • 对于toggle1变量,程序中初始值为true,在命令行中赋值一次,切换一次。第一次赋值为0(代表false),切换的时候将取非,因此,最终输出true。
  • 对于toggle2变量,程序中初始值为true,在命令行中切换三次。切换奇数次的结果和切换一次是一样的,切换偶数次和没有切换是一样的。因此,最终输出结果为false。

经验:由于命令行参数可以对变量重复赋值,特别是布尔类型的变量,可以重复切换,很难从命令行当中看出各个参数最后的值是什么样的,因此最佳的实践就是在程序当中将最终的取值打印出来,以便确认后再开始仿真。

5. 使用命令行通过回调设置变量

有些时候,当我们通过命令行参数设置变量的时候,会遇到一种情况就是这个变量的值是依赖于一个逻辑的,而不是直接使用某一个值。比如用户输入的值需要进行一定的换算才能在程序当中直接使用。因此,我们需要运行一段代码来完成变量赋值的逻辑。在这种情况下可以通过回调来执行这种赋值逻辑。我们通过下面的例子来解释命令行回调的作用:

try-command-line-callback.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include "ns3/core-module.h"

using namespace ns3;

NS_LOG_COMPONENT_DEFINE ("TryCommandLineCallback");

double g_temp = 60;

bool ConvertTemp(std::string c)
{
DoubleValue value;
value.DeserializeFromString(c, MakeDoubleChecker<double>());
g_temp = ( 9.0 / 5.0 ) * value.Get() + 32;
return true;
}

int
main (int argc, char *argv[])
{
LogComponentEnable("TryCommandLineCallback", LOG_LEVEL_LOGIC);

CommandLine cmd;
cmd.AddValue("temp", "temperature in degree centigrade", MakeCallback(ConvertTemp));
cmd.Parse(argc, argv);

NS_LOG_UNCOND("temp: " << g_temp);
}

程序中定义了一个命令行参数temp,表示温度。我们比较习惯使用摄氏度,因此需要用户输入摄氏度,然而假设在程序中,由于某种原因,我们只能内部使用华氏度,因此在设置内部变量的值的时候,我们必须做一次转换。因此我们写了一个函数ConvertTemp()。由于命令行参数必须要求回调的返回值为bool,而参数必须为std::string。因此,我们实现的ConvertTemp()函数必须满足这种要求。但是温度是一个数值,因此,我们利用了NS3的属性值可以直接从字符串转换的特性,直接将输入的字符串转换成一个浮点数,然后再参与转换。最终我们通过如下命令来运行程序:

1
2
3
4
5
6
7
8
$ ./waf --run "try-command-line-callback --temp=10"
Waf: Entering directory `/home/rainsia/Applications/ns-allinone-3.29/ns-3.29/build'
[1938/2027] Compiling scratch/try-command-line-callback.cc
[2001/2027] Linking build/scratch/try-command-line-callback
Waf: Leaving directory `/home/rainsia/Applications/ns-allinone-3.29/ns-3.29/build'
Build commands will be stored in build/compile_commands.json
'build' finished successfully (39.020s)
temp: 50

可见,我们参数传入的值是10,而最后设置到变量上的值是50。

6. 非选项命令行参数

传统的命令行参数必须使用“–name=value”的形式调用。有时候,我们只想去输入数值,而不去输入参数的名字。这个时候可以使用非选项参数。例如下面的程序:

try-command-line-nonoption.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include "ns3/core-module.h"

using namespace ns3;

NS_LOG_COMPONENT_DEFINE ("TryCommandLineNonOption");

int
main (int argc, char *argv[])
{
LogComponentEnable("TryCommandLineNonOption", LOG_LEVEL_LOGIC);

uint32_t local = 2;

CommandLine cmd;
cmd.AddNonOption("local", "a local variable", local);

cmd.Parse(argc, argv);

NS_LOG_UNCOND("local: " << local);
}

其中我们定义了一个非选项参数local,并将其绑定到一个局部变量local上。我们在设置参数local的值的时候只需要使用如下的命令行参数:

1
2
3
4
5
6
./waf --run "try-command-line-nonoption 10"
Waf: Entering directory `/home/rainsia/Applications/ns-allinone-3.29/ns-3.29/build'
Waf: Leaving directory `/home/rainsia/Applications/ns-allinone-3.29/ns-3.29/build'
Build commands will be stored in build/compile_commands.json
'build' finished successfully (32.433s)
local: 10

如果任何参数都不提供,那么变量将会是默认值:

1
2
3
4
5
6
$ ./waf --run "try-command-line-nonoption"
Waf: Entering directory `/home/rainsia/Applications/ns-allinone-3.29/ns-3.29/build'
Waf: Leaving directory `/home/rainsia/Applications/ns-allinone-3.29/ns-3.29/build'
Build commands will be stored in build/compile_commands.json
'build' finished successfully (32.515s)
local: 2

本文标题:NS-3学习笔记(十一):命令行

文章作者:Rain Sia

发布时间:2018年10月31日 - 01:10

最后更新:2018年11月02日 - 23:11

原始链接: http://rainsia.github.io/2018/10/31/ns3-011/

版权信息:本文为作者原创文章,如需进行非商业性转载,请注明出处并保留原文链接及作者。如要进行商业性转载,请获得作者授权!

联系方式:rainsia@163.com