ns-3 网络模拟器教程。 第5章

ns-3 网络模拟器教程。 第5章
第1,2章
第3章
第4章

5 设置
5.1 使用日志模块
5.1.1 日志记录概述
5.1.2 启用日志记录
5.1.3 在代码中添加日志记录
5.2 使用命令行参数
5.2.1 覆盖默认属性值
5.2.2 捕获自己的命令
5.3 使用追踪系统
5.3.1 ASCII 跟踪
解析 ASCII 跟踪
5.3.2 PCAP 跟踪

第5章

调整

5.1 使用日志模块

我们已经通过查看脚本简要了解了 ns-3 日志记录模块 第一个.cc。 在本章中,我们将仔细研究日志子系统的可能用途。

5.1.1 日志记录概述

许多大型系统都支持某种消息记录工具,ns-3 也不例外。 在某些情况下,只有错误消息会写入“操作员控制台”(在基于 Unix 的系统上通常是 stderr)。 在其他系统上,可能会显示警告消息以及更详细的信息。 在某些情况下,日志记录工具用于输出可以快速模糊输出的调试消息。

ns-3 中使用的 subHRD 假设所有这些级别的信息内容都是有用的,并且我们提供了一种选择性的、分层的消息记录方法。 日志记录可以完全禁用、针对每个组件或全局启用。 为此目的,使用可调整的信息内容级别。 ns-3 日志记录模块提供了一种相对简单的方法来从模拟中获取有用的信息。

您应该了解,我们提供了一种通用机制 - 跟踪 - 用于从模型中提取数据,这应该是模拟的首选输出(有关我们的跟踪系统的更多信息,请参阅教程第 5.3 节)。 日志记录应该是获取调试信息、警告、错误消息或随时从脚本或模型快速输出消息的首选方法。

目前,系统按信息内容递增的顺序定义了七种日志消息级别(类型)。

  • LOG_ERROR - 记录错误消息(相关宏:NS_LOG_ERROR);
  • LOG_WARN - 记录警告消息(相关宏:NS_LOG_WARN);
  • LOG_DEBUG - 记录相对罕见的特殊调试消息(相关宏:NS_LOG_DEBUG);
  • LOG_INFO - 注册有关程序进度的信息消息(相关宏:NS_LOG_INFO);
  • LOG_FUNCTION - 记录描述每个调用的函数的消息(两个相关的宏:NS_LOG_FUNCTION,用于成员函数,和 NS_LOG_FUNCTION_NOARGS,用于静态函数);
  • LOG_LOGIC - 描述函数内逻辑流程的日志消息(相关宏:NS_LOG_LOGIC);
  • LOG_ALL - 记录上述所有内容(无关联宏)。
    对于每种类型 (LOG_TYPE),还有一个 LOG_LEVEL_TYPE,如果使用它,除了它自己的级别之外,还允许记录其之上的所有级别。 (因此,LOG_ERROR和LOG_LEVEL_ERROR以及LOG_ALL和LOG_LEVEL_ALL在功能上是等效的。)例如,启用LOG_INFO将仅允许由NS_LOG_INFO宏提供的消息,而启用LOG_LEVEL_INFO还将包括由宏NS_LOG_DEBUG、NS_LOG_WARN和NS_LOG_ERROR提供的消息。

我们还提供了一个始终显示的无条件日志记录宏,无论日志记录级别或选择组件如何。

  • NS_LOG_UNCOND - 关联消息的无条件日志记录(无关联的日志记录级别)。

每个级别可以单独查询,也可以累加查询。 可以使用 sh 环境变量 NS_LOG 或通过记录系统函数调用来配置日志记录。 如前所述,日志记录系统有 Doxygen 文档,如果您还没有,现在是查看它的好时机。

现在您已经详细阅读了文档,让我们利用这些知识从示例脚本中获取一些有趣的信息 从头开始/myfirst.cc您已经编译过。

5.1.2 启用日志记录

