文章目录
- NS3-Tutorial
-
- Introduction
- Getting Started
- Conceptual Overview
-
- Key Abstractions
-
- Node
- Application
- Channel
- Net Device
- Topology Helper 拓扑结构Helper
- A First ns-3 Script
-
- Boilerplate 规章制度
- Module Includes
- Ns3 Namespace
- Logging
- Main Function
- Topology Helpers
-
- NodeContainer
- PointToPointHelper
- NetDeviceContainer
- InternetStackHelper
- Ipv4AddressHelper
- Applications
-
- UdpEchoServerHelper
- UdpEchoClientHelper
- Simulator
-
- When the simulator will stop?
- Building Your Script
- Tweaking
-
- Using the Logging Module
-
- Logging Overview
- Enabling Logging
- 重定向使用
- Adding Logging to your Code
- Using Command Line Arguments
-
- Overriding Default Attributes 涵盖默认属性
- Hooking Your Own Values
- Using the Tracing System
-
- ASCII Tracing
-
- Parsing Ascii Traces
- PCAP Tracing
-
- Reading output with tcpdump
- Reading output with Wireshark
- Building Topologies
-
- Building a Bus Network Topology
-
- 混合和非混合跟踪的区别
- Models, Attributes and Reality
- Building a Wireless Network Topology
- Queues in ns-3
-
- Available queueing models in ns-3
- Changing from the defaults
- Tracing
-
- Background
-
- Blunt Instruments
- Overview
-
- Callback
- fourth.cc
- Connect with Config
- Finding Sources
-
- Available Sources
- Config Paths
- Callback Signatures
-
- Implementation
- Traced Values
- Real Example
-
- Available Sources
- Finding Examples
- Dynamic Trace Sources
- fifth.cc
-
- 对内容解内容
- The MyApp Application
- Trace Sinks
- Main Program
- Running fifth.cc
- Using Mid-Level Helpers
-
- Sixth.cc
- Trace Helpers
- Summary
- Conclusion
-
- Closing
NS3-Tutorial
Introduction
ns-3不是ns-2.向后兼容扩展;这是一个新的模拟器。这两个模拟器都使用C 编写的,但ns-3是不支持的新模拟器ns-2的API。
tutorial的用法
- Try to download and build a copy;
- Try to run a few sample programs;
- Look at simulation output, and try to adjust it.
Getting Started
本节的目的是让用户从未安全过过ns-3的机器开始进入工作状态。
Conceptual Overview
解释系统中的一些核心概念和抽象。一些网络中常用的术语,但在ns-3中具有特定的含义。
Key Abstractions
Node
因为ns-3是一个网络模拟器,而不是专门的互联网模拟器,我们有意不使用主机(host)这个术语。相反,使用节点(node)
在ns-3中,基本的计算设备抽象被称为节点。这个抽象概念在C++中由Node类表示。Node类提供了管理模拟中计算设备的表示的方法。
你应该把**一个节点看作是一台计算机,你将向其添加功能。人们会添加一些东西,如应用程序、协议栈和外围卡及其相关的驱动程序,**以使计算机能够进行有用的工作。我们在ns-3中使用同样的基本模型。
Application
用户通常会运行一个应用程序,获取并使用由系统软件控制的资源来完成一些目标。
通常情况下,系统和应用软件之间的分界线是在操作系统陷阱中发生的权限级别变化上进行的。在ns-3中,没有操作系统的真正概念,特别是没有权限级别或系统调用的概念。然而,我们确实有一个应用程序的概念。
在ns-3中,产生一些要模拟的活动的用户程序的基本抽象是应用程序。这个抽象概念在C++中由表示。应用程序类提供了管理我们的用户级应用程序在模拟中的表现的方法。开发人员要在面向对象的编程意义上对Application类进行专业化,以创建新的应用程序。
在本教程中,我们将使用名为UdpEchoClientApplication和UdpEchoServerApplication的Application类的特殊化。正如你所期望的那样,这些应用程序组成了一个客户端/服务器应用程序集,用于生成和呼应模拟的网络数据包
Channel
在ns-3的模拟世界中,人们将一个节点连接到一个代表通信通道的对象上。在这里,基本的通信子网络抽象被称为通道,在C++中用Channel类来表示。
Channel类提供了管理通信子网对象和将节点连接到它们的方法。
在本教程中,我们将使用名为CsmaChannel、PointToPointChannel和WifiChannel的专门版本的通道。例如,CsmaChannel是一个通信子网的模型,它实现了载波感应多路存取通信介质。这给了我们类似以太网的功能。
Net Device
如果没有控制硬件的软件驱动程序,网卡将无法工作。在Unix(或Linux)中,一个外围硬件被归类为一个设备。设备使用设备驱动程序进行控制,而。在Unix和Linux中,你用诸如eth0这样的名字来指代这些网络设备。
在ns-3中,网络设备的抽象涵盖了软件驱动和模拟的硬件。网络设备被 "安装 "在一个节点上,以使该节点能够通过通道与模拟中的其他节点通信。就像在真实的计算机中,一个节点可以通过多个网络设备连接到一个以上的通道。
网络设备的抽象在C++中由NetDevice类表示。NetDevice类提供了管理与Node和Channel对象的连接的方法
我们将在本教程中使用NetDevice的几个专门版本,称为CsmaNetDevice、PointToPointNetDevice和WifiNetDevice。就像以太网网卡被设计为与以太网网络一起工作一样,CsmaNetDevice被设计为与CsmaChannel一起工作;PointToPointNetDevice被设计为与PointToPointChannel一起工作,WifiNetNevice被设计为与WifiChannel一起工作。
Topology Helper 拓扑结构Helper
由于将NetDevices连接到Nodes,将NetDevices连接到Channels,分配IP地址等等,都是ns-3中常见的任务,我们提供了所谓的拓扑帮助器,使之尽可能的简单。例如,创建一个NetDevice,添加一个MAC地址,在节点上安装该网络设备,配置节点的协议栈,然后将NetDevice连接到通道上,可能需要许多不同的ns-3核心操作。甚至需要更多的操作来将多个设备连接到多点通道上,然后将各个网络连接到一起,形成国际网络。我们提供了拓扑帮助对象,将这些不同的操作结合到一个易于使用的模型中,以方便你的使用。
A First ns-3 Script
Boilerplate 规章制度
Module Includes
在构建过程中,每个ns-3的包含文件都被放在一个叫做ns3的目录下(在构建目录下),以帮助避免包含文件名称的冲突。ns3/core-module.h文件对应于你在下载的发行版中src/core目录下找到的ns-3模块。如果你列出这个目录,你会发现有大量的头文件。当你进行构建时,Waf会根据你的配置,将公共头文件放在适当的构建/调试或构建/优化目录下的ns3目录中。Waf还将自动生成一个模块包含文件来加载所有的公共头文件。
Ns3 Namespace
using namespace ns3;
ns-3项目是在一个名为ns3的C++命名空间中实现的。这将所有与 ns-3 有关的声明归入全局命名空间之外的范围
Logging
NS_LOG_COMPONENT_DEFINE ("FirstScriptExample");
NS_LOG_COMPONENT_DEFINE文档。我不会在这里重复文档,但总结一下,这一行声明了一个名为FirstScriptExample的日志组件,允许你通过引用名称来启用和禁用控制台消息记录。
Main Function
Time::SetResolution (Time::NS);
将时间分辨率设置为1纳秒,这刚好是默认值。
分辨率是可以表示的最小的时间值(也是两个时间值之间可以表示的最小的差异)
LogComponentEnable("UdpEchoClientApplication", LOG_LEVEL_INFO);
LogComponentEnable("UdpEchoServerApplication", LOG_LEVEL_INFO);
启用Echo客户端和Echo服务器应用程序中内置的两个日志组件。
LogComponentEnable(“UdpEchoClientApplication”, LOG_LEVEL_INFO)。 LogComponentEnable(“UdpEchoServerApplication”, LOG_LEVEL_INFO)。 如果你已经阅读了日志组件的文档,你会发现在每个组件上都有若干级别的日志粗略程度/细节,你可以启用。这两行代码在INFO级别为echo客户端和服务器启用调试日志。这将导致应用程序在模拟过程中发送和接收数据包时打印出信息。
he ns-3 logging subsystem is discussed in the Using the Logging Module section, so we’ll get to it later in this tutorial, but you can find out about the above statement by looking at the Core
module, then expanding the Debugging tools
book, and then selecting the Logging
page. Click on Logging
.
Topology Helpers
Now we will get directly to the business of creating a topology and running a simulation. We use the topology helper objects to make this job as easy as possible.
NodeContainer
NodeContainer nodes;
nodes.Create (2);
NodeContainer拓扑帮助器提供了一种方便的方式来创建、管理和访问我们为运行模拟而创建的任何Node对象。上面的第一行只是声明了一个NodeContainer,我们称之为nodes。第二行调用nodes对象的Create方法,要求容器创建两个节点。正如Doxygen中所描述的那样,容器向下调用ns-3系统本身来创建两个Node对象,并在内部存储指向这些对象的指针。
PointToPointHelper
脚本中的节点什么都不做。构建拓扑结构的下一步是将我们的节点连接成一个网络。我们支持的最简单的网络形式是两个节点之间的单一点对点链接。我们将在这里构建一个这样的链接。
PointToPointHelper pointToPoint;
pointToPoint.SetDeviceAttribute ("DataRate", StringValue ("5Mbps"));
pointToPoint.SetChannelAttribute ("Delay", StringValue ("2ms"));
在栈上实例化了一个PointToPointHelper对象。从高层的角度来看
告诉PointToPointHelper对象在创建PointToPointNetDevice对象时使用 “5Mbps”(每秒5兆比特)作为 "DataRate "的值。 告诉PointToPointHelper使用值 “2ms”(两毫秒)作为它随后创建的每个点对点通道的传播延迟值。
NetDeviceContainer
我们有一个NodeContainer,它包含两个节点。我们有一个PointToPointHelper,它已经准备好制造PointToPointNetDevices并在它们之间连接PointToPointChannel对象。
NetDeviceContainer devices;
devices = pointToPoint.Install (nodes);
需要有一个所有被创建的NetDevice对象的列表,所以我们使用NetDeviceContainer来保存它们
第一行声明了上面提到的设备容器,
第二行做了繁重的工作。PointToPointHelper的需要一个NodeContainer作为参数。在内部,一个NetDeviceContainer被创建。对于NodeContainer中的每个节点(点对点链接必须正好有两个),一个PointToPointNetDevice被创建并保存在设备容器中。一个PointToPointChannel被创建,两个PointToPointNetDevice被连接。当对象被PointToPoint帮助器创建时,先前在帮助器中设置的属性被用来初始化创建对象中的相应属性。
在执行pointToPoint.Install (nodes)调用后,
- 我们将有两个节点,
- 每个节点都有一个已安装的点对点网络设备,
- 它们之间有一个单一的点对点通道。这两个设备将被配置为在有两毫秒传输延迟的信道上以每秒5兆比特的速度传输数据。
InternetStackHelper
协议栈
InternetStackHelper stack;
stack.Install (nodes);
当它被执行时,它将在节点容器中的每个节点上安装一个互联网栈(TCP、UDP、IP等)。
Ipv4AddressHelper
Ipv4AddressHelper address;
address.SetBase ("10.1.1.0", "255.255.255.0");
将我们节点上的设备与IP地址联系起来。我们提供一个拓扑帮助器来管理IP地址的分配。唯一用户可见的API是,以便在执行实际的地址分配时使用
告诉它应该从网络10.1.1.0开始分配IP地址,使用掩码255.255.255.0来定义可分配的位。默认情况下,分配的地址将从1开始并单调地增加,所以从这个基数分配的第一个地址将是10.1.1.1,接着是10.1.1.2,等等。
低级别的ns-3系统实际上记住了所有分配的IP地址,如果你不小心导致同一个地址产生两次,就会产生一个致命的错误(顺便说一下,这是一个非常难以调试的错误)。
Ipv4InterfaceContainer interfaces = address.Assign (devices);
使用Ipv4Interface对象在IP地址和设备之间建立了联系。就像我们有时需要一个由助手创建的网络设备列表供将来参考一样,我们有时需要一个Ipv4Interface对象的列表。Ipv4InterfaceContainer提供了这个功能。
Applications
现在我们已经建立了一个点对点的网络,并安装了堆栈和分配了IP地址。在这一点上,我们需要的是应用程序来产生流量。
UdpEchoServerHelper
UdpEchoServerHelper echoServer (9);
ApplicationContainer serverApps = echoServer.Install (nodes.Get (1));
serverApps.Start (Seconds (1.0));
serverApps.Stop (Seconds (10.0));
在我们先前创建的一个节点上设置一个UDP echo server application
UdpEchoServerHelper echoServer (9)我们要求将作为构造函数的参数
我们现在看到echoServer.Install将在我们用来管理节点的NodeContainer的索引号上找到的节点上安装一个UdpEchoServerApplication。(在这种情况下是一个,因为我们传递了一个包含一个节点的NodeContainer)。
应用程序需要一个时间来 "开始 "产生流量,并可能需要一个可选的时间来 “停止”。我们提供这两个时间。这些时间是通过ApplicationContainer的Start和Stop方法设置的。这些方法需要时间参数。
回声服务器应用程序在模拟的一秒钟内开始(启用自己),在模拟的十秒钟内停止(禁用自己)。
UdpEchoClientHelper
UdpEchoClientHelper echoClient (interfaces.GetAddress (1), 9);
echoClient.SetAttribute ("MaxPackets", UintegerValue (1));
echoClient.SetAttribute ("Interval", TimeValue (Seconds (1.0)));
echoClient.SetAttribute ("PacketSize", UintegerValue (1024));
ApplicationContainer clientApps = echoClient.Install (nodes.Get (0));
clientApps.Start (Seconds (2.0));
clientApps.Stop (Seconds (10.0));
对于回声客户端,我们需要设置五个不同的Attributes。
回顾一下,我们使用了一个Ipv4InterfaceContainer来跟踪我们分配给设备的。
- 接口容器中的第一个接口对应于节点容器中第一个节点的IP地址。
- 接口容器中的第2个接口将对应于节点容器中第2个节点的IP地址。
因此,在第一行代码中(从上面开始),我们正在创建帮助器,并告诉它将客户端的远程地址设置为分配给服务器所在节点的IP地址。我们还告诉它安排向第9端口发送数据包。
MaxPackets "属性告诉客户端在模拟过程中我们允许它发送的最大数据包。
Interval"属性告诉客户机在数据包之间要等待多长时间,而
"PacketSize "属性告诉客户机数据包的。
就像在回声服务器的情况下,我们告诉回声客户端开始和停止,
Simulator
Simulator::Run ();
在这一点上,我们需要做的是实际运行模拟。这是用全局函数Simulator::Run完成的。
serverApps.Start (Seconds (1.0));
serverApps.Stop (Seconds (10.0));
...
clientApps.Start (Seconds (2.0));
clientApps.Stop (Seconds (10.0));
我们实际上在模拟器中安排了1.0秒、2.0秒的事件和10.0秒的两个事件。当Simulator::Run被调用时,系统将开始查看并执行它们。
-
首先,它将运行1.0秒的事件,这将启用回声服务器应用程序(这个事件可能反过来安排许多其他事件)。然后它将运行计划在t=2.0秒的事件,这将启动回声客户端应用程序。同样,这个事件可能会安排许多其他事件。在回声客户端应用程序中的启动事件实现将通过向服务器发送一个数据包来开始模拟的数据传输阶段。
-
向服务器发送数据包的行为将触发一连串的事件,这些事件将在幕后自动安排,并根据我们在脚本中设置的各种时间参数来执行数据包回声的机械操作。
-
最终,由于我们只发送了一个数据包(记得MaxPackets属性被设置为一个),由那个单一的客户端回声请求引发的事件链将逐渐减少,模拟将进入空闲状态。一旦发生这种情况,剩下的事件将是服务器和客户端的停止事件。
当这些事件被执行后,没有进一步的事件需要处理,Simulator::Run就会返回。仿真就完成了。
Simulator::Destroy ();
return 0;
}
剩下的就是清理了。调用Simulator::Destroy并退出。
When the simulator will stop?
ns-3是一个**离散事件(**DE)模拟器。在这样的模拟器中,每个事件都与它的执行时间相关联,模拟通过执行事件在模拟时间的时间顺序进行。
+ Simulator::Stop (Seconds (11.0));
Simulator::Run ();
Simulator::Destroy ();
return 0;
}
Building Your Script
output:
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)
Sent 1024 bytes to 10.1.1.2
Received 1024 bytes from 10.1.1.1
Received 1024 bytes from 10.1.1.2
你看到回声客户端上的日志组件显示它已经向10.1.1.2的回声服务器发送了一个1024字节的数据包。
你也看到回声服务器上的日志组件说它已经收到了来自10.1.1.1的1024字节。
**回声服务器默默地回声了该数据包,**你看到回声客户端记录说它已经从服务器收到了它的数据包。
Tweaking
Using the Logging Module
我们提供了一个可选择的、多级别的消息记录方法。日志可以被完全禁用,也可以逐个组件地启用,或者全局地启用;它还提供了可选择的粗体等级。
Logging Overview
- LOG_ERROR — Log error messages (associated macro: NS_LOG_ERROR);
- LOG_WARN — Log warning messages (associated macro: NS_LOG_WARN);
- LOG_DEBUG — Log relatively rare, ad-hoc debugging messages (associated macro: NS_LOG_DEBUG);
- LOG_INFO — Log about program progress (associated macro: NS_LOG_INFO);
- LOG_FUNCTION — 记录描述每个函数调用的信息 (two associated macros: NS_LOG_FUNCTION, used for 成员函数, and NS_LOG_FUNCTION_NOARGS, used for 静态函数);
- LOG_LOGIC – 记录函数内逻辑流程的信息 (associated macro: NS_LOG_LOGIC);
- LOG_ALL —记录描述每个函数调用的信息(no associated macro).
每个级别都可以单独或累计请求;并且可以使用shell环境变量(NS_LOG)或通过日志系统函数调用来设置日志。
Enabling Logging
$ 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
事实证明,你在上面看到的 "发送 "和 "接收 "信息实际上是UdpEchoClientApplication和UdpEchoServerApplication的日志信息。例如,我们可以通过设置其日志级别,要求客户端应用程序打印更多信息。
UDP 回声客户端应用程序对 scratch/myfirst.cc 中的以下一行代码做出了响应。
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设置为字符串。
This sets the shell environment variable NS_LOG
to the string,
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()
$ export 'NS_LOG=UdpEchoClientApplication=level_all|prefix_func'
注意,引号是必须的,因为我们用来表示OR操作的竖条也是Unix的管道连接器。
现在,如果你运行该脚本,你会看到日志系统确保来自给定日志组件的每条消息都
消息 "收到来自10.1.1.2的1024字节 "现在被清楚地识别为来自回声客户程序。
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()
$ export 'NS_LOG=UdpEchoClientApplication=level_all|prefix_func:
UdpEchoServerApplication=level_all|prefix_func'
现在,如果你运行这个脚本,你会看到来自echo客户端和服务器应用程序的所有日志信息。你可能会发现,这在调试问题时非常有用。
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位上的OR来做到这一点。
$ 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秒。
$ export 'NS_LOG=*=level_all|prefix_func|prefix_time'
上面的* 是记录组件的。这将开启模拟中使用的所有组件中的所有日志记录。
重定向的使用
$ ./waf --run scratch/myfirst > log.out 2>&1
极其粗略的日志记录版本。我可以很容易地跟踪代码的进展,而不需要在调试器中设置断点和步入代码。我可以在我最喜欢的编辑器中编辑输出,搜索我所期望的东西,并看到我所不期望的事情发生。当我对出错的地方有一个大致的概念时,我就会过渡到调试器中,对问题进行细微的检查。
Adding Logging to your Code
示例
NS_LOG_INFO ("Creating Topology");
你将看不到你的新消息,因为它相关的日志组件(FirstScriptExample)还没有被启用。为了看到你的消息,你必须启用
需要启用它
$ export NS_LOG=FirstScriptExample=info
Using Command Line Arguments
Overriding Default Attributes 覆盖默认属性
通过命令行参数来改变ns-3脚本
首先代码中写入
int
main (int argc, char *argv[])
{
...
CommandLine cmd;
cmd.Parse (argc, argv);
...
}
$ ./waf --run "scratch/myfirst --PrintHelp"
这将要求 Waf 运行 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.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.
$ ./waf --run "scratch/myfirst --PrintAttributes=ns3::PointToPointNetDevice"
系统将打印出这种网络设备的所有属性。在这些属性中,你将看到列出的是。
--ns3::PointToPointNetDevice::DataRate=[32768bps]:
The default data rate for point to point links
Hooking Your Own Values
Using the Tracing System
仿真的全部意义在于产生输出,以便进一步研究,而ns-3追踪系统是这方面的一个主要机制。
由于ns-3是一个C++程序,可以使用从C++程序生成输出的标准设施。
#include <iostream>
...
int main ()
{
...
std::cout << "The value of x is " << x << std::endl;
...
}
ns-3追踪系统的基本目标是。
- 对于基本的任务,追踪系统应该允许用户为流行的追踪源产生标准的追踪,并且可以自定义哪些对象产生追踪。
- 中级用户必须能够扩展追踪系统,修改生成的输出格式,或者插入新的追踪源,而不需要修改模拟器的核心。
- 高级用户可以修改模拟器的核心,增加新的跟踪源和汇。
ASCII Tracing
ns-3提供了帮助功能,包裹了低级别的跟踪系统,以帮助你在配置一些容易理解的数据包跟踪时涉及的细节。如果你启用了这个功能,你会看到ASCII文件的输出–因此而得名。对于那些熟悉ns-2输出的人来说,这种类型的跟踪类似于许多脚本所产生的out.tr。
在我们的 scratch/myfirst.cc 脚本中添加一些 ASCII 追踪输出。在调用 Simulator::Run () 之前,添加以下几行代码。
AsciiTraceHelper ascii;
pointToPoint.EnableAsciiAll (ascii.CreateFileStream ("myfirst.tr"));
这段代码使用了一个帮助对象来帮助创建ASCII码的跟踪。第二行包含两个嵌套的方法调用。内部 "方法,CreateFileStream()使用了一个未命名的对象习惯法,在堆栈中创建一个文件流对象(没有对象名),并将其传递给被调用的方法。
myfirst.tr的文件。由于Waf的工作方式,这个文件不是在本地目录下创建的,它默认是在版本库的顶级目录下创建的。如果你想控制痕迹的保存位置,你可以使用Waf的-cwd选项来指定。
Parsing Ascii Traces
- +: 设备队列上发生了一个enqueue操作。
- -: 在设备队列上发生了一个去queue操作。
- d: 一个数据包被丢弃,通常是因为队列已满。
- r: 网络设备收到了一个数据包。
举例
+
2
/NodeList/0/DeviceList/0/$ns3::PointToPointNetDevice/TxQueue/Enqueue
ns3::PppHeader (
Point-to-Point Protocol: IP (0x0021))
ns3::Ipv4Header (
tos 0x0 ttl 64 id 0 protocol 17 offset 0 flags [none]
length: 1052 10.1.1.1 > 10.1.1.2)
ns3::UdpHeader (
length: 1032 49153 > 9)
Payload (size=1024)
我们有一个 "+"字符,所以这相当于在发送队列上的一个enqueue操作。第二部分(参考号1)是以秒为单位的模拟时间。你可能还记得,我们要求UdpEchoClientApplication在两秒内开始发送数据包。在这里我们看到了确认,这确实是在发生。
可以把追踪命名空间看作是文件系统命名空间的一部分。该**命名空间的根是NodeList。**它对应于ns-3核心代码中管理的一个容器,它包含了所有在脚本中创建的节点。就像文件系统在根目录下可能有目录一样,我们在NodeList中可能有节点编号。因此,**字符串/NodeList/0指的是NodeList中的第2个节点,**我们通常认为是 “节点0”。在每个节点中,都有一个已经安装的设备列表。这个列表接下来出现在命名空间中。你可以看到这个追踪事件来自DeviceList/0,它是安装在节点中的第2个设备。
$ns3::PointToPointNetDevice告诉你什么样的设备是在节点0的设备列表的第2个位置。回顾一下,在参考00处发现的操作+意味着设备的发送队列上发生了一个enqueue操作。这反映在 "跟踪路径 "的最后一段,即TxQueue/Enqueue。
追踪文件中的第三行显示数据包被有回声服务器的节点上的网络设备接收。
r
2.25732
/NodeList/1/DeviceList/0/$ns3::PointToPointNetDevice/MacRx
ns3::Ipv4Header (
tos 0x0 ttl 64 id 0 protocol 17 offset 0 flags [none]
length: 1052 10.1.1.1 > 10.1.1.2)
ns3::UdpHeader (
length: 1032 49153 > 9)
Payload (size=1024)
现在跟踪操作是r,模拟时间增加到2.25732秒。如果你一直紧跟教程的步骤,这意味着你已经把网络设备的DataRate和通道Delay设置为它们的默认值。这个时间应该很熟悉,因为你以前在前面的章节中见过它。
跟踪源命名空间条目(参考02)已经改变,以反映该事件来自节点1(/NodeList/1)和包接收跟踪源(/MacRx)。通过查看文件中其他的跟踪记录,你应该很容易跟踪数据包在拓扑结构中的进展。
PCAP Tracing
ns-3设备助手也可以用来创建.pcap格式的跟踪文件。首字母缩写 pcap(通常用小写)代表数据包捕获,实际上是一个包括.pcap文件格式定义的API。
能够读取和显示这种格式的最流行的程序是Wireshark
用于启用 pcap 追踪的代码是一个单行代码。
pointToPoint.EnablePcapAll ("myfirst");
注意,我们只传递了字符串 “myfirst”,而不是 "myfirst.cap “或类似的东西。这是因为这个参数是一个前缀,而不是一个完整的文件名。助手实际上将为模拟中的每个点对点设备创建一个跟踪文件。文件名将使用前缀、节点号、设备号和”.pcap "后缀来建立。
Reading output with 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.cap(客户端设备)的转储中看到,回波数据包是在模拟过程中的2秒发送的。如果你看一下第二个转储(myfirst-1-0.cap),你可以看到该数据包在2.257324秒被接收。在第二个转储中,你看到数据包在2.257324秒时被回传,最后,你看到数据包在2.514648秒时被客户端接收。
Reading output with Wireshark
略
Building Topologies
Building a Bus Network Topology
s-3提供了一个网络设备和通道,我们称之为CSMA(载波感应多路存取)。
ns-3 CSMA设备以以太网的精神模拟了一个简单的网络。一个真正的以太网使用CSMA/CD(带碰撞检测的载波感应多路存取)方案,用指数级增加的后退来争夺共享的传输介质。ns-3的CSMA设备和通道模型只是其中的一个子集。
相较于first,cc,有些不同的地方
bool verbose = true;
uint32_t nCsma = 3;
CommandLine cmd;
cmd.AddValue ("nCsma", "Number of \"extra\" CSMA nodes/devices", nCsma);
cmd.AddValue ("verbose", "Tell echo applications to log if true", verbose);
cmd.Parse (argc, argv);
if (verbose)
{
LogComponentEnable("UdpEchoClientApplication", LOG_LEVEL_INFO);
LogComponentEnable("UdpEchoServerApplication", LOG_LEVEL_INFO);
}
nCsma = nCsma == 0 ? 1 : nCsma;
verbose标志来确定UdpEchoClientApplication和UdpEchoServerApplication的日志组件是否被启用。这个标志默认为 “true”(日志组件被启用
CommandLine cmd允许你通过命令行参数改变CSMA网络上的设备数量。当我们允许在命令行参数部分改变发送数据包的数量时
nCsma = nCsma == 0 ? 1 : nCsma; 确保你至少有一个 "额外 "的节点。
后面和first.cc很像,略
Ipv4GlobalRoutingHelper::PopulateRoutingTables ();
每个节点的行为就像它是一个OSPF路由器,在幕后与所有其他路由器进行即时和神奇的通信。每个节点生成链接广告,并将其直接传达给全局路由管理器,后者使用这些全局信息为每个节点构建路由表。
接下来我们启用 pcap 追踪。在点对点帮助器中启用 pcap 追踪的第一行代码现在你应该很熟悉了。
第二行是在CSMA帮助器中启用pcap跟踪,有一个额外的参数你还没有遇到过。
pointToPoint.EnablePcapAll ("second");
csma.EnablePcap ("second", csmaDevices.Get (1), true);
在这个例子中,我们将选择CSMA网络中的,从而模拟tcpdump的工作。
如果你是在一台Linux机器上,你可能会做一些类似tcpdump -i eth0的事情来获得跟踪。在这种情况下,我们用
$ cp examples/tutorial/second.cc scratch/mysecond.cc
$ ./waf
我们使用second.cc文件作为我们的回归测试之一,以验证它是否完全按照我们认为的那样工作,以使你的教程体验是积极的。这意味着项目中已经存在一个名为second的可执行文件。
$ export NS_LOG=
$ ./waf --run scratch/mysecond
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.415s)
Sent 1024 bytes to 10.1.2.4
Received 1024 bytes from 10.1.1.1
Received 1024 bytes from 10.1.2.4
第一条信息 “发送1024字节到10.1.2.4”,是UDP回声客户端向服务器发送一个数据包。在这种情况下,服务器在另一个网络上(10.1.2.0)。
第二条消息,"收到来自10.1.1.1的1024字节,"是来自UDP回波服务器,当它收到回波数据包时产生。
最后一条消息,“收到来自10.1.2.4的1024字节”,是来自回声客户端,表明它已经收到了来自服务器的回声。
如果你现在去看顶层目录,你会发现跟踪文件。
second-0-0.pcap second-1-0.pcap second-2-0.pcap
这些文件的命名。它们都有相同的形式:**<名称>-<节点>-<设备>.pcap。**例如,列表中的第一个文件是 second-0-0.cap,这是来自零号节点、零号设备的 pcap 跟踪。
// Default Network Topology
//
// 10.1.1.0
// n0 -------------- n1 n2 n3 n4
// point-to-point | | | |
// ================
// LAN 10.1.2.0
如果你参考本节开始时的拓扑图,你会发现零号节点是点对点链路的最左边的节点,
节点一是同时拥有点对点设备和CSMA设备的节点。
节点二是CSMA网络上的第一个 "额外 "节点,
$ tcpdump -nn -tt -r second-0-0.pcap
reading from file second-0-0.pcap, link-type PPP (PPP)
2.000000 IP 10.1.1.1.49153 > 10.1.2.4.9: UDP, length 1024
2.017607 IP 10.1.2.4.9 > 10.1.1.1.49153: UDP, length 1024
转储的第一行表明链接类型是PPP(点对点),
回声包通过与IP地址10.1.1.1相关的设备离开零号节点,前往IP地址10.1.2.4(最右边的CSMA节点)。
这个数据包将在点对点链路上移动,被节点一上的点对点网络设备接收。
$ tcpdump -nn -tt -r second-1-0.pcap
reading from file second-1-0.pcap, link-type PPP (PPP)
2.003686 IP 10.1.1.1.49153 > 10.1.2.4.9: UDP, length 1024
2.013921 IP 10.1.2.4.9 > 10.1.1.1.49153: UDP, length 1024
来自IP地址10.1.1.1的数据包(在2.000000秒时发送)向IP地址10.1.2.4的方向出现在这个接口上。现在,在这个节点的内部,数据包将被转发到CSMA接口,我们应该看到它在该设备上弹出,前往其最终目的地。
$ tcpdump -nn -tt -r second-2-0.pcap
reading from file second-2-0.pcap, link-type EN10MB (Ethernet)
2.007698 ARP, Request who-has 10.1.2.4 (ff:ff:ff:ff:ff:ff) tell 10.1.2.1, length 50
2.007710 ARP, Reply 10.1.2.4 is-at 00:00:00:00:00:06, length 50
2.007803 IP 10.1.1.1.49153 > 10.1.2.4.9: UDP, length 1024
2.013815 ARP, Request who-has 10.1.2.1 (ff:ff:ff:ff:ff:ff) tell 10.1.2.4, length 50
2.013828 ARP, Reply 10.1.2.1 is-at 00:00:00:00:00:03, length 50
2.013921 IP 10.1.2.4.9 > 10.1.1.1.49153: UDP, length 1024
正如你所看到的,现在的链接类型是 “Ethernet”。不过,出现了一些新东西。
总线网络需要ARP,即地址解析协议。节点一知道它需要向IP地址10.1.2.4发送数据包,但它不知道相应节点的MAC地址。它在CSMA网络上进行广播(ff:ff:ff:ff:ff:ff:ff:ff),询问拥有IP地址10.1.2.4的设备。在这种情况下,最右边的节点回复说它在MAC地址00:00:00:00:00:06。
注意,节点二没有直接参与这个交换,而是在嗅探网络并报告它所看到的所有流量。
这种交换在下面几行中可以看到。
2.007698 ARP, Request who-has 10.1.2.4 (ff:ff:ff:ff:ff:ff) tell 10.1.2.1, length 50
2.007710 ARP, Reply 10.1.2.4 is-at 00:00:00:00:00:06, length 50
然后节点一,设备一继续前进,向IP地址为10.1.2.4的UDP回音服务器发送回音包。
2.007803 IP 10.1.1.1.49153 > 10.1.2.4.9: UDP, length 1024
服务器收到回声请求,并把数据包转过来,试图把它发回给源。服务器知道这个地址是在另一个网络上,它通过IP地址10.1.2.1到达。这是因为我们初始化了全局路由,它已经为我们想好了这一切。但是,回声服务器节点不知道第一个CSMA节点的MAC地址,所以它必须像第一个CSMA节点那样进行ARP查找。
2.013815 ARP, Request who-has 10.1.2.1 (ff:ff:ff:ff:ff:ff) tell 10.1.2.4, length 50
2.013828 ARP, Reply 10.1.2.1 is-at 00:00:00:00:00:03, length 50
然后,服务器将回波发送至转发节点。
2.013921 IP 10.1.2.4.9 > 10.1.1.1.49153: UDP, length 1024
回看点对点链接的最右边的节点。
$ tcpdump -nn -tt -r second-1-0.pcap
现在你可以看到回波的数据包作为跟踪转储的最后一行回到了点对点链路上。
reading from file second-1-0.pcap, link-type PPP (PPP)
2.003686 IP 10.1.1.1.49153 > 10.1.2.4.9: UDP, length 1024
2.013921 IP 10.1.2.4.9 > 10.1.1.1.49153: UDP, length 1024
看一下产生回声的节点
$ tcpdump -nn -tt -r second-0-0.pcap
该回波数据包在2.017607秒时到达源头。
reading from file second-0-0.pcap, link-type PPP (PPP)
2.000000 IP 10.1.1.1.49153 > 10.1.2.4.9: UDP, length 1024
2.017607 IP 10.1.2.4.9 > 10.1.1.1.49153: UDP, length 1024
利用命令行把节点数目改为4个
$ ./waf --run "scratch/mysecond --nCsma=4"
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)
At time 2s client sent 1024 bytes to 10.1.2.5 port 9
At time 2.0118s server received 1024 bytes from 10.1.1.1 port 49153
At time 2.0118s server sent 1024 bytes to 10.1.1.1 port 49153
At time 2.02461s client received 1024 bytes from 10.1.2.5 port 9
现在回声服务器已经被重新定位到CSMA节点中的最后一个,也就是10.1.2.5
你有可能不满意由CSMA网络中的旁观者产生的跟踪文件。你可能真的想得到一个单一设备的跟踪,你可能对网络上的任何其他流量不感兴趣。
pointToPoint.EnablePcap ("second", p2pNodes.Get (0)->GetId (), 0);
csma.EnablePcap ("second", csmaNodes.Get (nCsma)->GetId (), 0, false);
csma.EnablePcap ("second", csmaNodes.Get (nCsma-1)->GetId (), 0, false);
认识到NodeContainers包含指向ns-3 Node对象的指针。节点对象有一个叫做GetId的方法,它将返回该节点的ID,也就是我们要找的节点编号。
在复杂的拓扑结构中,使用GetId方法可以更容易地确定节点编号。
旧的跟踪文件从顶层目录中清除掉
$ rm *.pcap
$ rm *.tr
重新跑程序,得
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.407s)
At time 2s client sent 1024 bytes to 10.1.2.101 port 9
At time 2.0068s server received 1024 bytes from 10.1.1.1 port 49153
At time 2.0068s server sent 1024 bytes to 10.1.1.1 port 49153
At time 2.01761s client received 1024 bytes from 10.1.2.101 port 9
现在回声服务器位于10.1.2.101,这相当于有100个 "额外的 "CSMA节点,回声服务器位于最后一个节点。
second-0-0.pcap second-100-0.pcap second-101-0.pcap
文件second-0-0.cap是 "最左边 "的点对点设备,它是回声数据包的来源。文件second-101-0.cap对应的是最右边的CSMA设备,也就是回音服务器所在的位置。
在回声服务器节点上启用 pcap 跟踪的调用的最后一个参数是 false。这意味着在该节点上收集的跟踪是以非混杂模式进行的。
混杂和非混杂跟踪之间的区别
Models, Attributes and Reality
无论何时,当人们使用模拟时,重要的是要明白到底什么是被建模的,什么不是。例如,我们很容易把上一节中使用的CSMA设备和信道想成是真实的以太网设备;并期望仿真结果能直接反映真实的以太网中会发生什么。事实并非如此。
根据定义,模型是对现实的抽象。仿真脚本作者最终有责任确定整个仿真的所谓 "准确范围 "和 “适用领域”,因此也包括其组成部分。
在某些情况下,比如Csma,要确定哪些东西没有被建模是相当容易的。通过阅读模型描述(csma.h),你可以发现CSMA模型中没有碰撞检测,并决定在你的仿真中如何使用它,或者你可能想在结果中加入什么注意事项。在其他情况下,可以很容易地配置一些行为,这些行为可能与你可以出去买的任何现实不一致。事实证明,花一些时间来调查一些这样的例子是值得的,你可以很容易地在你的模拟中转向现实的界限之外。
正如你所看到的,NS-3提供了用户可以轻松设置的属性来改变模型行为。考虑一下CsmaNetDevice的两个属性。Mtu和EncapsulationMode。Mtu属性表示设备的最大传输单元。这是该设备可以发送的最大的协议数据单元(PDU)的大小。
MTU在CsmaNetDevice中默认为1500字节。这个默认值对应于RFC894中的一个数字,“在以太网上传输IP数据报的标准”。这个数字实际上是由10Base5(全规格以太网)网络的最大数据包大小–1518字节得出的。如果你减去以太网数据包的DIX封装开销(18字节),你将得到1500字节的最大可能数据大小(MTU)。人们还可以发现,IEEE 802.3网络的MTU是1492字节。这是因为LLC/SNAP封装给数据包增加了额外的8个字节的开销。在这两种情况下,底层硬件只能发送1518字节,但数据大小不同。
为了设置封装模式,CsmaNetDevice提供了一个名为EncapsulationMode的属性,它可以取值为Dix或Llc。这些值分别对应于以太网和LLC/SNAP成帧。
如果将Mtu保持在1500字节,并将封装模式改为Llc,结果将是一个用LLC/SNAP构架封装1500字节PDU的网络,导致1526字节的数据包,这在许多网络中是非法的,因为它们每个数据包最多可传输1518字节。这很可能导致模拟结果与你所期望的现实情况有相当大的出入。
为了使情况复杂化,还存在巨型帧(1500 < MTU <= 9000字节)和超级巨型帧(MTU > 9000字节),它们没有得到IEEE的正式认可,但在一些高速(千兆)网络和网卡中可用。人们可以将封装模式设置为Dix,并将CsmaNetDevice上的Mtu属性设置为64000字节–即使相关的CsmaChannel DataRate被设置为每秒10兆比特。这基本上是一个以太网交换机的模型,它是由支持超级巨量数据报的1980年代风格的10Base5网络组成的吸血鬼。这当然不是曾经制造过的东西,也不可能被制造出来,但对你来说,配置它是相当容易的。
在前面的例子中,你用命令行创建了一个有100个Csma节点的模拟。你也可以很容易地创建一个有500个节点的模拟。如果你真的在模拟10Base5吸血式分接头网络,全规格的以太网电缆的最大长度是500米,最小分接头间距是2.5米。这意味着在一个真正的网络上只能有200个分接点。你也可以很容易地以这种方式建立一个非法网络。这可能会也可能不会产生有意义的模拟,这取决于你试图模拟的内容。
在NS-3和任何模拟器中,类似的情况会在很多地方发生。例如,你可能会以这样的方式定位节点,使它们在同一时间占据相同的空间,或者你可能会配置违反基本物理定律的放大器或噪声水平。
ns-3通常倾向于灵活性,许多模型将允许自由设置属性,而不试图强制执行任何任意的一致性或特定的基础规范。
从这里可以看出,NS-3将提供一个超级灵活的基础,供你实验。你应该理解你要求系统做什么,并确保你创建的模拟有一定的意义和价值。
Building a Wireless Network Topology
ns-3提供了一套802.11模型,试图提供802.11规范的精确MAC级实现和802.11a规范的 "不那么慢的 "PHY级模型。
// Default Network Topology
//
// Wifi 10.1.3.0
// AP
// * * * *
// | | | | 10.1.1.0
// n5 n6 n7 n0 -------------- n1 n2 n3 n4
// point-to-point | | | |
// ================
// LAN 10.1.2.0
在左边挂一个无线网络。注意,这是一个默认的网络拓扑结构,因为你实际上可以改变在有线和无线网络上创建的节点数量。就像在第二个.cc脚本案例中一样,如果你改变nCsma,它将给你一些 "额外的 "CSMA节点。
同样地,你可以设置nWifi来控制模拟中创建多少个STA(站)节点。
前面的与first,second相似,不赘述
接下来的代码构建了wifi设备和这些wifi节点