让我们使用 NS_LOG 环境变量来运行更多日志,但首先,为了了解情况,请像之前一样运行最后一个脚本,

$ ./waf --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

事实证明,您在上面看到的“已发送”和“已接收”消息实际上是来自的记录消息 UdpEchoClient应用程序 и UdpEchoServer应用程序。 例如,我们可以通过 NS_LOG 环境变量设置其日志记录级别来要求客户端应用程序打印附加信息。

从现在开始,我将假设您使用的是类似 sh 的 shell,它使用“VARIABLE=value”语法。 如果您使用的是类似 csh 的 shell,那么您必须将我的示例转换为这些 shell 所需的“setenv 变量值”语法。

目前,UDP echo 客户端应用程序响应以下代码行 从头开始/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

所以我们将 sh 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_FUNCTION 级别。 它显示脚本执行期间函数调用的每个实例。 作为一般规则,最好(至少)在方法函数中使用NS_LOG_FUNCTION (this)... 用 NS_LOG_FUNCTION_NOARGS ()
仅在静态函数中。 但请注意,ns-3 系统不需要支持任何日志记录功能。 关于记录多少信息的决定留给各个模型开发人员。 在回显应用程序中,可以使用大量的日志输出。

您现在可以查看应用程序进行的函数调用的日志。 如果你仔细观察,你会注意到线条之间有一个冒号 UdpEchoClient应用程序 以及方法的名称,您可能会在其中看到 C++ 作用域运算符 (: :)。 这是故意的。

这实际上不是类的名称,而是日志记录组件的名称。 当源文件和类之间存在匹配时,它通常是类的名称,但您应该意识到它实际上并不是类的名称,并且有一个冒号而不是双冒号。 这是一种帮助您以相对微妙的方式在概念上将日志记录 bean 名称与类名称分开的方法。

但是,在某些情况下,可能很难确定实际生成日志消息的方法。 如果您查看上面的文本,您可能想知道“Received 1024 bytes from 10.1.1.2” 可以通过设置level来解决这个问题 前缀函数 到 NS_LOG 环境变量。 请尝试以下操作:

$ export 'NS_LOG=UdpEchoClientApplication=level_all|prefix_func'

请注意,引号是必需的,因为我们用来表示 OR 运算的竖线也是 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" 现在已明确标识为来自 echo 客户端应用程序。 其余消息必须来自 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()

有时能够查看生成日志消息的模拟时间也很有用。 您可以通过添加 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()

请注意,构造函数 UDP回显服务器 在模拟 0 秒期间被调用。 这实际上发生在模拟开始之前,但时间显示为零秒。 构造函数消息也是如此 UdpEcho客户端.

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()

回想一下脚本 从头开始/第一个.cc 在模拟开始前一秒启动回显服务器应用程序。 现在你可以看到该方法 启动应用程序 服务器实际上是在第一秒被调用的。 您可能还注意到,正如我们在脚本中所要求的那样,回显客户端在模拟的第二秒启动。

您现在可以随叫随到地跟踪模拟进度 调度传输 在调用 HandleRead 回调的客户端中发送 echo 服务器应用程序。 请注意,通过点对点链路发送数据包所需的时间为 3,69 毫秒。 您可以看到 echo 服务器记录了一条消息,表明它已响应数据包,然后,经过通道延迟后,您会看到 echo 客户端在其 HandleRead 方法中收到了 echo 数据包。

在这个模拟中,很多事情都会在您没有注意到的情况下发生。 但是,您可以通过启用系统中的所有日志记录组件来非常轻松地跟踪整个过程。 尝试将 NS_LOG 变量设置为以下值,

$ export 'NS_LOG=*=level_all|prefix_func|prefix_time'

上面的星号是日志记录组件的通配符。 这将包括模拟中使用的所有组件中的所有条目。 我不会在此处重现输出(在撰写本文时,它会为单个 echo 数据包生成 1265 行输出),但您可以将此信息重定向到文件并在您喜欢的编辑器中查看它。

$ ./waf --run scratch/myfirst > log.out 2>&1

当我遇到问题并且不知道哪里出了问题时,我个人会使用这种极其详细的日志记录版本。 我可以非常轻松地跟踪代码执行,而无需设置断点并单步执行调试器中的代码。 我可以在我最喜欢的编辑器中编辑输出,并查找我期望的内容,并看到发生我没有预料到的事情。 一旦我大致了解出了什么问题,我就会跳入调试器来深入研究问题。 当您的脚本执行完全意想不到的操作时,这种类型的输出尤其有用。 如果您只使用调试器,您可能会完全错过一个转折点。 注册使这种转变变得引人注目。

5.1.3 在代码中添加日志记录

您可以通过从多个宏调用日志组件来向模拟添加新条目。 让我们在脚本中完成它 我的第一个.cc,我们在“clean”目录中。 回想一下,我们在这个场景中定义了一个日志组件:

NS_LOG_COMPONENT_DEFINE ("FirstScriptExample");

您知道,您可以通过在不同级别设置 NS_LOG 环境变量来启用来自该组件的所有消息的日志记录。 让我们继续向脚本添加一些条目。 用于向日志添加信息级消息的宏是 NS_LOG_INFO。 让我们添加一条消息(就在我们开始创建节点之前),告诉您脚本处于“创建拓扑”阶段。 这是在以下代码片段中完成的,
打开 从头开始/myfirst.cc 在您最喜欢的编辑器中添加以下行,
NS_LOG_INFO ("Creating Topology");
就在线条之前,

NodeContainer nodes;
nodes.Create (2);

现在使用编译脚本 WAF,并清除 NS_LOG 变量以禁用我们之前启用的日志流:

$ ./waf
$ export NS_LOG=
Теперь, если вы запустите скрипт,
$ ./waf --run scratch/myfirst

您不会看到新消息,因为关联的日志记录组件 (FirstScriptExample) 尚未启用。 要查看您的消息,您需要启用日志记录组件 FirstScript 示例 级别不低于 NS_LOG_INFO。 如果您只想查看这个特定的日志记录级别,您可以这样启用它,

$ 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 全局变量和属性系统的大门。 让我们在主脚本函数的开头添加两行代码 从头开始/myfirst.cc。 继续,我们编译脚本并运行它,运行时我们发出如下帮助请求,

$ ./waf --run "scratch/myfirst --PrintHelp"

该命令会询问 WAF 运行脚本 从头开始/我的第一个 并传递一个命令行参数 —打印帮助。 需要使用引号来指示该参数用于哪个程序。 命令行解析器将检测参数 —打印帮助 并会显示答案,

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.

现在让我们看看这个选项 —打印属性。 我们在研究first.cc脚本时已经提到过ns-3属性系统。 我们看到了以下几行代码,

PointToPointHelper pointToPoint;
pointToPoint.SetDeviceAttribute ("DataRate", StringValue ("5Mbps"));
pointToPoint.SetChannelAttribute ("Delay", StringValue ("2ms"));

他们说 数据速率 实际上是一个属性 点对点网络设备。 让我们使用命令行参数解析器来查看属性 点对点网络设备。 帮助列表说明了我们必须提供的内容 类型标识。 这是感兴趣的属性所属的类的名称。 在我们的例子中它将是 ns3::点对点网络设备。 让我们继续前进,进入,

$ ./waf --run "scratch/myfirst --PrintAttributes=ns3::PointToPointNetDevice"

系统将打印该网络设备类型的所有属性。 您将看到列表中的属性包括:

--ns3::PointToPointNetDevice::DataRate=[32768bps]:
The default data rate for point to point links

这是系统创建对象时将使用的默认值 点对点网络设备。 我们将使用参数覆盖这个默认值 属性 в 点对点助手 更高。 让我们使用点对点设备和通道的默认值。 为此,我们将删除通话 设置设备属性 и 设置通道属性 из 我的第一个.cc,我们在一个干净的目录中。

您的脚本现在应该简单地声明 点对点助手 并且不执行任何安装操作,如下例所示,

...
NodeContainer nodes;
nodes.Create (2);
PointToPointHelper pointToPoint;
NetDeviceContainer devices;
devices = pointToPoint.Install (nodes);
...

继续创建一个新脚本 WAF (./waff),让我们返回并包含来自 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()

回想一下,上次我们查看模拟时间,即 echo 服务器收到数据包的那一刻,它是 2,00369 秒。

2.00369s UdpEchoServerApplication:HandleRead(): Received 1024 bytes from 10.1.1.1

现在他在 2.25732 秒内收到了数据包。 这是因为我们只是将 PointToPointNetDevice 数据速率从每秒 32768 兆位重置为默认值,即每秒 XNUMX 位。 如果我们使用命令行替换新的 DataRate,我们可以再次加快模拟速度。 我们将根据 help 元素隐含的公式执行以下操作:

$ ./waf --run "scratch/myfirst --ns3::PointToPointNetDevice::DataRate=5Mbps"

这会将 DataRate 属性返回到默认值 XNUMX 兆位/秒。 您对结果感到惊讶吗? 事实证明,为了返回脚本的原始行为,我们还需要设置通道延迟以匹配光速。 我们可以要求命令行系统打印通道属性,就像我们对网络设备所做的那样:

$ ./waf --run "scratch/myfirst --PrintAttributes=ns3::PointToPointChannel"

我们会发现通道延迟属性设置如下:

--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 秒后再次收到该数据包。 我们实际上可以通过这种方式设置脚本中使用的任何属性。 特别是,我们可以将 MaxPackets 属性设置为非 XNUMX 值 UdpEcho客户端.

你会如何使用它? 试一试。 请记住,您必须注释掉我们覆盖默认属性值并显式设置的地方 最大数据包数 在脚本中。 然后您必须重建脚本。 您还可以使用命令行获取语法帮助以设置新的默认属性值。 一旦理解了这一点,您就可以控制命令行上显示的包的数量。 由于我们是勤奋好学的人,我们的命令行应该如下所示:

$ ./waf --run "scratch/myfirst
--ns3::PointToPointNetDevice::DataRate=5Mbps
--ns3::PointToPointChannel::Delay=2ms
--ns3::UdpEchoClient::MaxPackets=2"

此时出现的自然问题是如何知道所有这些属性的存在。 同样,命令行系统有针对此事的帮助功能。 如果我们向命令行寻求帮助,我们应该看到:

$ ./waf --run "scratch/myfirst --PrintHelp"
myfirst [Program Arguments] [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.

如果您选择“PrintGroups”参数,您应该会看到所有已注册组的列表 类型标识。 组名称与源目录中的模块名称一致(尽管是大写)。 一次打印所有信息会过于庞大,因此可以使用附加过滤器来按组打印信息。 因此,再次关注点对点模块:

./waf --run "scratch/myfirst --PrintGroup=PointToPoint"
TypeIds in group PointToPoint:
ns3::PointToPointChannel
ns3::PointToPointNetDevice
ns3::PointToPointRemoteChannel
ns3::PppHeader

在这里您可以找到用于属性查找的可用 TypeId 名称,例如
--PrintAttributes = ns3 :: PointToPointChannel如上图所示。

了解属性的另一种方法是通过 Doxygen ns-3。 有一个页面列出了模拟器中注册的所有属性。

5.2.2 捕获自己的命令

您还可以通过命令行系统添加自己的挂钩。 使用命令行解析器方法可以非常简单地完成此操作 添加值.
让我们使用此功能以完全不同的方式指定要显示的包的数量。 让我们添加一个名为的局部变量 数据包 变成一个函数 。 我们将其设置为 XNUMX 以匹配我们之前的默认行为。 为了允许命令行解析器更改这个值,我们需要在解析器中捕获这个值。 我们通过添加一个调用来做到这一点 添加值。 去修改一下脚本 从头开始/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 属性的位置并更改它,以便将其设置为 nPackets 变量而不是常量 1,如下所示。

echoClient.SetAttribute ("MaxPackets", UintegerValue (nPackets));

现在,如果您运行脚本并提供 -PrintHelp 参数,您应该会看到新的用户参数。 帮助显示屏上列出。 进入,

$ ./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 使用追踪系统

建模的全部目的是生成输出以供进一步研究,而 ns-3 跟踪系统是其主要机制。 由于 ns-3 是一个 C++ 程序,因此可以使用从 C++ 程序生成输出的标准方法:

#include <iostream>
...
int main ()
{
...
std::cout << "The value of x is " << x << std::endl;
...
}

您甚至可以使用日志记录模块为您的解决方案添加一些结构。 这种方法引起了许多已知的问题,因此我们提供了一个通用的事件跟踪子系统来解决这些问题。

ns-3 跟踪系统的主要目标是:

  • 对于基本任务,跟踪系统应该允许用户为流行源生成标准跟踪并选择生成跟踪的对象;

  • 中级用户应该能够扩展跟踪系统以更改生成的输出格式或插入新的跟踪源,而无需修改模拟器核心;

  • 高级用户可以修改模拟器核心以添加新的跟踪源和接收器。 ns-3追踪系统建立在独立追踪源和接收者的原则上,以及连接源和消费者的统一机制之上。

ns-3跟踪系统建立在独立跟踪源和接收器的原则以及连接源和接收器的统一机制的基础上。 跟踪源是可以发出模拟中发生的事件信号并提供对感兴趣的基础数据的访问的对象。 例如,跟踪源可以指示网络设备何时接收到数据包,并使数据包的内容可供感兴趣的跟踪接收者使用。

跟踪源本身是无用的,除非它们与代码的其他部分“耦合”,这些代码实际上对接收器提供的信息做了一些有用的事情。 跟踪器是跟踪源提供的事件和数据的使用者。 例如,您可以创建一个跟踪接收器,它将(当连接到上一个示例的跟踪源时)打印出接收到的数据包中感兴趣的部分。

这种显式分离的基本原理是允许用户将新的接收器类型连接到现有的跟踪源,而无需编辑和重新编译模拟器核心。 因此,在上面的示例中,用户可以在其脚本中定义新的跟踪器,并仅通过编辑用户脚本将其连接到模拟核心中定义的现有跟踪源。

在本教程中,我们将介绍一些预定义的源和接收器,并展示如何以用户最少的努力来配置它们。 有关高级跟踪配置的信息,请参阅 ns-3 手册或操作方法部分,包括扩展跟踪命名空间和创建新的跟踪源。

5.3.1 ASCII 跟踪

ns-3 提供了帮助程序功能,该功能提供了低级跟踪系统,可帮助您在设置简单的数据包跟踪时了解详细信息。 如果启用此功能,您将看到 ASCII 文件中的输出。 对于熟悉 ns-2 输出的人来说,这种类型的跟踪类似于 输出.tr,它是由许多脚本生成的。

让我们言归正传,将一些 ASCII 跟踪结果添加到我们的 scrap/myfirst.cc 脚本中。 就在通话之前 Simulator :: Run (),添加以下代码行:
AsciiTraceHelper ascii;

pointToPoint.EnableAsciiAll (ascii.CreateFileStream ("myfirst.tr"));

与许多其他 ns-3 习惯用法一样,此代码使用辅助对象来创建 ASCII 跟踪。 第二行包含两个嵌套方法调用。 “内部”方法 创建文件流() 使用匿名对象习惯用法在堆栈上创建文件流对象(没有对象名称)并将其传递给被调用的方法。 我们将来会更深入地讨论这个问题,但此时您需要知道的是您正在创建一个表示名为的文件的对象 我的第一个.tr 并将其传输到 ns-3。 我们委托 ns-3 在其整个生命周期中照顾所创建的对象,在此期间它解决了由与 C+​​+ 流对象复制构造函数相关的鲜为人知(有意)限制引起的问题。

外部呼叫 启用AsciiAll() 告诉助手您希望在所有点对点设备连接的模拟中包含 ASCII 跟踪,并且您希望(指定的)跟踪接收器以 ASCII 格式记录数据包移动信息。

对于熟悉 ns-2 的人来说,跟踪事件相当于记录事件“+”、“-”、“d”和“r”的已知跟踪点。
现在您可以构建脚本并从命令行运行它:

$ ./waf --run scratch/myfirst

与之前多次一样,您将看到来自 Waf 的多条消息,然后“‘构建’成功完成”以及来自正在运行的程序的一些消息。

程序运行时会创建一个名为 我的第一个.tr。 由于工作性质 WAF,默认情况下,文件不是在本地目录中创建的,而是在存储库的顶级目录中创建的。 如果你想改变保存轨迹的路径,那么你可以使用Waf参数来指定它 --cwd。 我们还没有这样做,因此要在您最喜欢的编辑器中查看 ASCII 跟踪文件 myfirst.tr,我们需要导航到存储库的顶级目录。

解析 ASCII 跟踪

其中包含相当密集的大量信息,但您首先需要注意的是该文件由单独的行组成。 如果您将观察窗口扩大得更宽,这将变得清晰可见。

文件中的每一行对应一个跟踪事件。 在这种情况下,我们跟踪模拟中每个点对点网络设备中存在的传输队列中的事件。 传输队列是点对点链路中每个数据包必须经过的队列。 请注意,跟踪文件中的每一行都以单个字符开头(后面有一个空格)。 该符号将具有以下含义:

+:设备队列上发生了排队操作;
-:设备队列中发生元素检索操作;
d:数据包被丢弃,通常是因为队列已满;
r:数据包被网络设备接收。

让我们仔细看看跟踪文件中的第一行。 我将把它分成几个部分(为了清晰起见,用缩进表示),行号在左边:

0 +
1 2
2 /NodeList/0/DeviceList/0/$ns3::PointToPointNetDevice/TxQueue/Enqueue
3 ns3::PppHeader (
4   Point-to-Point Protocol: IP (0x0021))
6   ns3::Ipv4Header (
7     tos 0x0 ttl 64 id 0 protocol 17 offset 0 flags [none]
8     length: 1052 10.1.1.1 > 10.1.1.2)
9     ns3::UdpHeader (
10      length: 1032 49153 > 9)
11      Payload (size=1024)

此扩展跟踪事件的第一部分(第 0 行)是操作。 我们这里有一个+符号,对应的是排队传输的操作。 第二部分(第 1 行)是模拟时间,以秒为单位。 你可能还记得我们问过什么 UdpEchoClient应用程序 两秒后开始发送数据包。 在这里我们看到这确实发生了。

跟踪示例的下一部分(从第 2 行开始)显示哪个跟踪源生成了此事件(指示名称空间跟踪)。 您可以将跟踪命名空间视为文件系统命名空间。 命名空间的根是 节点列表。 这对应于主 ns-3 代码中管理的容器。 它包含在脚本中创建的所有节点。 正如文件系统的根目录可以有目录一样, 节点列表 我们可以有很多节点。 所以 /NodeList/0 行指的是 NodeList 中的空节点,我们通常将其视为“节点 0”。 每个节点都有一个已安装的设备列表。 该列表位于命名空间的下一个位置。 可以看到这个trace事件来自 设备列表/0,这是节点中安装的空设备。

下一个子串, $ ns3 :: PointToPointNetDevice,告诉哪个设备位于零位置:节点零的设备列表。 回想一下,第 0 行中的 + 操作意味着将一个元素添加到设备的传输队列中。 这反映在“轨道路径”的最后一段中: 发送队列/入队.

跟踪中的其余部分应该相当直观。 第 3-4 行表示数据包被封装在点对点协议中。 第 5-7 行显示数据包具有 IP4 版本标头并且源自 IP 地址 10.1.1.1 并且旨在 10.1.1.2。 第 8-9 行显示该数据包具有 UDP 标头,最后第 10 行显示有效负载为预期的 1024 字节。

跟踪文件中的下一行显示从同一节点上的传输队列中提取了相同的数据包。

跟踪文件中的第三行显示数据包是由回显服务器主机上的网络设备接收的。 我在下面重现了该事件。

0 r
1 2.25732
2 /NodeList/1/DeviceList/0/$ns3::PointToPointNetDevice/MacRx
3   ns3::Ipv4Header (
4     tos 0x0 ttl 64 id 0 protocol 17 offset 0 flags [none]
5     length: 1052 10.1.1.1 > 10.1.1.2)
6     ns3::UdpHeader (
7       length: 1032 49153 > 9)
8       Payload (size=1024)

请注意,跟踪操作现在为 r,并且模拟时间已增加到 2,25732 秒。 如果您仔细遵循本教程,这意味着您将网络设备的 DataRate 和 Link Delay 保留为其默认值。 正如您在上一节中看到的那样,这种时态应该很熟悉。

跟踪源命名空间条目(第 2 行)已被修改,以反映此事件源自节点 1 (/节点列表/1) 并且数据包被跟踪源接收(/麦克接收器)。 通过查看文件中的剩余跟踪,您应该相当容易地跟踪数据包在拓扑中的移动。

5.3.2 PCAP 跟踪

ns-3 设备助手还可用于创建 .pcap 格式的跟踪文件。 缩写词 电容 (通常以小写形式书写)代表数据包捕获,实际上是一个 API,其中包括定义 .pcap 文件格式。 可以读取和显示这种格式的最流行的程序是 Wireshark的 (以前称为 空灵)。 然而,有许多流量跟踪分析器使用这种数据包格式。 我们鼓励用户使用许多可用的工具来分析 pcap 痕迹。 在本教程中,我们将重点关注使用查看 pcap 跟踪 转储.

启用 pcap 跟踪只需一行代码即可完成。

pointToPoint.EnablePcapAll ("myfirst");

将此行代码粘贴到我们刚刚添加的 ASCII 跟踪代码之后 从头开始/myfirst.cc。 请注意,我们只传递了字符串“myfirst”,而不是“myfirst.pcap”或类似的任何内容。 这是因为该参数是前缀,而不是完整的文件名。 在模拟过程中,助手实际上会为每个点对点设备创建一个跟踪文件。 文件名将使用前缀、节点号、设备号和后缀“.电容“。

对于我们的示例脚本,我们最终会看到名为“myfirst-0-0.pcap“和”myfirst-1-0.pcap”,分别是节点 0-设备 0 和节点 1-设备 0 的 pcap 跟踪。 添加代码行以启用 pcap 跟踪后,您可以按通常的方式运行脚本:

$ ./waf --run scratch/myfirst

如果查看发行版的顶级目录,您应该会看到三个文件: ASCII 跟踪文件 我的第一个.tr,我们之前研究过的文件 myfirst-0-0.pcap и myfirst-1-0.pcap - 我们刚刚生成的新 pcap 文件。

使用 tcpdump 读取输出

目前,查看 pcap 文件的最简单方法是使用 tcpdump。

$ 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 (客户端设备)可以看到模拟2秒后发送了echo数据包。 如果你看第二个转储(myfirst-1-0.pcap),您将看到数据包在 2,257324 秒收到。 您将在第二次转储中看到数据包在 2.257324 秒返回,最后在第一次转储中客户端在 2.514648 秒收到数据包。

使用 Wireshark 读取输出

如果你不熟悉 Wireshark的,有一个网站可以下载程序和文档: http://www.wireshark.org/. Wireshark的 是一个可用于显示这些跟踪文件的 GUI。 如果您有 Wireshark,则可以打开任何跟踪文件并显示内容,就像使用数据包嗅探器捕获数据包一样。

来源: habr.com

添加评论