这是命令 ns-3-tutorial,可以使用我们的多个免费在线工作站之一(例如 Ubuntu Online、Fedora Online、Windows 在线模拟器或 MAC OS 在线模拟器)在 OnWorks 免费托管提供商中运行
程序:
您的姓名
ns-3-tutorial - ns-3 教程
这是 ns-3 教程 . ns-3 项目的主要文档有五个
形式:
· ns-3 Doxygen的: 模拟器的公共 API 的文档
· 教程 (这个 文件)、手册和模型库 最新 释放 和
发展 树
· ns-3 维基
这份文件是写在 reStructuredText的 HPMC胶囊 人头狮身 并保持在
文档/教程 ns-3 的源代码目录。
引言
这个 ns-3 模拟器是一个离散事件网络模拟器,主要用于研究
和教育用途。 这 ns-3 项目,始于2006年,是一个开源项目
发展 ns-3.
本教程的目的是介绍新的 ns-3 用户以结构化方式访问系统
方式。 新用户有时很难从详细的信息中收集必要的信息
手册并将这些信息转换为工作模拟。 在本教程中,我们
将构建几个示例模拟,介绍和解释关键概念
随我们走的功能。
随着教程的展开,我们将介绍完整的 ns-3 文档并提供
为那些有兴趣深入研究其工作原理的人提供源代码指针
系统。
一开始有几个关键点值得注意:
· ns-3 是开源的,该项目致力于维护一个开放的环境
研究人员贡献并分享他们的软件。
· ns-3 不是向后兼容的扩展 ns-2; 这是一个新的模拟器。 他们俩
模拟器都是用 C++ 编写的,但是 ns-3 是一个新的模拟器,不支持
ns-2 蜜蜂。 部分型号来自 ns-2 已经被移植自 ns-2 至 ns-3。 该
项目将继续维持 ns-2 而 ns-3 正在建设中,将研究
过渡和整合机制。
关于我们 ns-3
ns-3 旨在提供一个开放的、可扩展的网络仿真平台,
网络研究和教育。 简单来说, ns-3 提供数据包数据如何传输的模型
网络工作和执行,并提供模拟引擎供用户进行
模拟实验。 使用的一些原因 ns-3 包括进行以下研究
在真实系统中执行、研究系统行为更加困难或不可能
在高度控制、可重复的环境中,并了解网络如何工作。
用户会注意到,可用模型设置在 ns-3 专注于对互联网的建模
协议和网络可以工作,但是 ns-3 不限于互联网系统; 几个用户
正在使用 ns-3 对非基于互联网的系统进行建模。
有许多用于网络仿真研究的仿真工具。 下面是一些
的显着特征 ns-3 与其他工具相比。
· ns-3 被设计为一组库,可以组合在一起,也可以与其他库组合
外部软件库。 虽然一些仿真平台为用户提供了
所有任务均在单一、集成的图形用户界面环境中进行
出来, ns-3 在这方面更加模块化。 多名外部动画师和数据分析
和可视化工具可以与 ns-3。 但是,用户应该期望在
命令行以及 C++ 和/或 Python 软件开发工具。
· ns-3 主要用于 Linux 系统,但也支持 FreeBSD、Cygwin
(适用于 Windows),并且本机 Windows Visual Studio 支持正在开发中
发达。
· ns-3 不是任何公司的官方支持的软件产品。 支持 ns-3
是在 ns-3-users 邮件列表上尽最大努力完成的。
对于 ns-2 用户
对于那些熟悉 ns-2 (一种流行的工具,早于 ns-3),向外最明显
搬到时改变 ns-3 就是脚本语言的选择。 节目于 ns-2 旨在
使用 OTcl 编写脚本,并且可以使用 Network Animator 将模拟结果可视化
名称。 无法运行模拟 ns-2 纯粹来自 C++(即,作为 main()
程序没有任何OTcl)。 此外,一些组件 ns-2 是用 C++ 编写的并且
OTcl 中的其他人。 在 ns-3,模拟器完全用C++编写,可选Python
绑定。 因此,模拟脚本可以用 C++ 或 Python 编写。 新动画师
和可视化工具已经可用并且正在开发中。 自从 ns-3 生成 pcap
数据包跟踪文件,其他实用程序也可用于分析跟踪。 在这个
在教程中,我们将首先专注于直接用 C++ 编写脚本并解释结果
通过跟踪文件。
但也有相似之处(例如,两者都基于 C++ 对象,并且有些
来自的代码 ns-2 已经被移植到 ns-3)。 我们将尽力突出差异
之间 ns-2 和 ns-3 当我们继续本教程时。
我们经常听到的一个问题是“我还应该使用 ns-2 或移至 ns-3?”在这
作者的意见,除非用户以某种方式归属 ns-2 (或者基于现有的
个人舒适度和知识 ns-2,或基于特定的仿真模型
仅适用于 ns-2),用户将提高工作效率 ns-3 对于以下
原因:
· ns-3 通过活跃、响应迅速的用户邮件列表进行积极维护,同时 ns-2 is
仅进行了少量维护,并且其主代码树尚未看到重大发展
十多年来
· ns-3 提供不可用的功能 ns-2,比如执行代码执行
环境(允许用户在模拟器中运行真实的实现代码)
· ns-3 与相比,提供了较低的基本抽象级别 ns-2,使其能够对齐
更好地了解真实系统的组合方式。 发现一些限制 ns-2 (如
正确支持节点上的多种类型的接口)已在 ns-3.
ns-2 拥有比以前更多样化的贡献模块 ns-3,由于其长
历史。 然而, ns-3 在几个热门研究领域有更详细的模型
(包括复杂的LTE和WiFi模型)及其对实现代码的支持
承认非常广泛的高保真模型。 用户可能会惊讶地发现
整个 Linux 网络堆栈可以封装在一个 ns-3 节点,使用直接
代码执行 (DCE) 框架。 ns-2 模型有时可以移植到 ns-3,特别是
如果它们已在 C++ 中实现。
如果有疑问,一个好的指导方针是查看两个模拟器(以及其他模拟器)
模拟器),特别是可用于您研究的模型,但请记住
您在使用正在积极开发的工具时可能会获得更好的体验,并且
保持 (ns-3).
特约
ns-3 是一个由研究界开发并服务于研究界的研究和教育模拟器。 它会
依靠社区的持续贡献来开发新模型、调试或
维护现有的并分享结果。 我们希望有一些政策能够
鼓励人们做出贡献 ns-3 就像他们一样 ns-2:
· 基于 GNU GPLv2 兼容性的开源许可
· 维基
· 缴入 代码 页面,类似于 ns-2的流行贡献代码 页
· 打开 错误 跟踪
我们意识到,如果您正在阅读本文档,那么对项目的贡献就是
此时可能不是您最关心的问题,但我们希望您意识到
贡献是本项目的精神,甚至是给我们留言的行为
关于你的早期经历 ns-3 (例如“本教程部分不清楚......”),
非常感谢陈旧文档的报告等。
教程 工作机构
本教程假设新用户最初可能遵循如下路径:
· 尝试下载并构建副本;
· 尝试运行一些示例程序;
· 查看模拟输出,并尝试调整它。
因此,我们尝试按照上述广泛的顺序来组织本教程
事件。
即将上线
这个 网上
有几个重要的资源,其中任何 ns-3 用户必须意识到。 主网
网站位于 http://www.nsnam.org 并提供有关基本信息的访问
ns-3 系统。 详细文档可通过主网站获取
http://www.nsnam.org/documentation/。 您还可以找到与系统相关的文档
此页面的架构。
有一个 Wiki 可以补充主要内容 ns-3 您可以在该网站上找到
http://www.nsnam.org/wiki/。 您可以在那里找到用户和开发人员常见问题解答,以及
故障排除指南、第三方贡献的代码、论文等。
源代码可以在以下位置找到并浏览 http://code.nsnam.org/. 在那里你会发现
存储库中当前的开发树名为 ns-3-开发。 过去的版本和
核心开发人员的实验存储库也可以在那里找到。
水银
复杂的软件系统需要某种方式来管理组织和变更
底层代码和文档。 有很多方法可以实现这一壮举,您可以
听说过一些当前用于执行此操作的系统。 并发
版本系统(CVS)可能是最著名的。
这个 ns-3 项目使用 Mercurial 作为其源代码管理系统。 虽然你不
需要了解更多关于 Mercurial 才能完成本教程,我们建议
熟悉 Mercurial 并使用它来访问源代码。 Mercurial 有一个
网站在 http://www.selenic.com/mercurial/,从中您可以获得二进制文件或源代码
此软件配置管理 (SCM) 系统的版本。 Selenic(开发者
Mercurial 的)还提供了一个教程
http://www.selenic.com/mercurial/wiki/index.cgi/Tutorial/以及快速入门指南,网址为
http://www.selenic.com/mercurial/wiki/index.cgi/QuickStart/.
您还可以找到有关使用 Mercurial 的重要信息以及 ns-3 在主要 ns-3 卷筒纸
网站。
WAF
将源代码下载到本地系统后,您将需要对其进行编译
生成可用程序的源。 就像源代码管理的情况一样,
有许多工具可用于执行此功能。 可能是其中最广为人知的
工具是 使。 除了最为人熟知之外, 使 可能是最困难的
用于非常大且高度可配置的系统。 正因为如此,有很多替代方案
已经开发了。 最近这些系统都是使用Python开发的
语言。
构建系统 Waf 用于 ns-3 项目。 它是新一代的之一
基于 Python 的构建系统。 您无需了解任何 Python 即可构建
现有 ns-3 系统。
对于那些对 Waf 的详细细节感兴趣的人,可以在以下位置找到其主要网站:
http://code.google.com/p/waf/.
研发支持 环境
如上所述,编写脚本 ns-3 是用 C++ 或 Python 完成的。 大部分的 ns-3 API是
在 Python 中可用,但在任何一种情况下模型都是用 C++ 编写的。 一个正在工作的
本文档假定您了解 C++ 和面向对象的概念。 我们将采取
花一些时间复习一些更高级的概念或可能不熟悉的语言
特征、习惯用法和设计模式的出现。 我们不希望本教程
不过,我们确实希望能够掌握该语言的基本命令。
有关 C++ 的信息来源数量几乎难以想象,可在
网络或印刷版。
如果您是 C++ 新手,您可能需要找到基于教程或食谱的书籍或网站
并在继续之前至少了解该语言的基本特征。 为了
实例 Free Introduction 教程.
这个 ns-3 系统使用 GNU“工具链”的多个组件进行开发。 A
软件工具链是给定环境中可用的一组编程工具。 为了
快速回顾一下 GNU 工具链中包含的内容,请参阅:
http://en.wikipedia.org/wiki/GNU_toolchain. ns-3 使用 gcc、GNU binutils 和 gdb。
但是,我们不使用 GNU 构建系统工具,无论是 make 还是 autotools。 我们使用瓦夫
对于这些功能。
通常一个 ns-3 作者将在 Linux 或类似 Linux 的环境中工作。 对于那些
在Windows下运行,确实存在模拟Linux环境的环境
不同程度的。 这 ns-3 项目过去(但现在)支持过
为这些用户在 Cygwin 环境中进行开发。 看 http://www.cygwin.com/ HPMC胶囊
有关下载的详细信息,请访问 ns-3 wiki 了解有关 Cygwin 的更多信息和
ns-3。 目前 MinGW 尚未得到官方支持。 Cygwin 的另一种替代方法是
安装VMware服务器等虚拟机环境,安装Linux虚拟
机。
插座 代码编程
我们将假设本示例中使用的 Berkeley Sockets API 具有基本功能
教程。 如果您不熟悉套接字,我们建议您查看 API 和一些常见用法
案例。 为了更好地概述 TCP/IP 套接字编程,我们推荐 TCP / IP协议 套接字 in
C, 多纳胡 和 卡尔弗特.
有一个相关的网站,其中包含书中示例的源代码,其中
您可以在以下位置找到: http://cs.baylor.edu/~donahoo/practical/CSockets/.
如果您理解本书的前四章(或者对于那些无法访问的人
到这本书的副本,上面网站中显示的回显客户端和服务器)您将
保持良好状态以理解本教程。 有一本类似的关于多播的书
插座, 多播 插座, 马科夫斯克 和 阿尔默罗斯。 涵盖您可能需要的材料
如果您查看发行版中的多播示例,就会明白。
抵达 已开始
本节旨在让用户从一台机器开始进入工作状态
可能从未有过 ns-3 安装。 它涵盖了支持的平台、先决条件、方法
获得 ns-3, 构建方法 ns-3以及验证您的构建和运行简单程序的方法。
概述
ns-3 被构建为一个协同工作的软件库系统。 用户程序可以是
编写与这些库链接(或从这些库导入)的内容。 用户程序写在
C++ 或 Python 编程语言。
ns-3 作为源代码分发,这意味着目标系统需要有
软件开发环境先构建库,再构建用户
程序。 ns-3 原则上可以作为选定的预构建库进行分发
系统,将来可能会这样分布式,但是目前很多用户
实际上通过编辑来完成他们的工作 ns-3 本身,所以有源代码来重建
图书馆很有用。 如果有人想承担制作预建的工作
操作系统的库和软件包,请联系 ns-developers 邮件
名单。
下面我们来看看两种下载和构建的方式 ns-3。 首先是
从主网站下载并构建正式版本。 第二个是获取
并构建开发副本 ns-3。 我们将逐步介绍这两个示例,因为工具
涉及的内容略有不同。
下载 ns-3
这个 ns-3 整个系统是一个相当复杂的系统,并且有许多依赖项
其他组件。 连同您最有可能每天处理的系统(
GNU 工具链、Mercurial、文本编辑器)您需要确保一些
在继续之前,您的系统上已经存在其他库。 ns-3 提供维基百科
页面包含许多有用的提示和技巧的页面。 其中一个页面是
“安装”页面, http://www.nsnam.org/wiki/Installation.
此 wiki 页面的“先决条件”部分解释了需要哪些软件包
支持通用 ns-3 选项,还提供了用于安装它们的命令
常见的 Linux 变体。 Cygwin 用户必须使用 Cygwin 安装程序(如果您是
Cygwin 用户,您使用它来安装 Cygwin)。
您可能想借此机会探索 ns-3 wiki 一下,因为确实有
那里有丰富的信息。
从现在开始,我们将假设读者正在 Linux 或
Linux仿真环境(Linux、Cygwin等)并已安装GNU工具链并
与上述先决条件一起验证。 我们还将假设
您已在目标系统上安装并运行 Mercurial 和 Waf。
这个 ns-3 代码可在服务器上的 Mercurial 存储库中找到 http://code.nsnam.org.
您还可以在以下位置下载 tarball 版本: http://www.nsnam.org/release/,或者你可以工作
使用 Mercurial 的存储库。 我们建议使用 Mercurial,除非有好的
不这样做的理由。 有关如何获取 tarball 的说明,请参阅本节末尾
释放。
开始使用 Mercurial 存储库的最简单方法是使用 ns-3-蒜酮
环境。 这是一组管理下载和构建的脚本
的各个子系统 ns-3 为你。 我们建议您开始您的 ns-3 从事这方面的工作
环境。
一种做法是创建一个名为的目录 工作空间 在某人的主目录下
人们可以保留本地 Mercurial 存储库。 任何目录名都可以,但我们假设
这 工作空间 此处使用(注: 休息 也可以在某些文档中用作
示例目录名称)。
下载 ns-3 运用 a 塔球
tarball 是一种特殊的软件存档格式,其中捆绑了多个文件
在一起并且存档可能被压缩。 ns-3 软件版本是通过
可下载的压缩包。 下载过程 ns-3 通过 tarball 很简单; 你刚才
必须选择一个版本,下载并解压缩。
假设您作为用户希望构建 ns-3 在名为的本地目录中
工作空间。 如果您采用 工作空间 目录方法,您可以获得发行版的副本
通过在 Linux shell 中输入以下内容(替换适当的版本号,
当然):
$ cd
$ mkdir 工作区
$ cd 工作区
$ wget http://www.nsnam.org/release/ns-allinone-3.22.tar.bz2
$ tar xjf ns-allinone-3.22.tar.bz2
如果你切换到目录 ns-蒜酮-3.22 您应该看到许多文件:
$ls
烘焙常量.py ns-3.22 自述文件
build.py netanim-3.105 pybindgen-0.16.0.886 util.py
您现在已准备好建造基地 ns-3 分布。
下载 ns-3 运用 烤
Bake 是一个分布式集成和构建工具,专为 ns-3 项目。
Bake 可用于获取开发版本 ns-3 软件,以及下载和
构建基础的扩展 ns-3 分发,例如直接代码执行
环境、网络模拟摇篮、创建新的 Python 绑定的能力等等。
在最近 ns-3 发布后,Bake 已包含在发布 tarball 中。 配置
发布版本中包含的文件将允许人们下载以前发布的任何软件
发布时的当前状态。 例如,Bake 的版本是
分布与 ns-3.21 release 可用于获取组件 ns-3 释放
或更早版本,但不能用于获取后续版本的组件(除非
烘焙配置文件 文件已更新)。
您还可以获得最新的副本 烤 在你的 Linux 中输入以下内容
shell(假设您已经安装了 Mercurial):
$ cd
$ mkdir 工作区
$ cd 工作区
$ hg 克隆 http://code.nsnam.org/bake
当 hg (Mercurial) 命令执行时,您应该看到类似以下内容
显示,
...
目标目录:烘焙
请求所有更改
添加变更集
添加清单
添加文件更改
添加了 339 个变更集,其中对 796 个文件进行了 63 项更改
更新到分支默认值
更新了 45 个文件,合并了 0 个文件,删除了 0 个文件,0 个文件未解决
克隆命令完成后,您应该有一个名为 烤, 内容
其中应该类似于以下内容:
$ls
烘焙 Bakeryconf.xml 文档生成二进制.py TODO
Baker.py 示例测试
请注意,您实际上只是下载了一些 Python 脚本和一个名为的 Python 模块
烤。 下一步将使用这些脚本下载并构建 ns-3
您选择的分布。
有一些可用的配置目标:
1. ns-3.22:release对应的模块; 它将下载类似的组件
到发布 tarball。
2. ns-3-开发:类似的模块,但使用开发代码树
3. ns-蒜酮-3.22:包含其他可选功能(例如点击)的模块
路由、开放流 ns-3,以及网络模拟底座
4. ns-3-蒜酮:与 allinone 模块的发布版本类似,但是对于
开发代码。
当前的开发快照(未发布) ns-3 可以在
http://code.nsnam.org/ns-3-dev/。 开发人员尝试将这些存储库保留在
一致的工作状态,但它们处于具有未发布代码的开发区域
目前,因此如果不需要,您可能需要考虑保留官方版本
新推出的功能。
您可以通过检查存储库列表找到最新版本的代码
或前往 “ns-3 发布” 网页并单击最新版本链接。
我们将继续本教程示例 ns-3.22.
我们现在将使用烘焙工具来拉下各个部分 ns-3 你将会
使用。 首先,我们来谈谈运行烘焙。
Baker的工作原理是将源包下载到源目录中,然后安装
库到构建目录。 Baker 可以通过引用二进制文件来运行,但如果
选择从下载到的目录外部运行bake,这是明智的
将烘焙放入您的路径中,如下所示(Linux bash shell 示例)。 首先,改变
进入“bake”目录,然后设置以下环境变量
$导出BAKE_HOME=`pwd`
$导出PATH=$PATH:$BAKE_HOME:$BAKE_HOME/build/bin
$ 导出 PYTHONPATH=$PYTHONPATH:$BAKE_HOME:$BAKE_HOME/build/lib
这会将bake.py程序放入shell的路径中,并允许其他程序
查找由bake 创建的可执行文件和库。 尽管一些烘焙用例不
需要如上所述设置 PATH 和 PYTHONPATH,完整构建 ns-3-allinone(使用
可选包)通常是这样。
进入工作区目录并在 shell 中输入以下内容:
$ ./bake.py 配置-e ns-3.22
接下来,我们将要求 Baker 检查我们是否有足够的工具来下载各种组件。
类型:
$ ./bake.py 检查
您应该看到类似以下内容,
> Python - 好的
> GNU C++ 编译器 - 好的
> Mercurial - 好的
> CVS - 好的
> 吉特 - 好的
> 集市 - 好的
> Tar 工具 - 好
> 解压工具-确定
> 解压工具 - 丢失
> 7z 数据压缩实用程序 - 好
> XZ 数据压缩实用程序 - 好
> 制作 - 确定
> cMake - 好的
> 补丁工具-确定
> autoreconf 工具 - 好的
> 工具搜索路径:/usr/lib64/qt-3.3/bin /usr/lib64/ccache
在/ usr / local / bin目录 /箱 / usr / bin / usr / local / sbin / usr / sbin /宾
/home/tomh/bin 箱
尤其是Mercurial、CVS、GIT、Bazaar等下载工具是我们的主要产品
此时值得关注,因为它们允许我们获取代码。 请安装缺少的
在此阶段使用适合您系统的常用方式(如果可以的话)使用工具,或联系
您的系统管理员根据需要安装这些工具。
接下来,尝试下载软件:
$ ./bake.py 下载
应该产生类似的结果:
>> 搜索系统依赖 pygoocanvas - OK
>> 搜索系统依赖 python-dev - OK
>> 搜索系统依赖 pygraphviz - OK
>> 下载 pybindgen-0.16.0.886 - 好的
>> 搜索系统依赖项 g++ - OK
>> 搜索系统依赖 qt4 - OK
>> 下载 netanim-3.105 - 确定
>> 下载 ns-3.22 - 好的
上面表明已经下载了三个源。 检查 资源 目录
现在并输入 ls; 人们应该看到:
$ls
netanim-3.105 ns-3.22 pybindgen-0.16.0.886
您现在已准备好构建 ns-3 分布。
构建 ns-3
构建 - 构建.py
当使用已发布的 tarball 进行工作时,第一次构建 ns-3 你可以做的项目
使用在以下位置找到的便捷程序进行构建 ALLINONE 目录。 这个程序叫做
构建.py。 该程序将以最常见的方式为您配置项目
有用的方法。 但是,请注意,更高级的配置和使用 ns-3 将
通常涉及使用本机 ns-3 构建系统 Waf,稍后将介绍
教程。
如果您使用 tarball 下载,您应该有一个名为类似的目录
ns-蒜酮-3.22 在你的 〜/工作区 目录。 输入以下内容:
$ ./build.py --enable-examples --enable-tests
因为我们正在本教程中使用示例和测试,并且因为它们不是
默认内置于 ns-3,build.py 的参数告诉它为我们构建它们。 这
程序还默认构建所有可用的模块。 稍后,您可以构建 ns-3
没有示例和测试,或者删除您的工作不需要的模块,
如果你希望。
您将看到构建脚本构建时显示许多典型的编译器输出消息
您下载的各个部分。 最终您应该看到以下内容:
Waf:离开目录“/path/to/workspace/ns-allinone-3.22/ns-3.22/build”
“构建”成功完成(6m25.032s)
构建的模块:
天线 AODV 应用
桥梁建筑配置存储
核心 csma csma 布局
DSDV DSR 能源
fd-net-设备流量监控互联网
LR-WPAN LTE 网状网络
移动性 mpi netanim(无 Python)
网络 nix-矢量路由 olsr
点对点点对点布局传播
Sixlowpan 频谱统计
分接桥测试(无 Python)拓扑读取
uan 虚拟网络设备 wave
无线网络 Wimax
未构建的模块(有关说明,请参阅 ns-3 教程):
布里特点击开放流
可视化
离开目录“./ns-3.22”
关于未构建模块的部分:
未构建的模块(有关说明,请参阅 ns-3 教程):
布里特点击开放流
可视化
这仅仅意味着一些 ns-3 依赖于外部库的模块可能不会
已经构建,或者配置特别要求不要构建它们。 确实如此
并不意味着模拟器没有成功构建或者它将提供错误的
列出为正在构建的模块的结果。
构建 - 烤
如果您使用上面的bake从项目存储库中获取源代码,您可以继续
用它来构建 ns-3。 类型
$ ./bake.py 构建
你应该看到类似的东西:
>> 构建 pybindgen-0.16.0.886 - 好的
>> 构建 netanim-3.105 - 好的
>> 构建 ns-3.22 - 好的
暗示: 能够 还 演出 都 脚步, download 和 建立 by 调用 '烘焙.py 部署'。
如果发生失败,请看一下下面的命令告诉我们什么
你; 它可能会提示缺少依赖项:
$ ./bake.py 显示
这将列出您尝试构建的包的各种依赖项。
构建 - WAF
到目前为止,我们已经使用了 构建.py 脚本,或者 烤 工具,得到
从建造开始 ns-3。 这些工具对于构建非常有用 ns-3 和支持
图书馆,他们调用 ns-3 调用 Waf 构建工具执行以下操作的目录
实际建筑。 大多数用户很快过渡到直接使用 Waf 进行配置和
建立 ns-3。 因此,要继续,请将您的工作目录更改为 ns-3 目录
您最初构建的。
目前这并不是严格要求的,但稍稍绕道是有价值的
并查看如何更改项目的配置。 可能是最多的
您可以进行的有用配置更改将是构建优化版本
代码。 默认情况下,您已将项目配置为构建调试版本。 让我们告诉
该项目进行优化构建。 向Waf解释它应该做优化
包含示例和测试的构建,您将需要执行以下命令
命令:
$ ./waf 干净
$ ./waf --build-profile=optimized --enable-examples --enable-tests 配置
这将从本地目录中运行 Waf(这是为了方便您而提供的)。
清除先前构建的第一个命令通常不是绝对必要的,但是
是很好的做法(但请参阅 构建 简介, 以下); 它将删除之前构建的
在目录中找到的库和目标文件 建立/。 当项目重新配置时
并且构建系统检查各种依赖关系,您应该看到看起来像的输出
类似于以下内容:
将 top 设置为 : 。
着手:构建
检查“gcc”(c 编译器):/usr/bin/gcc
检查 cc 版本:4.2.1
检查“g++”(c++ 编译器):/usr/bin/g++
检查升压包括:1_46_1
检查升压库:好的
检查升压链接:好的
检查点击位置:未找到
检查程序 pkg-config : /sw/bin/pkg-config
检查 'gtk+-2.0' >= 2.12 :是
检查“libxml-2.0”> = 2.7:是
检查类型 uint128_t :未找到
检查类型 __uint128_t :是
检查高精度实现:128 位整数(默认)
检查标头 stdint.h :是
检查标头 inttypes.h :是
检查标头 sys/inttypes.h :未找到
检查标头 sys/types.h :是
检查标头 sys/stat.h :是
检查头文件 dirent.h :是
检查标头 stdlib.h :是
检查标头 signal.h :是
检查头文件 pthread.h :是
检查标头 stdint.h :是
检查标头 inttypes.h :是
检查标头 sys/inttypes.h :未找到
检查库 rt:未找到
检查标头 netpacket/packet.h :未找到
检查标头 sys/ioctl.h :是
检查标头 net/if.h :未找到
检查标头 net/ethernet.h :是
检查头文件 linux/if_tun.h :未找到
检查标头 netpacket/packet.h :未找到
检查 NSC 位置:未找到
检查“mpic++”:是
检查“sqlite3”:是
检查头文件 linux/if_tun.h :未找到
检查程序 sudo : /usr/bin/sudo
检查程序 valgrind :/sw/bin/valgrind
检查“gsl”:是
检查编译标志 -Wno-error=deprecated-d... 支持:好的
检查编译标志 -Wno-error=deprecated-d... 支持:好的
检查编译标志 -fstrict-aliasing... 支持:好的
检查编译标志 -fstrict-aliasing... 支持:好的
检查编译标志 -Wstrict-aliasing... 支持:好的
检查编译标志 -Wstrict-aliasing... 支持:好的
检查程序 doxygen : /usr/local/bin/doxygen
---- 可选 NS-3 功能摘要:
构建配置文件:调试
构建目录:构建
Python 绑定:已启用
BRITE 集成:未启用(BRITE 未启用(请参阅选项 --with-brite))
NS-3 Click 集成:未启用(nsclick 未启用(请参阅选项 --with-nsclick))
GtkConfigStore :启用
XmlIo:已启用
线程原语:启用
实时模拟器:已启用(librt 不可用)
模拟网络设备:已启用( 包括未检测到)
文件描述符 NetDevice :启用
点击 FdNetDevice :未启用(需要 linux/if_tun.h)
仿真 FdNetDevice:未启用(需要 netpacket/packet.h)
PlanetLab FdNetDevice:未启用(未检测到 PlanetLab 操作系统(请参阅选项 --force-planetlab))
网络模拟摇篮:未启用(未找到 NSC(请参阅选项 --with-nsc))
MPI 支持:已启用
NS-3 OpenFlow 集成:未启用(未找到所需的 boost 库,缺少:系统、信号、文件系统)
SQlite 统计数据输出:启用
点击桥接:未启用( 包括未检测到)
PyViz 可视化工具:已启用
使用 sudo 设置 suid 位:未启用(未选择选项 --enable-sudo)
构建测试:启用
构建示例:启用
GNU 科学库 (GSL):已启用
“配置”成功完成(1.944s)
请注意上述输出的最后一部分。 一些 ns-3 默认情况下未启用选项或
需要底层系统的支持才能正常工作。 例如,要启用
XmlTo,必须在系统上找到库 libxml-2.0。 如果这个库没有
发现,对应的 ns-3 该功能将不会被启用,并且会出现一条消息
显示。 进一步注意,有一个使用该程序的功能 须藤 设置suid
某些程序的一些内容。 默认情况下未启用此功能,因此报告了此功能
为“未启用”。
现在继续切换回包含示例和测试的调试版本。
$ ./waf 干净
$ ./waf --build-profile=debug --enable-examples --enable-tests 配置
构建系统现已配置完毕,您可以构建调试版本 ns-3
只需键入即可进行程序
$./waf
好吧,抱歉,我让你建造了 ns-3 系统的一部分两次,但现在您知道如何
更改配置并构建优化的代码。
上面讨论的 build.py 脚本还支持 --启用示例 和 启用测试
参数,但一般来说,不直接支持其他WAF选项; 例如,这个
不管用:
$ ./build.py --disable-python
将导致
build.py:错误:没有这样的选项:--disable-python
然而,特殊的操作符 -- 可用于将附加选项传递给 waf,因此
代替上述内容,以下内容将起作用:
$ ./build.py -- --disable-python
因为它生成底层命令 ./waff 配置 --禁用Python.
这里还有一些关于 Waf 的介绍性技巧。
配置 与 构建
有些 Waf 命令仅在配置阶段才有意义,有些命令在配置阶段才有意义
在构建阶段有效。 例如,如果您想使用以下仿真功能
ns-3,您可能希望使用 sudo 启用设置 suid 位,如上所述。 这
事实证明这是一个配置时命令,因此您可以使用以下命令重新配置
以下命令还包括示例和测试。
$ ./waf configure --enable-sudo --enable-examples --enable-tests
如果您这样做,Waf 将运行 sudo 来更改套接字创建程序
以 root 身份运行的模拟代码。
Waf 中还有许多其他配置和构建时选项。 去探索这些
选项,类型:
$ ./waf --帮助
我们将在下一节中使用一些与测试相关的命令。
构建 简介
我们已经了解了如何配置 Waf 调试 or 优化 构建:
$ ./waf --build-profile=调试
还有一个中间构建配置文件, 释放. -d 是同义词
--构建配置文件.
默认情况下,Waf 将构建工件放在 建立 目录。 您可以指定一个
不同的输出目录 - 出去 选项,例如
$ ./waf 配置 --out=foo
将其与构建配置文件相结合可以让您在不同的编译选项之间切换
以干净的方式:
$ ./waf configure --build-profile=debug --out=build/debug
$ ./waf 构建
...
$ ./waf configure --build-profile=optimized --out=build/optimized
$ ./waf 构建
...
这允许您使用多个构建,而不是总是覆盖最后一个构建
建造。 当你切换时,Waf 只会编译它需要的内容,而不是重新编译
一切。
当您像这样切换构建配置文件时,您必须小心地给出相同的配置文件
每次配置参数。 定义一些环境可能会很方便
帮助您避免错误的变量:
$ 导出 NS3CONFIG="--enable-examples --enable-tests"
$ 导出 NS3DEBUG="--build-profile=debug --out=build/debug"
$ 导出 NS3OPT=="--build-profile=optimized --out=build/optimized"
$ ./waf 配置 $NS3CONFIG $NS3DEBUG
$ ./waf 构建
...
$ ./waf 配置 $NS3CONFIG $NS3OPT
$ ./waf 构建
编译器
在上面的示例中,Waf 使用 GCC C++ 编译器, 克++, 用于构建 ns-3。 然而,
可以通过定义来更改 Waf 使用的 C++ 编译器 CXX 环境
多变的。 例如,要使用 Clang C++ 编译器, 叮当++,
$ CXX="clang++" ./waf 配置
$ ./waf 构建
还可以设置 Waf 进行分布式编译 分区 以类似的方式:
$ CXX="distcc g++" ./waf 配置
$ ./waf 构建
更多信息 分区 分布式编译可以在它的上找到 项目 页 下
文档部分。
安装
Waf 可用于在系统上的各个位置安装库。 默认
构建库和可执行文件的位置位于 建立 目录,并且因为
Waf 知道这些库和可执行文件的位置,无需安装
其他地方的图书馆。
如果用户选择在构建目录之外安装东西,用户可以发出
./waff 安装 命令。 默认情况下,安装的前缀是 在/ usr /本地,所以 ./waff
安装 将安装程序到 在/ usr / local / bin目录, 库进入 在/ usr / local / lib目录和
标题进入 /usr/local/包括。 通常需要超级用户权限才能安装
默认前缀,所以典型的命令是 须藤 ./waff 安装. 跑步时
使用 Waf 编写程序时,Waf 首先会优先使用构建目录中的共享库,
然后会在本地环境中配置的库路径中查找库。 所以
将库安装到系统时,最好检查预期的库
正在使用库。
用户可以通过传递以下命令来选择安装到不同的前缀 - 字首 在选项
配置时间,例如:
./waf 配置 --prefix=/opt/local
如果稍后在构建之后用户发出 ./waff 安装 命令,前缀 /选择/本地
将被使用。
这个 ./waff 清洁 如果 Waf 将被配置,则应在重新配置项目之前使用命令
用于以不同的前缀安装东西。
综上所述,不需要调用 ./waff 安装 使用 ns-3。 大多数用户不会
需要此命令,因为 Waf 将从中获取当前库 建立 目录,
但如果某些用户的用例涉及使用外部程序,则可能会发现它很有用
的 ns-3 目录。
一个 WAF
只有一个 Waf 脚本,位于 Waf 的顶层 ns-3 源树。 当你工作时,你
可能会发现自己花了很多时间 划痕/,或深入 源代码/...,并且需要
调用 Waf。 您只需记住您所在的位置,然后像这样调用 Waf:
$ ../../../waf ...
但这很乏味,而且容易出错,而且有更好的解决方案。
如果你有完整的 ns-3 存储库这个小宝石是一个开始:
$ cd $(hg root) && ./waf ...
更好的方法是将其定义为 shell 函数:
$ function waff { cd $(hg root) && ./waf $* ; }
$ waff 构建
如果您只有 tarball,环境变量可以提供帮助:
$ 导出 NS3DIR="$PWD"
$ function waff { cd $NS3DIR && ./waf $* ; }
$ CD 刮擦
$ waff 构建
在模块目录中添加一个简单的内容可能很诱人 WAF 脚本沿着
EXEC ../../WAF。 请不要。 这会让新手感到困惑,如果做得不好的话
导致微妙的构建错误。 上述解决方案是可行的方法。
测试与验证 ns-3
您可以运行单元测试 ns-3 通过运行分发 ./测试.py -c 核心
脚本:
$ ./test.py -c 核心
这些测试由 Waf 并行运行。 您最终应该会看到一份报告说
92 个测试中有 92 个通过(92 个通过,0 个失败,0 个崩溃,0 个 valgrind 错误)
这是重要的信息。
您还将看到 Waf 的摘要输出以及执行每个测试的测试运行程序,
它实际上看起来像这样:
Waf:进入目录“/path/to/workspace/ns-3-allinone/ns-3-dev/build”
Waf:离开目录“/path/to/workspace/ns-3-allinone/ns-3-dev/build”
“构建”成功完成(1.799s)
构建的模块:
aodv应用桥
单击配置存储核心
csma csma 布局 DSDV
emu能量流监测器
互联网 LTE 网状网络
移动性 MPI Netanim
网络 nix-矢量路由 ns3tcp
ns3wifi olsr 开放流
点对点点对点布局传播
频谱统计分接桥
模板测试工具
拓扑读取 uan 虚拟网络设备
可视化仪 wifi wimax
通过:TestSuite ns3-wifi-干扰
通过:TestSuite 直方图
...
PASS:TestSuite 对象
PASS:TestSuite 随机数生成器
92 个测试中有 92 个通过(92 个通过,0 个失败,0 个崩溃,0 个 valgrind 错误)
此命令通常由用户运行以快速验证 ns-3 分布有
正确构建。 (注意顺序 经过: ... 线条可能会有所不同,这没关系。 什么是
重要的是最后的总结行报告所有测试都通过了; 没有失败或
坠毁了。)
运行 a 脚本
我们通常在 Waf 的控制下运行脚本。 这使得构建系统能够确保
共享库路径设置正确并且库可在
运行。 要运行程序,只需使用 - 跑 Waf 中的选项。 让我们运行 ns-3
相当于无处不在的 hello world 程序,输入以下内容:
$ ./waf --运行 hello-simulator
Waf 首先检查以确保程序构建正确,如果存在则执行构建
必需的。 然后 Waf 执行该程序,产生以下输出。
你好模拟器
恭喜! 您现在是 ns-3 用户了!
什么是 do I do if I 别 请点击 这些因素包括原料奶的可用性以及达到必要粉末质量水平所需的工艺。 输出?
如果您看到 Waf 消息表明构建已成功完成,但没有看到
看到“Hello Simulator”输出,很可能您已将构建模式切换为
优化 ,在 构建 - WAF 部分,但错过了改回 调试 模式。
本教程中使用的所有控制台输出都使用特殊的 ns-3 日志记录组件
对于将用户消息打印到控制台很有用。 该组件的输出是
当您编译优化代码时自动禁用 - 它被“优化掉”。 如果你
看不到“Hello Simulator”输出,请输入以下内容:
$ ./waf 配置 --build-profile=debug --enable-examples --enable-tests
告诉 Waf 构建调试版本 ns-3 包含示例的程序
和测试。 您仍然必须通过键入来构建代码的实际调试版本
$./waf
现在,如果您运行 你好模拟器 程序,您应该看到预期的输出。
教学计划 参数
将命令行参数提供给 ns-3 程序使用这种模式:
$ ./waf --运行--命令模板=“%s” ”
将您的程序名称替换为 ,以及论据 。 该
--命令模板 Waf 的论证基本上是构建实际的方法
Waf 应该使用命令行来执行程序。 Waf 检查构建是否正确
完成,设置共享库路径,然后使用提供的调用可执行文件
命令行模板,插入程序名称 %s 占位符。 (这个我承认
是有点尴尬,但就是这样。 欢迎补丁!)
另一个特别有用的示例是单独运行测试套件。 我们假设一个
我的测试 测试套件存在(它不存在)。 上面,我们使用了 ./测试.py 运行整个脚本
通过重复调用真实的测试程序,并行进行大量测试, 测试运行器.
调用 测试运行器 直接进行单个测试:
$ ./waf --run test-runner --command-template="%s --suite=mytest --verbose"
这将参数传递给 测试运行器 程序。 自从 我的测试 不存在,一个
将生成错误消息。 打印可用的 测试运行器 opţiuni:
$ ./waf --run test-runner --command-template="%s --help"
调试
跑步 ns-3 程序在另一个实用程序的控制下,例如调试器(例如 GDB)
或内存检查器(例如 瓦尔格林德),你使用类似的 --命令模板=“...” 形式。
例如,运行您的 ns-3 程序 你好模拟器 与论点 在下面
GDB 调试器:
$ ./waf --run=hello-simulator --command-template="gdb %s --args ”
注意, ns-3 程序名称与 - 跑 参数和控制效用
(这里 GDB) 是第一个标记 --命令模板 争论。 这 --参数 告诉 GDB
命令行的其余部分属于“劣等”程序。 (一些 GDB's
不明白 --参数 特征。 在这种情况下,请省略程序参数
--命令模板,并使用该 GDB 命令 集 ARGS.)
我们可以结合这个方法和前一个方法在调试器下运行测试:
$ ./waf --run test-runner --command-template="gdb %s --args --suite=mytest --verbose"
工进 目录
Waf 需要从其顶部的位置运行 ns-3 树。 这成为工作
将写入输出文件的目录。 但如果你想把这些 ouf 保留下来怎么办?
这些因素包括原料奶的可用性以及达到必要粉末质量水平所需的工艺。 ns-3 源树? 使用 --cwd 参数:
$ ./waf --cwd=...
从您想要输出的工作目录开始可能会更方便
文件,在这种情况下,一点间接可以帮助:
$函数瓦夫{
CWD=“$PWD”
cd $NS3DIR >/dev/null
./waf --cwd="$CWD" $*
cd->/dev/null
}
这个对上一个版本的点缀保存了当前工作目录, cd是为了
Waf 目录,然后指示 Waf 更改工作目录 背部 对已保存的
运行程序之前的当前工作目录。
概念性 产品详情
在真正开始查看或写作之前我们需要做的第一件事 ns-3 代码是
解释系统中的一些核心概念和抽象。 其中大部分可能会出现
对某些人来说这是显而易见的,但我们建议花时间阅读本文
部分只是为了确保您有一个坚实的基础。
主要 抽象
在本节中,我们将回顾一些网络中常用的术语,但有一个
具体含义在 ns-3.
Node
在互联网术语中,连接到网络的计算设备称为 主持人 or
有时一个 end 系统。 因为 ns-3 是一个 网络 模拟器,不是专门的
网络 模拟器,我们故意不使用术语“主机”,因为它与
与互联网及其协议相关。 相反,我们也使用更通用的术语
被其他源自图论的模拟器所使用—— 节点.
In ns-3 基本的计算设备抽象称为节点。 这个抽象是
在 C++ 中由类表示 Node。 该 Node 类提供了管理方法
模拟中计算设备的表示。
你应该想一个 Node 作为您要添加功能的计算机。 一位补充说
应用程序、协议栈和外围卡及其相关的东西
驱动程序使计算机能够执行有用的工作。 我们使用相同的基本模型 ns-3.
实践应用
通常,计算机软件分为两大类。 系统 软件 举办
各种计算机资源,例如内存、处理器周期、磁盘、网络等,
根据某种计算模型。 系统软件通常不使用这些资源
完成使用户直接受益的任务。 用户通常会运行 应用
获取并使用系统软件控制的资源来完成某些任务
的目标。
通常,系统和应用软件之间的界限是在
操作系统陷阱中发生的特权级别更改。 在 ns-3 没有真正的
操作系统的概念,尤其是没有特权级别或系统调用的概念。
然而,我们确实有一个应用程序的想法。 正如软件应用程序运行于
计算机在“现实世界”中执行任务, ns-3 应用程序运行于 ns-3 Nodes 至
在模拟世界中驱动模拟。
In ns-3 用户程序的基本抽象,生成一些活动
模拟的是应用程序。 这种抽象在 C++ 中由类表示
实践应用。 该 实践应用 类提供了管理表示的方法
我们的模拟用户级应用程序版本。 开发商预计
专门化 实践应用 面向对象编程意义上的类创建新的
应用程序。 在本教程中,我们将使用类的专业化 实践应用 被称为
UdpEchoClient应用程序 和 UdpEchoServer应用程序。 正如您所料,这些
应用程序组成一个客户端/服务器应用程序集,用于生成和回显模拟
网络数据包
频道
在现实世界中,人们可以将计算机连接到网络。 经常有媒体报道
这些网络中的数据流称为 通道。 当您将以太网电缆连接到
墙上的插头,您正在将计算机连接到以太网通信
渠道。 在模拟世界中 ns-3,一个连接一个 Node 到代表一个的对象
沟通渠道。 这里基本的通信子网抽象被称为
通道并在 C++ 中由类表示 频道.
这个 频道 类提供了管理通信子网对象的方法和
将节点连接到它们。 频道 也可能由对象的开发人员专门化
面向编程意识。 A 频道 专业化可以模拟像简单的东西
金属丝。 专门的 频道 还可以对像大型以太网这样复杂的事物进行建模
开关,或者无线网络中充满障碍物的三维空间。
我们将使用专门版本的 频道 被称为 Csma频道, 点对点信道
和 Wifi频道 在本教程中。 这 Csma频道,例如,对一个版本进行建模
通信子网实现了 承运人 感 多 ACCESS 场外通讯
中等的。 这为我们提供了类似以太网的功能。
净 设备
过去的情况是,如果您想将计算机连接到网络,您必须
购买一种特定类型的网络电缆和称为(PC 术语)的硬件设备
外围设备 卡 需要安装在您的计算机上。 如果是外设卡
实现了一些网络功能,它们被称为网络接口卡,或者 网卡.
如今,大多数计算机都内置了网络接口硬件,但用户看不到
这些构建块。
如果没有软件驱动程序来控制硬件,网卡将无法工作。 在 Unix 中(或
Linux),一个外围硬件被分类为 设备。 设备受控
运用 设备 驱动程序,并使用网络设备(NIC)进行控制 网络 设备
驱动程序 统称为 净 设备。 在 Unix 和 Linux 中你指的是这些网络
设备的名称如 eth0.
In ns-3 这些因素包括原料奶的可用性以及达到必要粉末质量水平所需的工艺。 净 设备 抽象涵盖软件驱动程序和模拟
硬件。 网络设备被“安装”在 Node 为了使 Node 至
与他人交流 Nodes 在模拟中通过 频道。 就像在真正的计算机中一样,
a Node 可能连接到多个 频道 通过多个 网络设备.
网络设备抽象在 C++ 中由类表示 网络设备。 该 网络设备
类提供了管理连接的方法 Node 和 频道 物体; 有可能
由面向对象编程意义上的开发人员专门开发。 我们将使用
的几个专门版本 网络设备 被称为 网络设备, 点对点网络设备,
和 WifiNet设备 在本教程中。 正如以太网 NIC 的设计目的是与
以太网网络, 网络设备 旨在与 Csma频道;的
点对点网络设备 旨在与 点对点信道 和 Wifi网络服务
旨在与 Wifi频道.
拓扑 助手
在真实网络中,您会发现带有添加(或内置)NIC 的主机。 在 ns-3 we
会说你会发现 Nodes 附有 网络设备。 在大型模拟网络中
你需要在之间安排许多连接 Nodes, 网络设备 和 频道.
自从连接以来 网络设备 至 Nodes, 网络设备 至 频道,分配IP地址,
等等,这些都是常见的任务 ns-3,我们提供我们所说的 拓扑 佣工 做这个
尽可能简单。 例如,可能需要许多不同的 ns-3 核心业务
创建一个网络设备,添加一个 MAC 地址,将该网络设备安装在 Node,配置
节点的协议栈,然后连接 网络设备 到 频道。 更多操作
需要将多个设备连接到多点通道,然后连接
各个网络一起形成互联网络。 我们提供拓扑辅助对象
为了您的方便,将这些许多不同的操作组合成一个易于使用的模型。
A (名) ns-3 脚本
如果您按照上面的建议下载了系统,您将获得一个版本 ns-3 育明在
目录称为 休息 在你的主目录下。 切换到该发布目录,然后
您应该找到如下所示的目录结构:
作者示例 scrap utils waf.bat*
绑定许可证 src utils.py waf-tools
构建 ns3 test.py* utils.pyc wscript
CHANGES.html 自述文件 testpy-output 版本 wutils.py
文档 RELEASE_NOTES testpy.supp waf* wutils.pyc
更改为 示例/教程 目录。 您应该看到一个名为 第一个.cc 位于
那里。 这是一个将在两个节点之间创建简单的点对点链接的脚本
并在节点之间回显单个数据包。 让我们看一下该脚本行
线,所以继续打开 第一个.cc 在您最喜欢的编辑器中。
样板
文件中的第一行是 emacs 模式行。 这告诉 emacs 有关格式化的信息
我们在源代码中使用的约定(编码风格)。
/* -*- 模式:C++; c-文件样式:“gnu”; 缩进制表符模式:nil; -*- */
这始终是一个有点争议的话题,所以我们不妨先把它撇开
立即地。 这 ns-3 与大多数大型项目一样,该项目采用了一种编码风格
所有贡献的代码都必须遵守。 如果您想将您的代码贡献给
项目,你最终必须遵守 ns-3 编码标准如所述
文件 文档/codingstd.txt 或显示在项目网页上 点击这里.
我们建议您习惯一下它的外观和感觉 ns-3 编码并采用
每当您使用我们的代码时,请遵循此标准。 所有的开发团队和
贡献者这样做时带着不同程度的抱怨。 上面的 emacs 模式行
如果您使用 emacs 编辑器,可以更轻松地获得正确的格式。
这个 ns-3 模拟器使用 GNU 通用公共许可证进行许可。 你会看到
适当的 GNU 法律术语位于每个文件的开头 ns-3 分配。 常常你
将看到参与其中的机构之一的版权声明 ns-3 上面的项目
下面列出了 GPL 文本和作者。
/*
* 本程序是免费软件; 您可以重新分发它和/或修改
* 根据 GNU 通用公共许可证第 2 版的条款
* 由自由软件基金会出版;
*
* 该程序发布是为了希望它有用,
* 但不提供任何保证; 甚至没有默示保证
* 适销性或特定用途的适用性。 请参阅
* GNU 通用公共许可证了解更多详细信息。
*
* 您应该已收到 GNU 通用公共许可证的副本
* 与此程序一起使用; 如果没有,请写信给自由软件
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 美国
*/
模块 包括
代码本身以许多包含语句开始。
#include“ns3/core-module.h”
#include“ns3/网络模块.h”
#include "ns3/internet-module.h"
#include“ns3/点对点模块.h”
#include“ns3/applications-module.h”
为了帮助我们的高级脚本用户处理存在于
系统中,我们按照比较大的模块进行分组。 我们提供单一
包含文件,它将递归加载每个模块中使用的所有包含文件。
而不必准确查找您需要的标头,并且可能必须获得
依赖项数量正确,我们使您能够大量加载一组文件
粒度。 这不是最有效的方法,但它确实使写作
脚本更容易。
的每一个 ns-3 包含文件放置在名为的目录中 ns3 (在构建下
目录),以帮助避免包含文件名冲突。 这
ns3/核心模块.h 文件对应于您将在目录中找到的 ns-3 模块
源代码/核心 在您下载的发行版中。 如果您列出该目录,您将
找到大量的头文件。 当您进行构建时,Waf 将放置公共标头
文件中的 ns3 相应目录下 构建/调试 or 构建/优化 目录
取决于您的配置。 Waf还会自动生成一个模块include
文件来加载所有公共头文件。
当然,既然您认真地遵循本教程,您就已经完成了
a
$ ./waf -d debug --enable-examples --enable-tests 配置
为了配置项目来执行包括示例和测试的调试构建。
您还将完成
$./waf
构建项目。 现在如果你查看目录 ../../构建/调试/ns3 你会
找到上面显示的四个模块包含文件。 你可以看一下内容
这些文件并发现它们确实包含了所有公共包含文件
各自的模块。
Ns3 命名空间
中的下一行 第一个.cc script 是一个命名空间声明。
使用命名空间 ns3;
这个 ns-3 项目是在名为的 C++ 命名空间中实现的 ns3。 这组所有
ns-3全局命名空间之外的范围内的相关声明,我们希望这会有所帮助
与其他代码集成。 C++ 运用 声明介绍了 ns-3 命名空间
进入当前(全局)声明区域。 这是一种奇特的说法
这个声明,你不必输入 ns3:: 所有之前的范围解析运算符
这些因素包括原料奶的可用性以及达到必要粉末质量水平所需的工艺。 ns-3 代码以便使用它。 如果您对命名空间不熟悉,请咨询
几乎所有 C++ 教程并进行比较 ns3 命名空间和用法与实例
STD 命名空间和 运用 命名空间 标准; 您在讨论中经常会发现的陈述
of COUT 和溪流。
记录
脚本的下一行如下:
NS_LOG_COMPONENT_DEFINE ("FirstScriptExample");
我们将使用此声明作为讨论 Doxygen 文档的方便场所
系统。 如果您查看该项目网站, ns-3 项目,你会发现一个链接
导航栏中的“文档”。 如果您选择此链接,您将被带到我们的
文档页面。 有一个“最新版本”的链接,可将您带到
最新稳定版本的文档 ns-3。 如果您选择“API
文档”链接,您将被带到 ns-3 API 文档页面。
沿着左侧,您会发现该结构的图形表示
文档。 一个好的起点是 NS-3 科目 “书”中 ns-3 导航
树。 如果你扩大 科目 你会看到一个列表 ns-3 模块文档。 这
这里模块的概念直接与上面讨论的模块包含文件相关。 这
ns-3 日志子系统在中讨论 C + +中 结构体 旧 by 全部 科目 部分,所以
继续并展开该文档节点。 现在,扩展 调试 预订然后
选择 记录 页面。
您现在应该查看 Logging 模块的 Doxygen 文档。在里面
列表 #定义在页面顶部,您将看到以下条目
NS_LOG_COMPONENT_DEFINE。在开始之前,最好先查找一下
日志模块的“详细说明”,让您感受一下整体操作。你
可以向下滚动或选择协作图下的“更多...”链接来执行操作
本。
一旦您对正在发生的事情有了大致了解,就可以继续查看具体情况
NS_LOG_COMPONENT_DEFINE 文档。我不会在这里重复文档,而是为了
总结一下,这一行声明了一个名为的日志记录组件 FirstScript 示例 这允许
您可以通过引用名称来启用和禁用控制台消息日志记录。
主要 功能
您将发现的脚本的下一行是:
INT
主要(int argc,char *argv[])
{
这只是程序(脚本)主函数的声明。正如在
任何 C++ 程序,您都需要定义一个 main 函数,它将是第一个运行的函数。
这里没有什么特别的。你的 ns-3 script 只是一个 C++ 程序。
下一行将时间分辨率设置为一纳秒,这恰好是默认值
值:
时间::SetResolution (时间::NS);
分辨率是可以表示的最小时间值(以及最小的
两个时间值之间的可表示差异)。您可以准确更改分辨率
一次。实现这种灵活性的机制有点需要内存,所以一旦
分辨率已明确设置,我们释放内存,防止进一步更新。
(如果没有明确设置分辨率,它将默认为一纳秒,并且
模拟开始时内存将被释放。)
脚本的接下来两行用于启用构建的两个日志记录组件
进入 Echo 客户端和 Echo 服务器应用程序:
LogComponentEnable("UdpEchoClientApplication", LOG_LEVEL_INFO);
LogComponentEnable("UdpEchoServerApplication", LOG_LEVEL_INFO);
如果您阅读过 Logging 组件文档,您将会看到
您可以在每个组件上启用多个级别的日志记录详细程度/详细程度。
这两行代码为 echo 客户端启用 INFO 级别的调试日志记录
服务器。这将导致应用程序在发送数据包时打印出消息
并在模拟过程中收到。
现在我们将直接开始创建拓扑并运行模拟。
我们使用拓扑辅助对象来使这项工作尽可能简单。
拓扑 助手
节点容器
我们脚本中的接下来的两行代码将实际创建 ns-3 Node 对象
将代表模拟中的计算机。
NodeContainer 节点;
节点.创建(2);
让我们找到该文件的文档 节点容器 在我们继续之前先上课。其他方式
要进入给定类的文档,可以通过 精品团课 Doxygen 中的选项卡
页。如果您手边还有 Doxygen,只需向上滚动到页面顶部,然后
选择 精品团课 标签。您应该会看到出现一组新选项卡,其中一个是 增益级
列表。在该选项卡下,您将看到所有的列表 ns-3 类。向下滚动,
寻找 ns3::节点容器。当您找到班级时,请继续选择要前往的班级
该类的文档。
您可能还记得我们的关键抽象之一是 Node。这代表一台计算机
我们将添加协议栈、应用程序和外围设备等内容
牌。这 节点容器 拓扑助手提供了一种便捷的方式来创建、管理和
访问任何 Node 我们为了运行模拟而创建的对象。上面第一行
只是声明了一个我们称之为的 NodeContainer 节点。第二行调用 创建
上的方法 节点 对象并要求容器创建两个节点。如中所述
Doxygen,容器向下调用 ns-3 系统正确创建两个 Node
对象并在内部存储指向这些对象的指针。
脚本中的节点不执行任何操作。下一步将构建
拓扑是将我们的节点连接在一起形成一个网络。我们最简单的网络形式
支持是两个节点之间的单个点对点链接。我们将建造其中之一
链接在这里。
点对点助手
我们正在构建一个点对点的链接,并且,其模式将变得相当
您很熟悉,我们使用拓扑辅助对象来完成放置所需的低级工作
链接在一起。回想一下,我们的两个关键抽象是 网络设备 和
频道。在现实世界中,这些术语大致对应于外围卡和
网络电缆。通常,这两件事是紧密联系在一起的,一个人不能
例如,期望交换以太网设备和无线通道。我们的拓扑
助手遵循这种紧密的耦合,因此您将使用单个
点对点助手 配置和连接 ns-3 点对点网络设备 和
点对点信道 该脚本中的对象。
脚本中接下来的三行是:
点对点助手点对点;
pointToPoint.SetDeviceAttribute("DataRate", StringValue("5Mbps"));
pointToPoint.SetChannelAttribute("延迟", StringValue("2ms"));
第一行,
点对点助手点对点;
实例化一个 点对点助手 堆栈上的对象。从高层次的角度来看
下一行,
pointToPoint.SetDeviceAttribute("DataRate", StringValue("5Mbps"));
讲述了 点对点助手 对象使用值“5Mbps”(每秒五兆位)作为
创建时的“DataRate” 点对点网络设备 目的。
从更详细的角度来看,字符串“DataRate”对应于我们所说的
属性 的 点对点网络设备。如果你看一下 Doxygen 的课程
ns3::点对点网络设备 并找到相关文档 获取类型 ID 方法,你会
找到一个列表 Attributes 为设备定义。其中包括“DataRate”
属性。大多数用户可见 ns-3 对象具有相似的列表 Attributes。我们用这个
无需重新编译即可轻松配置模拟的机制,正如您将在
以下部分。
类似于“DataRate” 点对点网络设备 你会发现一个“延迟” 属性
与 点对点信道。最后一行,
pointToPoint.SetChannelAttribute("延迟", StringValue("2ms"));
讲述了 点对点助手 使用值“2ms”(两毫秒)作为
它随后创建的每个点对点通道的传输延迟。
网络设备容器
此时在脚本中,我们有一个 节点容器 包含两个节点。我们有一个
点对点助手 已准备好并准备制作 点对点网络设备 和电线
点对点信道 他们之间的物体。正如我们使用的 节点容器 拓扑
辅助对象来创建 Nodes 对于我们的模拟,我们会询问 点对点助手
为我们完成创建、配置和安装设备的工作。我们
将需要拥有所有已创建的 NetDevice 对象的列表,因此我们使用
NetDeviceContainer 来保存它们,就像我们使用 NodeContainer 来保存节点一样
创建的。下面两行代码,
NetDeviceContainer 设备;
设备= pointToPoint.Install(节点);
将完成设备和通道的配置。第一行声明设备
上面提到的容器和第二个容器负责繁重的工作。这 安装 的方法
这些因素包括原料奶的可用性以及达到必要粉末质量水平所需的工艺。 点对点助手 需要一个 节点容器 作为参数。在内部,一个
网络设备容器 被建造。对于中的每个节点 节点容器 (必须恰好有
两个用于点对点链接)a 点对点网络设备 创建并保存在设备中
容器。 A 点对点信道 被创建并且两个 点对点网络设备 旨在
随附的。当对象被创建时 点对点助手, Attributes 先前
helper中的set用于初始化相应的 Attributes 在创建的
对象。
执行后 点对点安装 (节点) 调用我们将有两个节点,每个节点都有一个
安装了点对点网络设备以及它们之间的单个点对点通道。
两种设备都将配置为以每秒 5 兆比特的速度传输数据
通道有两毫秒的传输延迟。
互联网堆栈助手
我们现在已经配置了节点和设备,但是我们没有安装任何协议栈
在我们的节点上。接下来的两行代码将解决这个问题。
InternetStackHelper堆栈;
stack.Install(节点);
这个 互联网堆栈助手 是一个拓扑帮助器,对于互联网堆栈来说是什么
点对点助手 是点对点网络设备。这 安装 方法需要
节点容器 作为参数。当它执行时,它会安装一个Internet Stack
(TCP、UDP、IP 等)在节点容器中的每个节点上。
ipv4地址助手
接下来,我们需要将节点上的设备与 IP 地址相关联。我们提供一个
拓扑帮助器来管理 IP 地址的分配。唯一用户可见的 API 是
设置执行实际寻址时要使用的基本 IP 地址和网络掩码
分配(这是在助手内部的较低级别完成的)。
我们的示例脚本中的接下来两行代码, 第一个.cc,
Ipv4AddressHelper地址;
地址.SetBase("10.1.1.0","255.255.255.0");
声明一个地址辅助对象并告诉它应该开始分配 IP 地址
来自网络 10.1.1.0,使用掩码 255.255.255.0 定义可分配位。经过
默认分配的地址将从 1 开始并单调增加,因此第一个
从此基址分配的地址将为 10.1.1.1,然后是 10.1.1.2,依此类推。
水平 ns-3 系统实际上会记住所有分配的IP地址,并会生成一个
如果您不小心导致相同的地址生成两次(这是一个致命错误)
顺便说一句,很难调试错误)。
下一行代码,
Ipv4InterfaceContainer 接口 = 地址.分配(设备);
执行实际的地址分配。在 ns-3 我们在 IP 之间建立关联
地址和使用的设备 ipv4接口 目的。就像我们有时需要一个列表一样
由助手创建的网络设备以供将来参考,我们有时需要一个列表
ipv4接口 对象。 该 IPv4接口容器 提供此功能。
现在我们已经构建了一个点对点网络,安装了堆栈和 IP 地址
分配的。此时我们需要的是产生流量的应用程序。
应用
ns-3 系统的另一个核心抽象是 实践应用。在这
脚本我们使用两个专业化的核心 ns-3 程 实践应用 被称为
UdpEchoServer应用程序 和 UdpEchoClient应用程序。正如我们之前的
解释中,我们使用辅助对象来帮助配置和管理底层对象。
在这里,我们使用 UdpEcho服务器助手 和 UdpEchoClientHelper 使我们的生活更轻松的对象。
UdpEcho服务器助手
我们的示例脚本中的以下代码行, 第一个.cc,用于设置UDP回显
我们之前创建的节点之一上的服务器应用程序。
UdpEchoServerHelper echoServer (9);
ApplicationContainer serverApps = echoServer.Install(nodes.Get(1));
serverApps.Start(秒(1.0));
serverApps.Stop(秒(10.0));
上面代码片段中的第一行声明了 UdpEcho服务器助手。 照常,
这不是应用程序本身,它是一个用于帮助我们创建实际应用程序的对象
应用程序。我们的惯例之一是放置 必须 Attributes 在助手中
构造函数。在这种情况下,除非提供了帮助程序,否则它无法做任何有用的事情
客户端也知道的端口号。而不是仅仅选择一个并希望
一切顺利,我们需要端口号作为构造函数的参数。这
反过来,构造函数只是简单地做了一个 设置属性 与传递的值。如果你愿意,你
可以设置“端口” 属性 稍后使用另一个值 设置属性.
与许多其他辅助对象类似, UdpEcho服务器助手 对象有一个 安装
方法。正是这个方法的执行才真正引起了底层的回显
要实例化并附加到节点的服务器应用程序。有趣的是, 安装
方法需要 节点容器 作为参数,就像其他一样 安装 我们有的方法
看到了。这实际上是传递给该方法的内容,尽管它看起来并非如此
这个案例。有一个C++ 含蓄 转变 在这里工作的结果是
节点.Get (1) (它返回一个指向节点对象的智能指针--- 点)并使用它
在未命名的构造函数中 节点容器 然后传递给 安装。 如果你是
曾经不知所措地在 C++ 代码中找到编译和运行的特定方法签名
很好,寻找这些类型的隐式转换。
我们现在看到 回声服务器安装 将要安装一个 UdpEchoServer应用程序 在
在索引号一处找到的节点 节点容器 我们曾经管理我们的节点。 安装
将返回一个容器,其中包含指向所有应用程序的指针(在本例中是一个)
自从我们通过了 节点容器 包含一个节点)由助手创建。
应用程序需要一段时间来“开始”生成流量,并且可能需要一段可选的时间来“开始”生成流量
“停止”。我们两者都提供。这些时间是使用 应用容器 方法
开始 和 Stop 停止。这些方法需要 时间 参数。在这种情况下,我们使用 明确的 C + +中
转换序列以获取 C++ double 1.0 并将其转换为 ns-3 时间 对象使用
a 秒 投掷。请注意,转换规则可能由模型作者控制,
而且 C++ 有它自己的规则,所以你不能总是假设参数会很高兴
为你转换。两条线,
serverApps.Start(秒(1.0));
serverApps.Stop(秒(10.0));
将导致回显服务器应用程序 开始 (启用自身)在一秒钟内进入
模拟并 Stop 停止 (禁用自身)在模拟十秒后。凭借
事实上,我们已经声明了一个模拟事件(应用程序停止事件)
十秒执行,模拟将持续 at 最少 十秒。
UdpEchoClientHelper
echo 客户端应用程序的设置方法与
服务器。有一个底层 UdpEchoClient应用程序 由一个
UdpEchoClientHelper.
UdpEchoClientHelper echoClient(interfaces.GetAddress(1), 9);
echoClient.SetAttribute("MaxPackets", UintegerValue(1));
echoClient.SetAttribute("间隔", TimeValue(秒(1.0)));
echoClient.SetAttribute("PacketSize", UintegerValue(1024));
ApplicationContainer clientApps = echoClient.Install(nodes.Get(0));
clientApps.Start(秒(2.0));
clientApps.Stop(秒(10.0));
然而,对于 echo 客户端,我们需要设置五个不同的 Attributes。 前两个
Attributes 是在施工期间设定的 UdpEchoClientHelper。我们传递参数
用于(在助手内部)设置“RemoteAddress”和“RemotePort”
Attributes 按照我们的惯例,做出所需的 Attributes 中的参数
辅助构造函数。
还记得我们使用过一个 IPv4接口容器 跟踪我们的 IP 地址
分配给我们的设备。第 0 个接口 接口 容器将会
对应于第0个节点的IP地址 节点 容器。首先
界面中的 接口 容器对应第一个节点的IP地址
这些因素包括原料奶的可用性以及达到必要粉末质量水平所需的工艺。 节点 容器。因此,在第一行代码(从上面开始)中,我们正在创建
helper 并告诉它将客户端的远程地址设置为 IP 地址
分配给服务器所在的节点。我们还告诉它安排发送
发往端口 9 的数据包。
“最大数据包数” 属性 告诉客户端我们允许的最大数据包数量
在模拟期间发送。 “间隔” 属性 告诉客户要等待多长时间
数据包之间以及“PacketSize” 属性 告诉客户端它的数据包有多大
有效负载应该是。通过这种特殊的组合 Attributes,我们正在告诉
客户端发送一个1024字节的数据包。
就像回显服务器的情况一样,我们告诉回显客户端 开始 和 Stop 停止,但
这里我们在服务器启用后一秒启动客户端(在启动后两秒)
模拟)。
模拟器
此时我们需要做的是实际运行模拟。这是使用完成的
全局函数 模拟器::运行.
模拟器::运行();
当我们之前调用这些方法时,
serverApps.Start(秒(1.0));
serverApps.Stop(秒(10.0));
...
clientApps.Start(秒(2.0));
clientApps.Stop(秒(10.0));
我们实际上在模拟器中安排了 1.0 秒、2.0 秒和两个事件的事件
10.0 秒。什么时候 模拟器::运行 被调用时,系统将开始查找
计划事件的列表并执行它们。首先它将在 1.0 秒运行事件,
这将启用回显服务器应用程序(此事件可能会反过来安排许多
其他事件)。然后它将运行计划在 t=2.0 秒开始的事件
echo 客户端应用程序。同样,此活动可能会安排更多活动。开始
echo 客户端应用程序中的事件实现将开始数据传输阶段
通过向服务器发送数据包进行模拟。
将数据包发送到服务器的行为将触发一系列事件
在幕后自动安排,并将执行以下机制
根据我们在脚本中设置的各种计时参数进行数据包回显。
最终,由于我们只发送一个数据包(回想一下 最大数据包数 属性 被设置为
一),由单个客户端回显请求触发的事件链将逐渐减少,并且
模拟将闲置。一旦发生这种情况,剩下的事件将是 Stop 停止
服务器和客户端的事件。当这些事件执行时,没有
进一步处理和处理事件 模拟器::运行 返回。然后模拟就完成了。
剩下的就是清理。这是通过调用全局函数来完成的
模拟器::摧毁。作为助手功能(或低级 ns-3 代码)执行后,他们
安排它以便在模拟器中插入钩子以销毁所有对象
被创建的。您不必自己跟踪这些对象中的任何一个——
你所要做的就是打电话 模拟器::摧毁 并退出。这 ns-3 系统照顾了
对你来说最困难的部分。我们第一行的其余行 ns-3 脚本, 第一个.cc, 只做
说:
模拟器::销毁();
0返回;
}
在规划婴儿食品行业的工艺要求时,安全性和可靠性是工艺设计中最重要的方面。 这些因素包括原料奶的可用性以及达到必要粉末质量水平所需的工艺。 模拟器 将 停止?
ns-3 是一个离散事件 (DE) 模拟器。在这样的模拟器中,每个事件都是相关联的
及其执行时间,并且通过在时间中执行事件来进行模拟
模拟时间的顺序。事件可能会导致安排未来的事件(例如,
计时器可以重新安排自己在下一个时间间隔到期)。
初始事件通常由每个对象触发,例如IPv6将调度Router
广告、邻居请求等,应用程序安排第一个数据包
发送事件等
当处理一个事件时,它可能会生成零个、一个或多个事件。作为模拟
执行时,事件被消耗,但可能(或可能不会)生成更多事件。这
当事件队列中没有更多事件时,或者当
发现特殊的停止事件。 Stop 事件是通过以下方式创建的 模拟器::停止
(停止时间); 功能。
有一个典型的案例, 模拟器::停止 是绝对有必要阻止
模拟:当有一个自我维持的事件发生时。自我维持(或重复发生)的事件
是总是会自行重新安排的事件。因此,他们总是保留该事件
队列非空。
有许多协议和模块包含重复事件,例如:
· FlowMonitor - 定期检查丢失的数据包
· RIPng - 定期广播路由表更新
· 等等。
在这些情况下, 模拟器::停止 有必要优雅地停止模拟。在
另外,当 ns-3 处于仿真模式时, 实时模拟器 用于保持
模拟时钟与机器时钟对齐,以及 模拟器::停止 有必要停止
的过程。
教程中的许多模拟程序没有显式调用 模拟器::停止,
因为事件队列会自动耗尽事件。然而,这些计划将
也接受致电 模拟器::停止。例如,以下附加语句
第一个示例程序将在 11 秒处安排显式停止:
+ 模拟器::停止(秒(11.0));
模拟器::运行();
模拟器::销毁();
0返回;
}
上面的内容实际上不会改变这个程序的行为,因为这个特定的
10秒后模拟自然结束。但如果你要改变停止时间
将上面的语句从 11 秒改为 1 秒,你会注意到模拟
在任何输出打印到屏幕之前停止(因为输出发生在时间 2 左右)
模拟时间的秒数)。
打电话很重要 模拟器::停止 before 调用 模拟器::运行; 否则,
模拟器::运行 可能永远不会将控制权返回到主程序来执行停止!
构建 您的 脚本
我们使构建简单脚本变得轻而易举。你所要做的就是放下你的
将脚本复制到暂存目录中,如果您运行 Waf,它将自动构建。
我们来试试吧。复制 示例/教程/first.cc 到 划伤 更改后的目录
返回到顶级目录。
$ 光盘 ../ ..
$ cp 示例/教程/first.cc scrap/myfirst.cc
现在使用 waf 构建您的第一个示例脚本:
$./waf
您应该会看到消息报告您的 我的第一次 示例已成功构建。
Waf:进入目录“/home/craigdo/repos/ns-3-allinone/ns-3-dev/build”
[614/708] cxx:scratch/myfirst.cc -> 构建/调试/scratch/myfirst_3.o
[706/708] cxx_link:构建/调试/scratch/myfirst_3.o -> 构建/调试/scratch/myfirst
Waf:离开目录“/home/craigdo/repos/ns-3-allinone/ns-3-dev/build”
“构建”成功完成(2.357s)
您现在可以运行该示例(请注意,如果您在临时目录中构建程序
您必须从暂存目录中运行它):
$ ./waf --运行 scrap/myfirst
您应该看到一些输出:
Waf:进入目录“/home/craigdo/repos/ns-3-allinone/ns-3-dev/build”
Waf:离开目录“/home/craigdo/repos/ns-3-allinone/ns-3-dev/build”
“构建”成功完成(0.418s)
发送 1024 字节到 10.1.1.2
从 1024 接收到 10.1.1.1 字节
从 1024 接收到 10.1.1.2 字节
在这里您可以看到构建系统检查以确保文件已构建并且
然后运行它。您会看到 echo 客户端上的日志记录组件表明它已发送
一个 1024 字节数据包发送至 10.1.1.2 上的 Echo Server。您还可以看到日志记录组件
在 echo 服务器上说它已收到来自 1024 的 10.1.1.1 字节。回显服务器
默默地回显数据包,您会看到回显客户端日志表明它已收到数据包
从服务器返回。
NS-3 来源 代码
现在您已经使用了一些 ns-3 您可能想看看一些帮助者
实现该功能的源代码。最新的代码可以浏览
我们的网络服务器位于以下链接: http://code.nsnam.org/ns-3-dev。在那里,你会看到
Mercurial 摘要页面 ns-3 发展树。
在页面顶部,您会看到许多链接,
总结|短日志 |变更日志 |图表|标签 |文件
继续并选择 档 关联。这是我们大多数人的顶层
资料库 将要看:
drwxr-xr-x [上]
drwxr-xr-x 绑定 python 文件
drwxr-xr-x 文档文件
drwxr-xr-x 示例文件
drwxr-xr-x ns3 文件
drwxr-xr-x 临时文件
drwxr-xr-x src 文件
drwxr-xr-x utils 文件
-rw-r--r-- 2009-07-01 12:47 +0200 560 .hgignore 文件 |修订|注释
-rw-r--r-- 2009-07-01 12:47 +0200 1886 .hgtags 文件 |修订|注释
-rw-r--r-- 2009-07-01 12:47 +0200 1276 作者 文件 |修订|注释
-rw-r--r-- 2009-07-01 12:47 +0200 30961 CHANGES.html 文件 |修订|注释
-rw-r--r-- 2009-07-01 12:47 +0200 17987 许可证文件 |修订|注释
-rw-r--r-- 2009-07-01 12:47 +0200 3742 自述文件 |修订|注释
-rw-r--r-- 2009-07-01 12:47 +0200 16171 RELEASE_NOTES 文件 |修订|注释
-rw-r--r-- 2009-07-01 12:47 +0200 6 版本文件 |修订|注释
-rwxr-xr-x 2009-07-01 12:47 +0200 88110 waf 文件 |修订|注释
-rwxr-xr-x 2009-07-01 12:47 +0200 28 waf.bat 文件 |修订|注释
-rw-r--r-- 2009-07-01 12:47 +0200 35395 wscript 文件 |修订|注释
-rw-r--r-- 2009-07-01 12:47 +0200 7673 wutils.py 文件 |修订|注释
我们的示例脚本位于 例子 目录。如果您点击 例子 你会看到
子目录列表。其中一个文件位于 教程 子目录是 第一个.cc。 如果您
点击 第一个.cc 您将找到您刚刚浏览过的代码。
源码主要在 SRC 目录。您可以通过以下方式查看源代码
单击目录名称或单击 档 链接到右侧的
目录名称。如果您点击 SRC 目录,您将被带到以下列表
这些因素包括原料奶的可用性以及达到必要粉末质量水平所需的工艺。 SRC 子目录。如果您随后单击 核心 子目录,你会发现一个列表
文件。您将找到的第一个文件(截至撰写本文时)是 中止.h。 如果您点击
中止.h 链接,您将被发送到源文件 中止.h 其中包含有用的宏
用于在检测到异常情况时退出脚本。
我们在本章中使用的帮助程序的源代码可以在
源代码/应用程序/助手 目录。请随意在目录树中查找以获取
对那里的事物和风格的感觉 ns-3 程式。
调整
运用 这些因素包括原料奶的可用性以及达到必要粉末质量水平所需的工艺。 记录 模块
我们已经简要地了解了 ns-3 记录模块,同时浏览
第一个.cc 脚本。我们现在将仔细观察,看看有哪些用例
日志子系统的设计涵盖了。
记录 概述
许多大型系统支持某种消息记录工具,并且 ns-3 不是
例外。在某些情况下,只有错误消息会记录到“操作员控制台”(其中
通常是 斯特德 在基于 Unix 的系统中)。在其他系统中,警告消息可能是
输出以及更详细的信息消息。在某些情况下,伐木设施
用于输出调试消息,这可以快速使输出变得模糊。
ns-3 认为所有这些详细程度都是有用的,我们提供了一个
可选择的多级消息记录方法。可以完全禁用日志记录,
逐个组件启用或全局启用;并且它提供了可选择的
详细程度。这 ns-3 log模块提供了简单明了、相对容易使用的
从模拟中获取有用信息的方法。
您应该明白,我们确实提供了一种通用机制 --- 跟踪 ---
从模型中获取数据,这应该是模拟输出的首选(请参阅
有关我们的跟踪系统的更多详细信息,请参阅教程部分“使用跟踪系统”)。
对于调试信息、警告、错误消息或任何其他信息,应优先使用日志记录
当您想要轻松地从脚本或模型中获取快速消息时。
当前有七个级别的日志消息,其详细程度在
系统。
· 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 —— 无条件记录关联消息(无关联日志级别)。
每个级别可以单独请求,也可以累积请求;并可以使用设置日志记录
shell环境变量(NS_LOG)或通过记录系统函数调用。正如所见
在本教程的前面部分,日志记录系统有 Doxygen 文档,现在将是
如果您还没有仔细阅读日志记录模块文档,那么是时候仔细阅读了。
现在您已经详细阅读了文档,让我们使用其中的一些知识
从中获取一些有趣的信息 从头开始/myfirst.cc 你有的示例脚本
已经建成了。
启用 记录
让我们使用 NS_LOG 环境变量来打开更多日志记录,但首先,只是为了
了解我们的方位,继续运行最后一个脚本,就像您之前所做的那样,
$ ./waf --运行 scrap/myfirst
您应该看到现在熟悉的第一个输出 ns-3 示例程序
$ Waf:进入目录`/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
Waf:离开目录“/home/craigdo/repos/ns-3-allinone/ns-3-dev/build”
“构建”成功完成(0.413s)
发送 1024 字节到 10.1.1.2
从 1024 接收到 10.1.1.1 字节
从 1024 接收到 10.1.1.2 字节
事实证明,您在上面看到的“已发送”和“已接收”消息实际上正在记录
消息来自 UdpEchoClient应用程序 和 UdpEchoServer应用程序。我们可以询问
客户端应用程序,例如,通过设置其日志记录级别来打印更多信息
通过 NS_LOG 环境变量。
我将从这里开始假设您使用的是类似 sh 的 shell,它使用
“VARIABLE=值”语法。如果您使用类似 csh 的 shell,那么您必须
将我的示例转换为这些 shell 所需的“setenv VARIABLE value”语法。
现在,UDP 回显客户端应用程序正在响应以下代码行:
从头开始/myfirst.cc,
LogComponentEnable("UdpEchoClientApplication", LOG_LEVEL_INFO);
这行代码启用了 LOG_LEVEL_INFO 日志记录级别。当我们通过一个日志记录
level 标志,我们实际上正在启用给定级别和所有较低级别。在这种情况下,
我们已经启用了 NS_LOG_INFO, NS_LOG_DEBUG, NS_LOG_WARN 和 NS_LOG_ERROR。我们可以增加
日志记录级别并获取更多信息,而无需更改脚本并重新编译
像这样设置 NS_LOG 环境变量:
$ 导出 NS_LOG=UdpEchoClientApplication=level_all
这设置了 shell 环境变量 NS_LOG 到字符串,
UdpEchoClientApplication=level_all
赋值的左侧是我们要设置的日志组件的名称,
右侧是我们要使用的标志。在这种情况下,我们要打开
应用程序的所有调试级别。如果您运行设置了 NS_LOG 的脚本
这样, ns-3 日志系统将记录更改,您应该看到以下内容
输出:
Waf:进入目录“/home/craigdo/repos/ns-3-allinone/ns-3-dev/build”
Waf:离开目录“/home/craigdo/repos/ns-3-allinone/ns-3-dev/build”
“构建”成功完成(0.404s)
UdpEchoClientApplication:UdpEchoClient()
UdpEchoClient应用程序:设置数据大小(1024)
UdpEchoClientApplication:StartApplication()
UdpEchoClientApplication:ScheduleTransmit()
UdpEchoClientApplication:发送()
发送 1024 字节到 10.1.1.2
从 1024 接收到 10.1.1.1 字节
UdpEchoClientApplication:HandleRead(0x6241e0, 0x624a20)
从 1024 接收到 10.1.1.2 字节
UdpEchoClientApplication:StopApplication()
UdpEchoClientApplication:DoDispose()
UdpEchoClientApplication:~UdpEchoClient()
应用程序提供的附加调试信息来自 NS_LOG_FUNCTION
等级。这显示每次在脚本期间调用应用程序中的函数时
执行。一般来说,在成员函数中(至少)使用 NS_LOG_FUNCTION(this) 是
首选。仅在静态函数中使用 NS_LOG_FUNCTION_NOARGS()。但请注意,
中没有要求 ns-3 模型必须支持任何特定的系统
日志记录功能。关于记录多少信息的决定由
个人模型开发人员。对于 echo 应用程序,大量日志
输出可用。
您现在可以查看对应用程序进行的函数调用的日志。如果你
仔细观察你会发现字符串之间有一个冒号 UdpEchoClient应用程序
以及您可能期望使用 C++ 作用域运算符的方法名称 (::)。 这是
故意的。
该名称实际上不是类名,而是日志记录组件名称。当有一个
源文件和类之间的一一对应关系,这通常是
类名,但你应该明白它实际上不是一个类名,并且有一个
那里有一个冒号而不是双冒号,以一种相对微妙的方式提醒您
从概念上讲,日志记录组件名称与类名称分开。
事实证明,在某些情况下,很难确定实际使用哪种方法
生成一条日志消息。如果您查看上面的文本,您可能想知道字符串在哪里
"收到 1024 字节 , 10.1.1.2“ 来自。您可以通过 OR'ing 来解决这个问题
前缀函数 水平进入 NS_LOG 环境变量。尝试执行以下操作,
$ 导出 'NS_LOG=UdpEchoClientApplication=level_all|prefix_func'
请注意,引号是必需的,因为我们使用竖线来表示 OR
操作也是Unix管道连接器。
现在,如果您运行该脚本,您将看到日志记录系统确保每个
来自给定日志组件的消息以组件名称为前缀。
Waf:进入目录“/home/craigdo/repos/ns-3-allinone/ns-3-dev/build”
Waf:离开目录“/home/craigdo/repos/ns-3-allinone/ns-3-dev/build”
“构建”成功完成(0.417s)
UdpEchoClientApplication:UdpEchoClient()
UdpEchoClient应用程序:设置数据大小(1024)
UdpEchoClientApplication:StartApplication()
UdpEchoClientApplication:ScheduleTransmit()
UdpEchoClientApplication:发送()
UdpEchoClientApplication:Send():发送 1024 字节到 10.1.1.2
从 1024 接收到 10.1.1.1 字节
UdpEchoClientApplication:HandleRead(0x6241e0, 0x624a20)
UdpEchoClientApplication:HandleRead(): 从 1024 接收到 10.1.1.2 字节
UdpEchoClientApplication:StopApplication()
UdpEchoClientApplication:DoDispose()
UdpEchoClientApplication:~UdpEchoClient()
您现在可以看到来自 UDP 回显客户端应用程序的所有消息
被认定为如此。消息“从 1024 接收到 10.1.1.2 字节”现在清晰可见
被识别为来自 echo 客户端应用程序。剩余的消息必须是
来自 UDP 回显服务器应用程序。我们可以通过输入以下内容来启用该组件
NS_LOG 环境变量中的冒号分隔的组件列表。
$导出'NS_LOG = UdpEchoClientApplication = level_all | prefix_func:
UdpEchoServerApplication=level_all|prefix_func'
警告:您需要删除之后的换行符 : 在上面的示例文本中
仅用于文档格式化目的。
现在,如果您运行该脚本,您将看到来自 echo 客户端的所有日志消息
和服务器应用程序。您可能会发现这对于调试问题非常有用。
Waf:进入目录“/home/craigdo/repos/ns-3-allinone/ns-3-dev/build”
Waf:离开目录“/home/craigdo/repos/ns-3-allinone/ns-3-dev/build”
“构建”成功完成(0.406s)
UdpEchoServerApplication:UdpEchoServer()
UdpEchoClientApplication:UdpEchoClient()
UdpEchoClient应用程序:设置数据大小(1024)
UdpEchoServerApplication:StartApplication()
UdpEchoClientApplication:StartApplication()
UdpEchoClientApplication:ScheduleTransmit()
UdpEchoClientApplication:发送()
UdpEchoClientApplication:Send():发送 1024 字节到 10.1.1.2
UdpEchoServerApplication:HandleRead(): 从 1024 接收到 10.1.1.1 字节
UdpEchoServerApplication:HandleRead():回显数据包
UdpEchoClientApplication:HandleRead(0x624920,0x625160)
UdpEchoClientApplication:HandleRead(): 从 1024 接收到 10.1.1.2 字节
UdpEchoServerApplication:StopApplication()
UdpEchoClientApplication:StopApplication()
UdpEchoClientApplication:DoDispose()
UdpEchoServerApplication:DoDispose()
UdpEchoClientApplication:~UdpEchoClient()
UdpEchoServerApplication:~UdpEchoServer()
有时能够查看日志消息的模拟时间也很有用
被生成。您可以通过对 prefix_time 位进行“或”运算来完成此操作。
$ 导出 'NS_LOG=UdpEchoClientApplication=level_all|prefix_func|prefix_time:
UdpEchoServerApplication=level_all|prefix_func|prefix_time'
同样,您必须删除上面的换行符。如果您现在运行脚本,您应该
请参阅以下输出:
Waf:进入目录“/home/craigdo/repos/ns-3-allinone/ns-3-dev/build”
Waf:离开目录“/home/craigdo/repos/ns-3-allinone/ns-3-dev/build”
“构建”成功完成(0.418s)
0s UdpEchoServerApplication:UdpEchoServer()
0s UdpEchoClientApplication:UdpEchoClient()
0s UdpEchoClient应用程序:设置数据大小(1024)
1s UdpEchoServerApplication:StartApplication()
2s UdpEchoClientApplication:StartApplication()
2s UdpEchoClientApplication:ScheduleTransmit()
2s UdpEchoClientApplication:Send()
2s UdpEchoClientApplication:Send(): 发送1024字节到10.1.1.2
2.00369s UdpEchoServerApplication:HandleRead(): 从 1024 接收到 10.1.1.1 字节
2.00369s UdpEchoServerApplication:HandleRead(): 回显数据包
2.00737s UdpEchoClientApplication:HandleRead(0x624290, 0x624ad0)
2.00737s UdpEchoClientApplication:HandleRead(): 从 1024 接收到 10.1.1.2 字节
10s UdpEchoServerApplication:StopApplication()
10s UdpEchoClientApplication:StopApplication()
UdpEchoClientApplication:DoDispose()
UdpEchoServerApplication:DoDispose()
UdpEchoClientApplication:~UdpEchoClient()
UdpEchoServerApplication:~UdpEchoServer()
您可以看到 UdpEchoServer 的构造函数在模拟时间被调用
0秒。这实际上是在模拟开始之前发生的,但时间是
显示为零秒。 UdpEchoClient 构造函数消息也是如此。
回想一下 从头开始/第一个.cc 脚本在一秒钟内启动了 echo 服务器应用程序
进入模拟。您现在可以看到 启动应用程序 服务器的方法是,
事实上,就在一秒钟内调用了。您还可以看到 echo 客户端应用程序是
按照我们在脚本中的要求,从两秒的模拟时间开始。
您现在可以从以下位置跟踪模拟的进度 调度传输 呼叫
调用的客户端 发送 以及 句柄读取 echo 服务器应用程序中的回调。笔记
通过点对点链路发送数据包所用的时间为 3.69
毫秒。您会看到回显服务器记录一条消息,告诉您它已回显
数据包,然后,在另一个通道延迟之后,您会看到回显客户端收到
回显数据包在其 句柄读取 方法。
在这个模拟中,有很多你不知道的幕后发生的事情
也看到了。通过打开所有的功能,您可以非常轻松地跟踪整个过程
记录系统中的组件。尝试设置 NS_LOG 变量如下,
$导出'NS_LOG=*=level_all|prefix_func|prefix_time'
上面的星号是日志记录组件通配符。这将打开所有
记录模拟中使用的所有组件。我不会重现输出
这里(截至撰写本文时,它为单个数据包回显生成 1265 行输出)但是
您可以将此信息重定向到一个文件中,并使用您最喜欢的内容查看它
编辑如果你愿意的话
$ ./waf --run scrap/myfirst > log.out 2>&1
当我遇到一个问题时,我个人会使用这种极其冗长的日志记录版本
问题,我不知道哪里出了问题。我可以跟踪进展
无需设置断点并在调试器中单步执行代码即可轻松编写代码。
我可以在我最喜欢的编辑器中编辑输出并搜索我期望的东西,
并看到我意想不到的事情发生。当我对什么有一个大概的了解时
出错时,我会转换为调试器以对问题进行细粒度检查。
当您的脚本完全执行某些操作时,这种输出特别有用
意外。如果您使用调试器进行单步调试,您可能会错过意外的偏移
完全地。记录游览使其快速可见。
添加 记录 至 您的 代码
您可以通过调用日志组件来向模拟添加新的日志记录
几个宏。让我们在 我的第一个.cc 我们的脚本 划伤 目录。
回想一下,我们在该脚本中定义了一个日志组件:
NS_LOG_COMPONENT_DEFINE ("FirstScriptExample");
您现在知道可以通过设置启用此组件的所有日志记录
NS_LOG 环境变量到各个级别。让我们继续添加一些日志记录
剧本。用于添加信息级日志消息的宏是 NS_LOG_INFO。 走
前面并添加一个(就在我们开始创建节点之前),告诉您该脚本
是“创建拓扑”。这是按照此代码片段完成的,
可选 从头开始/myfirst.cc 在您最喜欢的编辑器中添加以下行,
NS_LOG_INFO ("创建拓扑");
就在线条之前,
NodeContainer 节点;
节点.创建(2);
现在使用 waf 构建脚本并清除 NS_LOG 变量来关闭 torrent
我们之前启用的日志记录:
$./waf
$ 导出 NS_LOG=
现在,如果您运行该脚本,
$ ./waf --运行 scrap/myfirst
你会 而不去 查看您的新消息,因为其关联的日志记录组件
(FirstScript 示例)尚未启用。为了查看您的消息,您必须
启用 FirstScript 示例 级别大于或等于的日志记录组件
NS_LOG_INFO。如果您只想查看此特定级别的日志记录,可以启用它
通过,
$ 导出 NS_LOG=FirstScriptExample=信息
如果您现在运行该脚本,您将看到新的“Creating Topology”日志消息,
Waf:进入目录“/home/craigdo/repos/ns-3-allinone/ns-3-dev/build”
Waf:离开目录“/home/craigdo/repos/ns-3-allinone/ns-3-dev/build”
“构建”成功完成(0.404s)
创建拓扑
发送 1024 字节到 10.1.1.2
从 1024 接收到 10.1.1.1 字节
从 1024 接收到 10.1.1.2 字节
运用 命令 LINE 参数
覆写 默认 Attributes
另一种可以改变方式的方法 ns-3 脚本无需编辑即可运行,构建是通过
命令 线 参数。 我们提供了一种解析命令行参数的机制
根据这些参数自动设置局部和全局变量。
使用命令行参数系统的第一步是声明命令行
解析器。这非常简单(在您的主程序中),如以下代码所示,
INT
主要(int argc,char *argv[])
{
...
命令行 cmd;
cmd.Parse(argc, argv);
...
}
这个简单的两行代码片段本身实际上非常有用。它打开了通往
ns-3 全局变量和 属性 系统。继续将两行代码添加到
这些因素包括原料奶的可用性以及达到必要粉末质量水平所需的工艺。 从头开始/myfirst.cc 开头的脚本 主。继续构建脚本并运行
它,但请通过以下方式向脚本寻求帮助,
$ ./waf --run "scratch/myfirst --PrintHelp"
这将要求 Waf 运行 从头开始/我的第一个 编写脚本并传递命令行参数
--打印帮助 到脚本。需要引号来区分哪个程序获取哪个
争论。命令行解析器现在将看到 --打印帮助 争论并回应,
Waf:进入目录“/home/craigdo/repos/ns-3-allinone/ns-3-dev/build”
Waf:离开目录“/home/craigdo/repos/ns-3-allinone/ns-3-dev/build”
“构建”成功完成(0.413s)
TcpL4协议:TcpStateMachine()
命令行:HandleArgument(): 句柄参数名称=PrintHelp 值=
--PrintHelp:打印此帮助消息。
--PrintGroups:打印组列表。
--PrintTypeIds:打印所有TypeId。
--PrintGroup=[group]: 打印group的所有TypeId。
--PrintAttributes=[typeid]:打印typeid的所有属性。
--PrintGlobals:打印全局列表。
让我们专注于 --打印属性 选项。我们已经暗示过 ns-3 属性
系统在行走时 第一个.cc 脚本。我们查看了以下几行
码,
点对点助手点对点;
pointToPoint.SetDeviceAttribute("DataRate", StringValue("5Mbps"));
pointToPoint.SetChannelAttribute("延迟", StringValue("2ms"));
并提到 数据速率 实际上是一个 属性 的 点对点网络设备。让我们
使用命令行参数解析器来查看 Attributes 的
点对点网络设备。帮助列表说我们应该提供一个 类型标识。 这
对应于该类的类名 Attributes 属于。在这种情况下
这将是 ns3::点对点网络设备。让我们继续输入,
$ ./waf --run "scratch/myfirst --PrintAttributes=ns3::PointToPointNetDevice"
系统会打印出所有的 Attributes 这种网络设备。之间
Attributes 你会看到列出的是,
--ns3::PointToPointNetDevice::DataRate=[32768bps]:
点对点链路的默认数据速率
这是默认值,当 点对点网络设备 在中创建
系统。我们用以下命令覆盖了这个默认值 属性 设置在 点对点助手
多于。让我们使用点对点设备和通道的默认值
删除 设置设备属性 呼叫和 设置通道属性 呼叫来自 我的第一个.cc
我们在临时目录中。
您的脚本现在应该只声明 点对点助手 并且不做任何事情 集 操作
如下例所示,
...
NodeContainer 节点;
节点.创建(2);
点对点助手点对点;
NetDeviceContainer 设备;
设备= pointToPoint.Install(节点);
...
继续使用 Waf 构建新脚本(./waff)然后让我们返回并启用一些
从 UDP 回显服务器应用程序进行日志记录并打开时间前缀。
$ 导出 'NS_LOG=UdpEchoServerApplication=level_all|prefix_time'
如果运行该脚本,您现在应该看到以下输出:
Waf:进入目录“/home/craigdo/repos/ns-3-allinone/ns-3-dev/build”
Waf:离开目录“/home/craigdo/repos/ns-3-allinone/ns-3-dev/build”
“构建”成功完成(0.405s)
0s UdpEchoServerApplication:UdpEchoServer()
1s UdpEchoServerApplication:StartApplication()
发送 1024 字节到 10.1.1.2
2.25732s 从 1024 接收到 10.1.1.1 字节
2.25732s 回显数据包
从 1024 接收到 10.1.1.2 字节
10s UdpEchoServerApplication:StopApplication()
UdpEchoServerApplication:DoDispose()
UdpEchoServerApplication:~UdpEchoServer()
回想一下,上次我们查看数据包的模拟时间
echo 服务器收到的时间为 2.00369 秒。
2.00369s UdpEchoServerApplication:HandleRead(): 从 1024 接收到 10.1.1.1 字节
现在它在 2.25732 秒接收数据包。这是因为我们刚刚删除了
的数据速率 点对点网络设备 降至默认值 32768 位/秒
每秒五兆位。
如果我们要提供一个新的 数据速率 使用命令行,我们可以加快模拟速度
再次起来。我们根据帮助中隐含的公式,按照以下方式执行此操作
项目:
$ ./waf --run "scratch/myfirst --ns3::PointToPointNetDevice::DataRate=5Mbps"
这将设置默认值 数据速率 属性 回到每 5 兆位
第二。您对结果感到惊讶吗?事实证明,为了得到原件
脚本返回的行为,我们必须设置通道的光速延迟
以及。我们可以要求命令行系统打印出 Attributes 频道的
就像我们对网络设备所做的那样:
$ ./waf --run "scratch/myfirst --PrintAttributes=ns3::PointToPointChannel"
我们发现 延迟 属性 通道的设置方式如下:
--ns3::PointToPointChannel::延迟=[0ns]:
通过通道的传输延迟
然后我们可以通过命令行系统设置这两个默认值,
$ ./waf --run "scratch/myfirst
--ns3::PointToPointNetDevice::DataRate=5Mbps
--ns3::PointToPointChannel::延迟=2ms"
在这种情况下,我们恢复了明确设置时的计时 数据速率 和 延迟
在脚本中:
Waf:进入目录“/home/craigdo/repos/ns-3-allinone/ns-3-dev/build”
Waf:离开目录“/home/craigdo/repos/ns-3-allinone/ns-3-dev/build”
“构建”成功完成(0.417s)
0s UdpEchoServerApplication:UdpEchoServer()
1s UdpEchoServerApplication:StartApplication()
发送 1024 字节到 10.1.1.2
2.00369s 从 1024 接收到 10.1.1.1 字节
2.00369s 回显数据包
从 1024 接收到 10.1.1.2 字节
10s UdpEchoServerApplication:StopApplication()
UdpEchoServerApplication:DoDispose()
UdpEchoServerApplication:~UdpEchoServer()
请注意,服务器在 2.00369 秒再次收到该数据包。我们可以
实际上设置任何 Attributes 在脚本中以这种方式使用。特别是我们可以
设置 UdpEcho客户端 属性 最大数据包数 到某个值之外的其他值。
你会怎么做呢?试一试。记住你必须注释掉这个地方
我们覆盖默认值 属性 并明确设置 最大数据包数 在脚本中。然后你
必须重建脚本。您还必须找到实际设置的语法
使用命令行帮助工具的新默认属性值。一旦你有了这个
发现你应该能够控制命令回显的数据包数量
线。由于我们是好人,我们会告诉您,您的命令行最终应该看起来像
就像是,
$ ./waf --run "scratch/myfirst
--ns3::PointToPointNetDevice::DataRate=5Mbps
--ns3::PointToPointChannel::延迟=2ms
--ns3::UdpEchoClient::MaxPackets=2"
挂钩 您的 购买 理念
您还可以将自己的挂钩添加到命令行系统。这很简单地通过以下方式完成
使用 添加值 命令行解析器的方法。
让我们使用这个工具来指定要以完全不同的方式回显的数据包数量
方式。让我们添加一个名为的局部变量 数据包 以及 主 功能。我们将初始化
将其设置为 1 以匹配我们之前的默认行为。允许命令行解析器
更改此值,我们需要将该值挂接到解析器中。我们通过添加一个调用来做到这一点
至 添加值。继续并更改 从头开始/myfirst.cc 脚本以开头
以下代码,
INT
主要(int argc,char *argv[])
{
uint32_t nPackets = 1;
命令行 cmd;
cmd.AddValue("nPackets", "要回显的数据包数量", nPackets);
cmd.Parse(argc, argv);
...
向下滚动到脚本中我们设置的位置 最大数据包数 属性 并改变它
这样它就被设置为变量 数据包 而不是常数 1 如下图所示。
echoClient.SetAttribute("MaxPackets", UintegerValue(nPackets));
现在,如果您运行脚本并提供 --打印帮助 争论,你应该看到你的新
用户 争论 帮助显示中列出。
尝试,
$ ./waf --run "scratch/myfirst --PrintHelp"
Waf:进入目录“/home/craigdo/repos/ns-3-allinone/ns-3-dev/build”
Waf:离开目录“/home/craigdo/repos/ns-3-allinone/ns-3-dev/build”
“构建”成功完成(0.403s)
--PrintHelp:打印此帮助消息。
--PrintGroups:打印组列表。
--PrintTypeIds:打印所有TypeId。
--PrintGroup=[group]: 打印group的所有TypeId。
--PrintAttributes=[typeid]:打印typeid的所有属性。
--PrintGlobals:打印全局列表。
用户争论:
--nPackets:要回显的数据包数量
如果您想指定要回显的数据包数量,您现在可以通过设置
--nPackets 命令行中的参数,
$ ./waf --run "scratch/myfirst --nPackets=2"
你现在应该看到
Waf:进入目录“/home/craigdo/repos/ns-3-allinone/ns-3-dev/build”
Waf:离开目录“/home/craigdo/repos/ns-3-allinone/ns-3-dev/build”
“构建”成功完成(0.404s)
0s UdpEchoServerApplication:UdpEchoServer()
1s UdpEchoServerApplication:StartApplication()
发送 1024 字节到 10.1.1.2
2.25732s 从 1024 接收到 10.1.1.1 字节
2.25732s 回显数据包
从 1024 接收到 10.1.1.2 字节
发送 1024 字节到 10.1.1.2
3.25732s 从 1024 接收到 10.1.1.1 字节
3.25732s 回显数据包
从 1024 接收到 10.1.1.2 字节
10s UdpEchoServerApplication:StopApplication()
UdpEchoServerApplication:DoDispose()
UdpEchoServerApplication:~UdpEchoServer()
您现在已经回显了两个数据包。很容易,不是吗?
您可以看到,如果您是 ns-3 用户,您可以使用命令行参数系统来
控制全球价值观并 Attributes。如果您是模型作者,您可以添加新的
Attributes 您的 对象 并且它们将自动可供您设置
用户通过命令行系统。如果您是脚本作者,您可以添加新的
将变量添加到您的脚本中,并将它们轻松地挂接到命令行系统中。
运用 这些因素包括原料奶的可用性以及达到必要粉末质量水平所需的工艺。 追踪 系统
模拟的重点是生成输出以供进一步研究,并且 ns-3
追踪系统是实现这一点的主要机制。自从 ns-3 是一个C++程序,标准
可以使用从 C++ 程序生成输出的工具:
#包括
...
int主()
{
...
std::cout << "x 的值为" << x << std::endl;
...
}
您甚至可以使用日志记录模块为您的解决方案添加一些结构。那里
这种方法产生了许多众所周知的问题,因此我们提供了一个
通用事件跟踪子系统来解决我们认为重要的问题。
的基本目标 ns-3 追踪系统有:
· 对于基本任务,跟踪系统应允许用户生成标准跟踪
对于流行的跟踪源,并自定义哪些对象生成跟踪;
· 中级用户必须能够扩展跟踪系统以修改输出格式
生成,或插入新的跟踪源,而不修改核心
模拟器;
· 高级用户可以修改模拟器核心以添加新的跟踪源和接收器。
这个 ns-3 溯源系统建立在独立溯源的概念之上,
跟踪接收器,以及将源连接到接收器的统一机制。追踪来源是
可以发出模拟中发生的事件信号并提供访问权限的实体
有趣的基础数据。例如,跟踪源可以指示数据包何时
由网络设备接收并提供对数据包内容的访问以进行感兴趣的跟踪
下沉。
跟踪源本身没有用,它们必须“连接”到其他部分
实际上利用接收器提供的信息做一些有用的事情的代码。痕迹
接收器是跟踪源提供的事件和数据的使用者。例如,
可以创建一个跟踪接收器(当连接到跟踪源时)
前面的示例)打印出收到的数据包中有趣的部分。
这种明确划分的基本原理是允许用户将新型接收器附加到
现有的跟踪源,无需编辑和重新编译核心
模拟器。因此,在上面的示例中,用户可以在她的中定义一个新的跟踪接收器
脚本并将其附加到模拟核心中定义的现有跟踪源
仅编辑用户脚本。
在本教程中,我们将介绍一些预定义的源和接收器,并展示如何
它们可以通过很少的用户努力来定制。请参阅 ns-3 手册或操作方法部分
有关高级跟踪配置的信息,包括扩展跟踪
命名空间并创建新的跟踪源。
ASCII码 追踪
ns-3 提供包装低级跟踪系统的帮助程序功能来帮助您
包含配置一些易于理解的数据包跟踪所涉及的详细信息。如果你
启用此功能,您将看到 ASCII 文件中的输出 --- 因而得名。为了
那些熟悉的人 ns-2 输出,这种类型的跟踪类似于 输出.tr 产生
通过许多脚本。
让我们直接开始并将一些 ASCII 跟踪输出添加到我们的 从头开始/myfirst.cc
脚本。就在致电之前 模拟器::运行 (),添加以下代码行:
AsciiTraceHelper ascii;
pointToPoint.EnableAsciiAll (ascii.CreateFileStream ("myfirst.tr"));
像其他很多 ns-3 习语,此代码使用辅助对象来帮助创建 ASCII
痕迹。第二行包含两个嵌套方法调用。 “内部”方法,
创建文件流() 使用未命名对象习惯用法在
堆栈(没有对象名称)并将其传递给被调用的方法。我们将讨论这个
将来会更多,但此时您所需要知道的是您正在创建一个
表示名为“myfirst.tr”的文件的对象并将其传递到 ns-3。你在告诉
ns-3 处理创建对象的生命周期问题,也处理问题
由与复制相关的 C++ ofstream 对象鲜为人知(有意)的限制引起
构造器。
外线电话,至 启用AsciiAll(),告诉帮助程序您要启用 ASCII
跟踪模拟中的所有点对点设备;并且你想要(提供的)
跟踪接收器以 ASCII 格式写出有关数据包移动的信息。
对于那些熟悉 ns-2,被追踪的事件相当于流行的追踪点
记录“+”、“-”、“d”和“r”事件。
您现在可以构建脚本并从命令行运行它:
$ ./waf --运行 scrap/myfirst
正如您之前多次看到的那样,您会看到来自 Waf 的一些消息,然后
“‘构建’成功完成”,并显示来自正在运行的程序的一些消息。
当它运行时,程序将创建一个名为 我的第一个.tr。因为方式
Waf 可以工作,该文件不是在本地目录中创建的,而是在
默认情况下存储库的顶级目录。如果你想控制痕迹的位置
已保存,您可以使用 --cwd Waf 的选项来指定这一点。我们还没有这样做,因此
我们需要切换到存储库的顶级目录并查看 ASCII
跟踪文件 我的第一个.tr 在您最喜欢的编辑器中。
解析 ASCII 痕迹
那里有很多非常密集的信息,但首先要注意的是
是这个文件中有许多不同的行。可能很难看到
除非你大幅拓宽你的窗口,否则这一点很明显。
文件中的每一行对应一个 追踪 活动。在这种情况下,我们正在跟踪事件
这些因素包括原料奶的可用性以及达到必要粉末质量水平所需的工艺。 发送 队列 存在于模拟中的每个点对点网络设备中。这
传输队列是一个队列,每个发往点对点通道的数据包都通过该队列
必须通过。请注意,跟踪文件中的每一行都以一个单独的字符开头(有一个
后面有空格)。该字符将具有以下含义:
· +:设备队列发生入队操作;
· -:设备队列发生出队操作;
· d:数据包被丢弃,通常是因为队列已满;
· r:网络设备收到数据包。
让我们更详细地查看跟踪文件中的第一行。我会把它分解
分成几个部分(为了清晰起见,缩进),左侧有参考编号:
+
2
/NodeList/0/DeviceList/0/$ns3::PointToPointNetDevice/TxQueue/Enqueue
ns3::PppHeader (
点对点协议:IP (0x0021))
ns3::Ipv4Header (
tos 0x0 ttl 64 id 0 协议 17 偏移 0 标志 [无]
长度:1052 10.1.1.1 > 10.1.1.2)
ns3::UdpHeader (
长度:1032 49153 > 9)
有效负载(大小=1024)
该扩展跟踪事件的第一部分(参考编号 0)是操作。我们
有 + 字符,所以这对应于 排队 对传输队列的操作。
第二部分(参考文献 1)是以秒为单位的模拟时间。您可以
记得我们问过 UdpEchoClient应用程序 两秒后开始发送数据包。
在这里,我们看到这确实正在发生。
示例跟踪的下一部分(参考文献 2)告诉我们哪个跟踪源源自
此事件(在跟踪命名空间中表示)。你可以想到跟踪命名空间
有点像文件系统名称空间。命名空间的根是
节点列表。这对应于在以下位置管理的容器 ns-3 核心代码包含所有
在脚本中创建的节点。就像文件系统可能有目录一样
在根下,我们可能有节点号 节点列表. 字符串 /节点列表/0
因此指的是第零个节点 节点列表 我们通常将其视为“节点
0”。在每个节点中都有一个已安装的设备列表。此列表出现
命名空间中的下一个。可以看到这个trace事件来自 设备列表/0 这是
节点中安装的第零个设备。
下一个字符串, $ns3::PointToPointNetDevice 告诉你里面有什么类型的设备
节点零的设备列表的第零位置。回想一下操作 + 发现于
引用 00 表示设备的传输队列上发生了入队操作。
这反映在“跟踪路径”的最后几段中,即 发送队列/入队.
跟踪中的其余部分应该相当直观。参考文献3-4表明
数据包封装在点对点协议中。参考文献5-7表明
该数据包具有 IP 版本 10.1.1.1 标头,并且源自 IP 地址 XNUMX 且
目的地为 10.1.1.2。参考文献 8-9 显示该数据包具有 UDP 标头,并且,
最后,参考文献 10 显示有效负载为预期的 1024 字节。
跟踪文件中的下一行显示同一数据包从传输中出列
同一节点上的队列。
跟踪文件中的第三行显示网络设备正在接收的数据包
与 echo 服务器的节点。我在下面重现了该事件。
r
2.25732
/NodeList/1/DeviceList/0/$ns3::PointToPointNetDevice/MacRx
ns3::Ipv4Header (
tos 0x0 ttl 64 id 0 协议 17 偏移 0 标志 [无]
长度:1052 10.1.1.1 > 10.1.1.2)
ns3::UdpHeader (
长度:1032 49153 > 9)
有效负载(大小=1024)
请注意,跟踪操作现在是 r 模拟时间增加到2.25732
秒。如果您一直严格遵循教程步骤,这意味着您已经
离开了 数据速率 网络设备和通道 延迟 设置为默认值。
这次应该很熟悉,因为您在上一节中已经看到过它。
跟踪源命名空间条目(参考 02)已更改,以反映此事件
来自节点 1 (/节点列表/1)和数据包接收跟踪源(/MacRx)。 它
您应该很容易通过拓扑跟踪数据包的进度
查看文件中的其余痕迹。
PCAP 追踪
这个 ns-3 设备助手还可以用于在中创建跟踪文件 .pcap 格式。 这
首字母缩略词 pcap(通常小写)代表数据包捕获,实际上是一个
API 包含一个定义 .pcap 文件格式。最受欢迎的节目是
可以读取并显示这种格式的是Wireshark(以前称为Ethereal)。然而,有
许多流量跟踪分析器都使用这种数据包格式。我们鼓励用户
利用许多可用于分析 pcap 痕迹的工具。在本教程中,我们
专注于使用 tcpdump 查看 pcap 跟踪。
用于启用 pcap 跟踪的代码是一行代码。
pointToPoint.EnablePcapAll("myfirst");
继续,在我们刚刚添加的 ASCII 跟踪代码后面插入这行代码
从头开始/myfirst.cc。请注意,我们只传递了字符串“myfirst”,而不是
“myfirst.pcap”或类似的东西。这是因为该参数是前缀,而不是
完整的文件名。助手实际上会为每个点对点创建一个跟踪文件
模拟中的设备。文件名将使用前缀、节点号、
设备号和“.pcap”后缀。
在我们的示例脚本中,我们最终将看到名为“myfirst-0-0.pcap”的文件和
“myfirst-1-0.pcap”是节点 0-设备 0 和节点 1-设备 0 的 pcap 跟踪,
。
添加代码行以启用 pcap 跟踪后,您可以在
通常的方式:
$ ./waf --运行 scrap/myfirst
如果您查看发行版的顶级目录,您现在应该看到三个日志
文件: 我的第一个.tr 是我们之前检查过的 ASCII 跟踪文件。 myfirst-0-0.pcap
和 myfirst-1-0.pcap 是我们刚刚生成的新 pcap 文件。
阅读 产量 - 转储
此时最简单的事情就是使用 转储 看看 电容 文件。
$ tcpdump -nn -tt -r myfirst-0-0.pcap
从文件 myfirst-0-0.pcap 中读取,链路类型 PPP (PPP)
2.000000 IP 10.1.1.1.49153 > 10.1.1.2.9:UDP,长度 1024
2.514648 IP 10.1.1.2.9 > 10.1.1.1.49153:UDP,长度 1024
tcpdump -nn -tt -r myfirst-1-0.pcap
从文件 myfirst-1-0.pcap 中读取,链路类型 PPP (PPP)
2.257324 IP 10.1.1.1.49153 > 10.1.1.2.9:UDP,长度 1024
2.257324 IP 10.1.1.2.9 > 10.1.1.1.49153:UDP,长度 1024
你可以在转储中看到 myfirst-0-0.pcap (客户端设备)回显数据包是
在模拟 2 秒时发送。如果你看第二个转储(myfirst-1-0.pcap)
您可以看到该数据包在 2.257324 秒时收到。你看到数据包是
在第二个转储中的 2.257324 秒处回显,最后,您看到数据包正在
在 2.514648 秒的第一次转储中收到返回给客户端。
阅读 产量 - Wireshark的
如果您不熟悉 Wireshark,可以通过一个网站访问
下载程序和文档: http://www.wireshark.org/.
Wireshark 是一个图形用户界面,可用于显示这些跟踪
文件。如果您有可用的 Wireshark,则可以打开每个跟踪文件并显示
内容就像您使用捕获的数据包一样 包 嗅探器.
建築 拓扑结构
构建 a 公共汽车 网络 拓扑
在本节中,我们将扩展我们对 ns-3 网络设备和通道
涵盖总线网络的示例。 ns-3 提供了一个网络设备和通道,我们称之为CSMA
(载波侦听多路访问)。
这个 ns-3 CSMA 设备本着以太网的精神模拟一个简单的网络。真正的以太网
使用 CSMA/CD(带冲突检测的载波侦听多路访问)方案
呈指数增加的退避来争夺共享传输介质。这 ns-3
CSMA 设备和信道模型只是其中的一个子集。
正如我们在构造时看到的点对点拓扑辅助对象
点对点拓扑,我们将在本节中看到等效的 CSMA 拓扑助手。
这些助手的外观和操作你应该很熟悉。
我们在 example/tutorial} 目录中提供了示例脚本。该脚本构建于
这些因素包括原料奶的可用性以及达到必要粉末质量水平所需的工艺。 第一个.cc 脚本并将 CSMA 网络添加到我们已经完成的点对点模拟中
经过考虑的。继续并打开 示例/教程/second.cc 在您最喜欢的编辑器中。你
已经看够了 ns-3 代码来理解这里发生的大部分事情
例如,但我们将检查整个脚本并检查一些输出。
正如在 第一个.cc 示例(以及所有 ns-3 示例)文件以 emacs 开头
模式行和一些 GPL 样板。
实际的代码首先加载模块包含文件,就像在 第一个.cc
例。
#include“ns3/core-module.h”
#include“ns3/网络模块.h”
#include“ns3/csma-module.h”
#include "ns3/internet-module.h"
#include“ns3/点对点模块.h”
#include“ns3/applications-module.h”
#include“ns3/ipv4-global-routing-helper.h”
一件非常有用的事情是一小段展示卡通的 ASCII 艺术
示例中构建的网络拓扑。你会发现类似的“图画”
我们的大多数例子。
在本例中,您可以看到我们将扩展我们的点对点示例(链接
在下面的节点 n0 和 n1 之间)通过将总线网络挂在右侧。注意
这是默认的网络拓扑,因为您实际上可以改变节点的数量
在 LAN 上创建。如果将 nCsma 设置为 1,则网络上总共有两个节点
LAN(CSMA 通道)——1 个必需节点和 1 个“额外”节点。默认有三个
“额外”节点如下所示:
// 默认网络拓扑
//
//10.1.1.0
// n0 -------------- n1 n2 n3 n4
// 点对点 | | | |
// ================
// 局域网10.1.2.0
那么 ns-3 命名空间是 用过的 并定义了一个日志组件。这一切都正如
它是在 第一个.cc,所以还没有什么新的东西。
使用命名空间 ns3;
NS_LOG_COMPONENT_DEFINE ("SecondScriptExample");
主程序的开头略有不同。我们使用详细标志来
确定是否 UdpEchoClient应用程序 和 UdpEchoServer应用程序 记录
组件已启用。该标志默认为 true(日志记录组件已启用)
但允许我们在本示例的回归测试期间关闭日志记录。
您将看到一些熟悉的代码,允许您更改网络上的设备数量
通过命令行参数的 CSMA 网络。当我们允许时,我们做了类似的事情
在命令行参数部分中要更改的发送数据包的数量。最后
线确保您至少有一个“额外”节点。
该代码由之前介绍的 API 的变体组成,因此您应该完全了解
到目前为止,您对本教程中的以下代码感到满意。
布尔详细 = true;
uint32_t nCsma = 3;
命令行 cmd;
cmd.AddValue("nCsma", "额外的CSMA节点/设备数量", nCsma);
cmd.AddValue("verbose", "如果 true 则告诉 echo 应用程序记录", verbose);
cmd.Parse(argc, argv);
如果(详细)
{
LogComponentEnable("UdpEchoClientApplication", LOG_LEVEL_INFO);
LogComponentEnable("UdpEchoServerApplication", LOG_LEVEL_INFO);
}
nCsma = nCsma == 0 ? 1:nCsma;
下一步是创建两个节点,我们将通过点对点链路连接它们。
这个 节点容器 用于执行此操作,就像在中所做的那样 第一个.cc.
NodeContainer p2pNodes;
p2pNodes.Create(2);
接下来,我们声明另一个 节点容器 保存将成为总线一部分的节点
(CSMA)网络。首先,我们只是实例化容器对象本身。
节点容器 csmaNodes;
csmaNodes.Add(p2pNodes.Get(1));
csmaNodes.Create(nCsma);
下一行代码 获取 第一个节点(如索引为 1)
点对点节点容器并将其添加到将获得 CSMA 的节点容器中
设备。有问题的节点最终将是一个点对点设备 和 信道SMA
设备。然后我们创建一些“额外”节点来组成 CSMA 的其余部分
网络。由于我们已经在 CSMA 网络中拥有一个节点——即将拥有的节点
无论是点对点还是CSMA网络设备,“额外”节点的数量都意味着数量
CSMA 部分中您想要的节点减一。
接下来的代码现在应该非常熟悉了。我们实例化一个 点对点助手
并设置关联的默认值 Attributes 这样我们就创造了每秒 5 兆比特
使用助手创建的设备上的发射器以及通道上的两毫秒延迟
由助手创建。
点对点助手点对点;
pointToPoint.SetDeviceAttribute("DataRate", StringValue("5Mbps"));
pointToPoint.SetChannelAttribute("延迟", StringValue("2ms"));
NetDeviceContainer p2pDevices;
p2pDevices = pointToPoint.Install(p2pNodes);
然后我们实例化一个 网络设备容器 跟踪点对点网络设备
和我们 安装 点对点节点上的设备。
我们上面提到您将看到 CSMA 设备和通道的帮助程序,并且
接下来几行介绍它们。这 Csma助手 就像一个 点对点助手,但
它创建并连接 CSMA 设备和通道。对于 CSMA 设备和
通道对,请注意数据速率由指定 渠道 属性 代替
设备 属性。这是因为真正的 CSMA 网络不允许混合,因为
例如,给定通道上的 10Base-T 和 100Base-T 设备。我们首先将数据速率设置为
每秒100兆,然后将通道的光速延迟设置为6560
纳秒(任意选择为 1 米段内每英尺 100 纳秒)。
请注意,您可以设置 属性 使用其本机数据类型。
CsmaHelper csma;
csma.SetChannelAttribute("DataRate", StringValue("100Mbps"));
csma.SetChannelAttribute("延迟", TimeValue (NanoSeconds (6560)));
NetDeviceContainer csmaDevices;
csmaDevices = csma.Install(csmaNodes);
正如我们创建了一个 网络设备容器 保存由创建的设备
点对点助手 我们创建一个 网络设备容器 保存我们创建的设备
Csma助手。我们称之为 安装 的方法 Csma助手 将设备安装到
的节点 csma节点 节点容器.
我们现在已经创建了节点、设备和通道,但是我们还没有协议栈
展示。正如在 第一个.cc 脚本,我们将使用 互联网堆栈助手 安装
这些堆栈。
InternetStackHelper堆栈;
stack.Install(p2pNodes.Get(0));
stack.Install(csmaNodes);
回想一下,我们从 p2p节点 容器并将其添加到
csma节点 容器。因此我们只需要在剩余的位置上安装堆栈即可 p2p节点
节点,以及该节点中的所有节点 csma节点 容器覆盖所有节点
模拟。
正如在 第一个.cc 示例脚本,我们将使用 ipv4地址助手 至
为我们的设备接口分配 IP 地址。首先我们使用网络10.1.1.0来创建
我们的两个点对点设备所需的两个地址。
Ipv4AddressHelper地址;
地址.SetBase("10.1.1.0","255.255.255.0");
Ipv4InterfaceContainer p2pInterfaces;
p2pInterfaces = 地址.Assign (p2pDevices);
回想一下,我们将创建的接口保存在容器中,以便于拉出
稍后寻址信息以用于设置应用程序。
现在,我们需要为 CSMA 设备接口分配 IP 地址。操作有效
就像点对点情况一样,只是我们现在正在执行操作
具有可变数量 CSMA 设备的容器 --- 记住我们制作了数量
CSMA 设备可通过命令行参数更改。 CSMA 设备将被关联
在本例中,IP 地址来自网络号 10.1.2.0,如下所示。
地址.SetBase("10.1.2.0","255.255.255.0");
Ipv4InterfaceContainer csmaInterfaces;
csmaInterfaces = 地址.Assign (csmaDevices);
现在我们已经构建了拓扑,但我们需要应用程序。这部分将是
基本上类似于应用程序部分 第一个.cc 但我们将会
在具有 CSMA 设备的节点之一上实例化服务器,在具有 CSMA 设备的节点上实例化客户端
仅具有点对点设备的节点。
首先,我们设置回显服务器。我们创建一个 UdpEcho服务器助手 并提供所需的
属性 构造函数的值是服务器端口号。回想一下这个端口
可以稍后使用更改 设置属性 如果需要的话,方法,但我们要求它是
提供给构造函数。
UdpEchoServerHelper echoServer (9);
ApplicationContainer serverApps = echoServer.Install(csmaNodes.Get(nCsma));
serverApps.Start(秒(1.0));
serverApps.Stop(秒(10.0));
回想一下 csma节点 节点容器 包含为以下创建的节点之一
点对点网络和 nCsma “额外”节点。我们想要得到的是最后一个
“额外”节点。第 0 个条目 csma节点 容器将是点对点的
节点。那么,想到这一点的简单方法是,如果我们创建一个“额外”CSMA 节点,那么它
将位于索引之一 csma节点 容器。通过归纳法,如果我们创建 nCsma “额外的”
最后一个节点将位于索引处 nCsma。你会看到这个展示在 积极 第一个
行代码。
客户端应用程序的设置与我们在 第一个.cc 示例脚本。再次,
我们提供所需的 Attributes 以及 UdpEchoClientHelper 在构造函数中(在本例中
远程地址和端口)。我们告诉客户端将数据包发送到服务器
安装在最后一个“额外”CSMA 节点上。我们把客户端安装在最左边
拓扑图中看到的点对点节点。
UdpEchoClientHelper echoClient(csmaInterfaces.GetAddress(nCsma), 9);
echoClient.SetAttribute("MaxPackets", UintegerValue(1));
echoClient.SetAttribute("间隔", TimeValue(秒(1.0)));
echoClient.SetAttribute("PacketSize", UintegerValue(1024));
ApplicationContainer clientApps = echoClient.Install(p2pNodes.Get(0));
clientApps.Start(秒(2.0));
clientApps.Stop(秒(10.0));
由于我们实际上已经在这里建立了互联网,因此我们需要某种形式的互联网
路由。 ns-3 提供我们所谓的全局路由来帮助您。全局路由需要
利用整个互联网可以在模拟中访问的事实,
运行为模拟创建的所有节点——它完成了以下艰苦的工作
为您设置路由,而无需配置路由器。
基本上,发生的情况是每个节点的行为就好像它是一个 OSPF 路由器,
与幕后的所有其他路由器进行即时且神奇的通信。每个节点
生成链接通告并将其直接传送给全局路由管理器
它使用此全局信息来构建每个节点的路由表。环境
这种形式的路由是单行的:
Ipv4GlobalRoutingHelper::PopulateRoutingTables ();
接下来我们启用 pcap 跟踪。启用 pcap 跟踪的第一行代码
现在您应该熟悉点对点助手了。第二行启用pcap
在 CSMA 帮助程序中进行跟踪,有一个您尚未遇到的额外参数。
pointToPoint.EnablePcapAll ("第二");
csma.EnablePcap("第二个", csmaDevices.Get(1), true);
CSMA网络是多点对点网络。这意味着可以(并且在
在这种情况下)共享介质上的多个端点。每个端点都有一个网络
与其关联的设备。收集踪迹有两种基本的替代方法
来自此类网络的信息。一种方法是为每个网络设备创建跟踪文件
并仅存储该网络设备发出或消耗的数据包。其他方式
是选择其中一个设备并将其置于混杂模式。那么那个单一设备
“嗅探”网络中的所有数据包并将它们存储在单个 pcap 文件中。就是这样
转储,例如,有效。最后一个参数告诉 CSMA 助手是否
安排以混杂模式捕获数据包。
在此示例中,我们将选择 CSMA 网络上的一台设备并询问它
对网络进行混杂的嗅探,从而模拟什么 转储 会做。
如果你在 Linux 机器上你可能会做类似的事情 转储 -i eth0 得到
痕迹。在本例中,我们使用指定设备 csmaDevices.Get(1),它选择
容器中的第一个设备。将最终参数设置为 true 可以实现混杂
捕获。
代码的最后一部分只是运行并清理模拟,就像 第一个.cc
例。
模拟器::运行();
模拟器::销毁();
0返回;
}
为了运行此示例,请复制 第二抄送 将示例脚本放入暂存目录
并使用 waf 进行构建,就像使用 第一个.cc 例子。如果您在
您刚刚键入的存储库的顶级目录,
$ cp 示例/教程/second.cc scrap/mysecond.cc
$./waf
警告:我们使用该文件 第二抄送 作为我们的回归测试之一,以验证其是否有效
正如我们所认为的那样,为了让您的教程体验变得积极。
这意味着名为 第二 项目中已经存在。为了避免任何
对您正在执行的内容感到困惑,请重命名为 我的第二个cc 建议
以上。
如果您认真地遵循本教程(您是,不是吗),您仍然可以
NS_LOG 变量已设置,因此请继续清除该变量并运行程序。
$ 导出 NS_LOG=
$ ./waf --运行 scrap/mysecond
由于我们已经设置了 UDP echo 应用程序来进行日志记录,就像我们在 第一个.cc, 你会
运行脚本时会看到类似的输出。
Waf:进入目录“/home/craigdo/repos/ns-3-allinone/ns-3-dev/build”
Waf:离开目录“/home/craigdo/repos/ns-3-allinone/ns-3-dev/build”
“构建”成功完成(0.415s)
发送 1024 字节到 10.1.2.4
从 1024 接收到 10.1.1.1 字节
从 1024 接收到 10.1.2.4 字节
回想一下第一条消息,“发送次数 1024 字节 至 10.1.2.4,” 是 UDP 回显客户端
向服务器发送数据包。在这种情况下,服务器位于不同的网络上
(10.1.2.0)。第二条消息“收到 1024 字节 , 10.1.1.1,”来自 UDP 回显
服务器接收到 echo 数据包时生成。最后的消息“收到 1024
字节 , 10.1.2.4,”来自echo客户端,表示已经收到它的echo
从服务器返回。
如果您现在查看顶级目录,您将发现三个跟踪文件:
second-0-0.pcap second-1-0.pcap second-2-0.pcap
让我们花点时间看一下这些文件的命名。它们都有着相同的形态,
- - .pcap。例如,列表中的第一个文件是
第二个-0-0.pcap 这是从节点零、设备零开始的 pcap 跟踪。这是
节点零上的点对点网络设备。文件 第二个-1-0.pcap 是 pcap 跟踪
节点一上的设备零,也是点对点网络设备;和文件 第二个-2-0.pcap is
节点二上设备零的 pcap 跟踪。
如果您回顾本节开头的拓扑图,您将看到
节点 0 是点对点链路的最左边节点,节点 1 是节点
同时具有点对点设备和 CSMA 设备。你会看到节点二是
CSMA 网络上的第一个“额外”节点及其设备零被选为设备
捕获混杂模式跟踪。
现在,让我们通过互联网跟踪回显数据包。首先,执行 tcpdump
最左边的点对点节点 --- 节点零的跟踪文件。
$ tcpdump -nn -tt -r 第二个-0-0.pcap
您应该看到显示的 pcap 文件的内容:
从文件 secondary-0-0.pcap 中读取,链路类型 PPP (PPP)
2.000000 IP 10.1.1.1.49153 > 10.1.2.4.9:UDP,长度 1024
2.017607 IP 10.1.2.4.9 > 10.1.1.1.49153:UDP,长度 1024
转储的第一行表明链路类型是 PPP(点对点),我们
预计。然后,您会看到回显数据包通过与 IP 关联的设备离开节点零
地址 10.1.1.1 前往 IP 地址 10.1.2.4(最右边的 CSMA 节点)。这个包
将在点对点链路上移动并被点对点网络设备接收
节点一。让我们来看看:
$ tcpdump -nn -tt -r 第二个-1-0.pcap
您现在应该看到点对点链路另一端的 pcap 跟踪输出:
从文件 secondary-1-0.pcap 中读取,链路类型 PPP (PPP)
2.003686 IP 10.1.1.1.49153 > 10.1.2.4.9:UDP,长度 1024
2.013921 IP 10.1.2.4.9 > 10.1.1.1.49153:UDP,长度 1024
在这里我们看到链路类型也是我们所期望的 PPP。可以看到来自IP的数据包
地址 10.1.1.1(在 2.000000 秒发送)前往 IP 地址 10.1.2.4
出现在这个界面上。现在,在该节点内部,数据包将被转发到
CSMA 接口,我们应该看到它在该设备上弹出,走向其终极
目的地。
请记住,我们选择节点 2 作为 CSMA 网络的混杂嗅探器节点,因此
然后让我们看一下 secondary-2-0.pcap 并看看它是否存在。
$ tcpdump -nn -tt -r 第二个-2-0.pcap
您现在应该看到节点二、设备零的混杂转储:
从文件 secondary-2-0.pcap 读取,链接类型 EN10MB(以太网)
2.007698 ARP,请求who-has 10.1.2.4(ff:ff:ff:ff:ff:ff)告诉10.1.2.1,长度50
2.007710 ARP,回复 10.1.2.4 is-at 00:00:00:00:00:06,长度 50
2.007803 IP 10.1.1.1.49153 > 10.1.2.4.9:UDP,长度 1024
2.013815 ARP,请求who-has 10.1.2.1(ff:ff:ff:ff:ff:ff)告诉10.1.2.4,长度50
2.013828 ARP,回复 10.1.2.1 is-at 00:00:00:00:00:03,长度 50
2.013921 IP 10.1.2.4.9 > 10.1.1.1.49153:UDP,长度 1024
如您所见,链接类型现在为“以太网”。不过,新的东西出现了。这
公交网络需求 ARP,地址解析协议。节点一知道它需要发送
数据包发送至 IP 地址 10.1.2.4,但不知道该数据包的 MAC 地址
对应的节点。它在 CSMA 网络 (ff:ff:ff:ff:ff:ff) 上广播,请求
IP 地址为 10.1.2.4 的设备。在这种情况下,最右边的节点会回复说
位于 MAC 地址 00:00:00:00:00:06。请注意,节点二不直接参与此过程
交换,但正在嗅探网络并报告它看到的所有流量。
这种交换可以在以下几行中看到,
2.007698 ARP,请求who-has 10.1.2.4(ff:ff:ff:ff:ff:ff)告诉10.1.2.1,长度50
2.007710 ARP,回复 10.1.2.4 is-at 00:00:00:00:00:06,长度 50
然后节点一、设备一继续将回显数据包发送到 UDP 回显服务器
IP 地址 10.1.2.4.
2.007803 IP 10.1.1.1.49153 > 10.1.2.4.9:UDP,长度 1024
服务器收到回显请求并尝试将数据包发送回
来源。服务器知道该地址位于它通过以下方式到达的另一个网络上
IP 地址 10.1.2.1。这是因为我们初始化了全局路由并且它已经计算出所有
这对我们来说。但是,echo 服务器节点不知道第一个服务器的 MAC 地址
CSMA 节点,因此它必须为它进行 ARP,就像第一个 CSMA 节点必须做的那样。
2.013815 ARP,请求who-has 10.1.2.1(ff:ff:ff:ff:ff:ff)告诉10.1.2.4,长度50
2.013828 ARP,回复 10.1.2.1 is-at 00:00:00:00:00:03,长度 50
然后服务器将回显发送回转发节点。
2.013921 IP 10.1.2.4.9 > 10.1.1.1.49153:UDP,长度 1024
回头看一下点对点链路的最右边的节点,
$ tcpdump -nn -tt -r 第二个-1-0.pcap
您现在可以看到回显的数据包作为最后一个数据包返回到点对点链路上
跟踪转储的行。
从文件 secondary-1-0.pcap 中读取,链路类型 PPP (PPP)
2.003686 IP 10.1.1.1.49153 > 10.1.2.4.9:UDP,长度 1024
2.013921 IP 10.1.2.4.9 > 10.1.1.1.49153:UDP,长度 1024
最后,您可以回顾一下发出回声的节点
$ tcpdump -nn -tt -r 第二个-0-0.pcap
并看到回显的数据包在 2.007602 秒返回源,
从文件 secondary-0-0.pcap 中读取,链路类型 PPP (PPP)
2.000000 IP 10.1.1.1.49153 > 10.1.2.4.9:UDP,长度 1024
2.017607 IP 10.1.2.4.9 > 10.1.1.1.49153:UDP,长度 1024
最后,回想一下,我们添加了控制 CSMA 设备数量的功能
通过命令行参数进行模拟。您可以按照与以下相同的方式更改此参数
我们研究了改变在 第一个.cc 例子。尝试跑步
该程序将“额外”设备的数量设置为四:
$ ./waf --run "scratch/mysecond --nCsma=4"
你现在应该看到,
Waf:进入目录“/home/craigdo/repos/ns-3-allinone/ns-3-dev/build”
Waf:离开目录“/home/craigdo/repos/ns-3-allinone/ns-3-dev/build”
“构建”成功完成(0.405s)
2s时刻客户端发送1024字节到10.1.2.5端口9
在时间 2.0118s 服务器从 1024 端口 10.1.1.1 接收到 49153 字节
在时间2.0118s服务器发送1024字节到10.1.1.1端口49153
在时间 2.02461s 客户端从 1024 端口 10.1.2.5 接收到 9 字节
请注意,回显服务器现已重新定位到最后一个 CSMA 节点,即
10.1.2.5 而不是默认情况 10.1.2.4。
您可能对旁观者生成的跟踪文件不满意
CSMA网络。您可能确实想从单个设备获取跟踪信息,但可能不会
对网络上的任何其他流量感兴趣。您可以相当轻松地做到这一点。
我们来看看吧 暂存/mysecond.cc 并添加该代码使我们能够更加
具体。 ns-3 帮助程序提供将节点号和设备号作为
参数。继续更换 启用Pcap 通过以下调用进行调用。
pointToPoint.EnablePcap("秒", p2pNodes.Get(0)->GetId(), 0);
csma.EnablePcap("第二个", csmaNodes.Get(nCsma)->GetId(), 0, false);
csma.EnablePcap("第二个", csmaNodes.Get(nCsma-1)->GetId(), 0, false);
我们知道我们想要创建一个基本名称为“second”的 pcap 文件,我们也知道
这两种情况下感兴趣的设备都将为零,因此这些参数不是
非常有意思。
为了获得节点编号,您有两种选择:首先,节点以 a 的形式编号。
按照您创建的顺序从零开始单调增加时尚
他们。获取节点号的一种方法是“手动”计算出该数字
考虑节点创建的顺序。如果你看一下网络拓扑
文件开头的插图,我们为您做到了这一点,您可以看到
最后一个 CSMA 节点将是节点号 nCsma + 1。这种方法可能会变得烦人
在较大的模拟中很困难。
我们在这里使用的另一种方法是认识到 节点容器 包含
指向 ns-3 Node 对象。这 Node 对象有一个方法叫做 获取ID 会的
返回该节点的 ID,即我们要查找的节点号。让我们一起去看看
多氧用于 Node 并找到该方法,该方法位于 ns-3 核心代码
比我们迄今为止所看到的;但有时你必须努力寻找有用的东西。
转到您的版本的 Doxygen 文档(请记住,您可以在
项目网站)。您可以前往 Node 通过查看文档
“班级”选项卡并向下滚动“班级列表”,直到找到 ns3::节点。 选择
ns3::节点 您将被带到相关文档 Node 班级。如果你现在
向下滚动到 获取ID 方法并选择它,您将进入详细信息
该方法的文档。使用 获取ID 方法可以确定节点数
在复杂的拓扑中要容易得多。
让我们从顶级目录中清除旧的跟踪文件,以避免混淆
到底是怎么回事,
$ rm *.pcap
$ rm *.tr
如果您构建新脚本并运行模拟设置 nCsma 到100,
$ ./waf --run "scratch/mysecond --nCsma=100"
您将看到以下输出:
Waf:进入目录“/home/craigdo/repos/ns-3-allinone/ns-3-dev/build”
Waf:离开目录“/home/craigdo/repos/ns-3-allinone/ns-3-dev/build”
“构建”成功完成(0.407s)
2s时刻客户端发送1024字节到10.1.2.101端口9
在时间 2.0068s 服务器从 1024 端口 10.1.1.1 接收到 49153 字节
在时间2.0068s服务器发送1024字节到10.1.1.1端口49153
在时间 2.01761s 客户端从 1024 端口 10.1.2.101 接收到 9 字节
请注意,echo 服务器现在位于 10.1.2.101,对应于具有 100
“额外”CSMA 节点,回显服务器位于最后一个节点。如果您列出 pcap 文件
您将看到的顶级目录,
second-0-0.pcap second-100-0.pcap second-101-0.pcap
跟踪文件 第二个-0-0.pcap 是“最左边”的点对点设备,即 echo
数据包来源。文件 第二个-101-0.pcap 对应于最右边的CSMA设备
是 echo 服务器所在的位置。您可能已经注意到,最后一个参数
在 echo 服务器节点上启用 pcap 跟踪的调用错误。这意味着跟踪
在该节点上收集的数据处于非混杂模式。
为了说明混杂和非混杂痕迹之间的区别,我们还
请求倒数第二个节点的非混杂跟踪。继续看一下
这些因素包括原料奶的可用性以及达到必要粉末质量水平所需的工艺。 转储 HPMC胶囊 第二个-100-0.pcap.
$ tcpdump -nn -tt -r 第二个-100-0.pcap
现在您可以看到节点 100 实际上是回声交换中的旁观者。唯一的
它收到的数据包是广播到整个 CSMA 的 ARP 请求
网络。
从文件 secondary-100-0.pcap 读取,链接类型 EN10MB(以太网)
2.006698 ARP,请求who-has 10.1.2.101(ff:ff:ff:ff:ff:ff)告诉10.1.2.1,长度50
2.013815 ARP,请求who-has 10.1.2.1(ff:ff:ff:ff:ff:ff)告诉10.1.2.101,长度50
现在来看看 转储 HPMC胶囊 第二个-101-0.pcap.
$ tcpdump -nn -tt -r 第二个-101-0.pcap
您现在可以看到节点 101 实际上是回声交换的参与者。
从文件 secondary-101-0.pcap 读取,链接类型 EN10MB(以太网)
2.006698 ARP,请求who-has 10.1.2.101(ff:ff:ff:ff:ff:ff)告诉10.1.2.1,长度50
2.006698 ARP,回复 10.1.2.101 is-at 00:00:00:00:00:67,长度 50
2.006803 IP 10.1.1.1.49153 > 10.1.2.101.9:UDP,长度 1024
2.013803 ARP,请求who-has 10.1.2.1(ff:ff:ff:ff:ff:ff)告诉10.1.2.101,长度50
2.013828 ARP,回复 10.1.2.1 is-at 00:00:00:00:00:03,长度 50
2.013828 IP 10.1.2.101.9 > 10.1.1.1.49153:UDP,长度 1024
楷模, Attributes 和 现实
这里是进行短途旅行和表达重要观点的便利场所。它可能
或者对您来说可能并不明显,但是每当人们使用模拟时,重要的是
准确了解正在建模的内容和未建模的内容。例如,这是很诱人的
将上一节中使用的 CSMA 设备和通道视为真实的
以太网设备;并期望模拟结果能够直接反映将会发生的情况
在真实的以太网中。不是这种情况。
根据定义,模型是现实的抽象。最终是责任
模拟脚本作者确定所谓的“精度范围”和“域
整个模拟及其组成部分的“适用性”。
在某些情况下,比如 晶闸管,可以很容易地确定什么是 而不去 建模。经过
阅读型号说明(csma.h)可以发现没有碰撞检测
在 CSMA 模型中,并决定其在您的模拟中的适用性或什么
您可能希望在结果中包含注意事项。在其他情况下,这可能很容易
配置可能与您可以出去购买的任何现实不符的行为。它
将证明值得花一些时间调查一些此类实例,以及如何
您可以轻松地在模拟中偏离现实的界限。
如您所见, ns-3 提供 Attributes 用户可以轻松设置以更改模型
行为。考虑其中的两个 Attributes 的 网络设备: 最大温度 和
封装模式。 该 最大温度 属性指示最大传输单元
设备。这是设备可以容纳的最大协议数据单元 (PDU) 的大小
发送。
MTU 默认为 1500 字节 网络设备。这个默认对应一个数字
参见 RFC 894,“通过以太网传输 IP 数据报的标准
网络。”该数字实际上源自 10Base5 的最大数据包大小
(全规格以太网)网络——1518 字节。如果减去DIX封装
以太网数据包的开销(18 字节),您最终将获得最大可能的数据大小
(MTU) 为 1500 字节。人们还可以发现, MTU 对于 IEEE 802.3 网络是 1492
字节。这是因为 LLC/SNAP 封装增加了额外的 8 个字节的开销
数据包。在这两种情况下,底层硬件只能发送1518字节,但是数据
大小不同。
为了设置封装模式, 网络设备 提供了一个 属性 被称为
封装模式 它可以接受价值观 迪克斯 or 有限责任公司。这些对应于以太网
和 LLC/SNAP 分别成帧。
如果一个人离开 最大温度 为 1500 字节,并将封装模式更改为 有限责任公司, 结果
将是一个使用 LLC/SNAP 帧封装 1500 字节 PDU 的网络,从而导致
1526 字节的数据包,这在许多网络中是非法的,因为它们可以传输
每个数据包最多 1518 字节。这很可能会导致模拟
相当巧妙地没有反映您可能期望的现实。
只是为了使图片复杂化,存在巨型帧(1500 < MTU <= 9000 字节)并且
未经 IEEE 正式认可但已批准的超级巨型(MTU > 9000 字节)帧
在某些高速(千兆位)网络和 NIC 中可用。一个人可以离开
封装模式设置为 迪克斯,然后设置 最大温度 属性 上 网络设备 到 64000 字节
——尽管有相关的 Csma频道 数据速率 被设置为每秒 10 兆比特。这
本质上会模拟一个由吸血鬼窃听的 1980 世纪 10 年代风格 5BaseXNUMX 制成的以太网交换机
支持超级巨型数据报的网络。这肯定不是以前的事
曾经制作过,也不可能制作过,但配置起来非常容易。
在前面的示例中,您使用命令行创建了一个具有 100
晶闸管 节点。您可以轻松创建具有 500 个节点的模拟。如果你
我们实际上正在对 10Base5 vampire-tap 网络进行建模,这是全规格的最大长度
以太网电缆长 500 米,最小分接头间距为 2.5 米。这意味着那里
在真实网络上只能有 200 次点击。你可以很容易地建造一个非法的
网络也以这种方式。这可能会也可能不会产生有意义的模拟
取决于您想要建模的内容。
类似的情况在很多地方都可能发生 ns-3 以及在任何模拟器中。例如,
您可以以这样的方式定位节点,使它们在
同时,或者您可以配置违反规定的放大器或噪声水平
物理基本定律。
ns-3 一般偏向于灵活性,很多型号都允许自由设置 Attributes
而不试图强制执行任何任意的一致性或特定的底层规范。
从中带回家的事情是 ns-3 将提供一个超级灵活的基础
供您实验。由您来了解您向系统询问的内容
去做并确保你创建的模拟具有一定的意义和一些
与您定义的现实的联系。
构建 a 无线耳机 网络 拓扑
在本节中,我们将进一步扩展我们的知识 ns-3 网络设备和
频道覆盖无线网络的示例。 ns-3 提供一组802.11模型
尝试提供 802.11 规范的准确 MAC 级实现
以及 802.11a 规范的“不太慢”的 PHY 级模型。
正如我们在以下情况中看到的点对点和 CSMA 拓扑辅助对象:
构建点对点拓扑,我们将看到等效的 免费无线网络 拓扑助手
本节。这些助手的外观和操作看起来应该很熟悉
你。
我们在我们的中提供了一个示例脚本 示例/教程 目录。该脚本构建于
这些因素包括原料奶的可用性以及达到必要粉末质量水平所需的工艺。 第二抄送 脚本并添加 Wifi 网络。继续并打开
示例/教程/third.cc 在您最喜欢的编辑器中。你已经看够了
ns-3 代码来理解这个例子中发生的大部分事情,但是有一些新的
的东西,所以我们将检查整个脚本并检查一些输出。
正如在 第二抄送 示例(并且在所有 ns-3 示例)文件以 emacs 开头
模式行和一些 GPL 样板。
看一下显示默认网络拓扑的 ASCII 艺术(复制如下)
示例中构建。你可以看到我们将进一步扩展我们的例子
将无线网络挂在左侧。请注意,这是默认网络
拓扑,因为您实际上可以改变在有线和无线上创建的节点数量
网络。正如在 第二抄送 脚本案例,如果你改变 nCsma,它会给你一个
“额外”CSMA 节点的数量。同样,你可以设置 无线网络 来控制多少个 RSPO小农培训师学院(STA)
(站)节点在模拟中创建。总会有一个 AP (切入点)
无线网络上的节点。默认情况下有三个“额外”CSMA 节点和三个
无线 RSPO小农培训师学院(STA) 节点。
代码首先加载模块包含文件,就像在 第二抄送 例。
有几个与 Wifi 模块和移动性相对应的新功能
我们将在下面讨论的模块。
#include“ns3/core-module.h”
#include“ns3/点对点模块.h”
#include“ns3/网络模块.h”
#include“ns3/applications-module.h”
#include“ns3/wifi-module.h”
#include“ns3/mobility-module.h”
#include“ns3/csma-module.h”
#include "ns3/internet-module.h"
网络拓扑图如下:
// 默认网络拓扑
//
// 无线网络10.1.3.0
// 美联社
// * * * *
// | | | | 10.1.1.0
// n5 n6 n7 n0 -------------- n1 n2 n3 n4
// 点对点 | | | |
// ================
// 局域网10.1.2.0
可以看到我们在左边的节点上添加了一个新的网络设备
成为无线网络接入点的点对点链路。一些
创建无线 STA 节点以填充新的 10.1.3.0 网络,如左图所示
插图的一侧。
插图结束后, ns-3 命名空间是 用过的 并定义了一个日志组件。
现在这一切都应该非常熟悉了。
使用命名空间 ns3;
NS_LOG_COMPONENT_DEFINE ("ThirdScriptExample");
主程序开始就像 第二抄送 通过添加一些命令行参数
启用或禁用日志记录组件以及更改创建的设备数量。
布尔详细 = true;
uint32_t nCsma = 3;
uint32_t nWifi = 3;
命令行 cmd;
cmd.AddValue("nCsma", "额外的CSMA节点/设备数量", nCsma);
cmd.AddValue("nWifi", "wifi STA 设备数量", nWifi);
cmd.AddValue("verbose", "如果 true 则告诉 echo 应用程序记录", verbose);
cmd.Parse(argc,argv);
如果(详细)
{
LogComponentEnable("UdpEchoClientApplication", LOG_LEVEL_INFO);
LogComponentEnable("UdpEchoServerApplication", LOG_LEVEL_INFO);
}
正如前面的所有示例一样,下一步是创建两个节点,我们将
通过点对点链路进行连接。
NodeContainer p2pNodes;
p2pNodes.Create(2);
接下来,我们见到了一位老朋友。我们实例化一个 点对点助手 并设置相关的
默认 Attributes 这样我们就可以在设备上创建每秒 5 兆比特的发射器
使用助手创建,并在助手创建的通道上有两毫秒的延迟。
然后,我们 安装 节点上的设备以及节点之间的通道。
点对点助手点对点;
pointToPoint.SetDeviceAttribute("DataRate", StringValue("5Mbps"));
pointToPoint.SetChannelAttribute("延迟", StringValue("2ms"));
NetDeviceContainer p2pDevices;
p2pDevices = pointToPoint.Install(p2pNodes);
接下来,我们声明另一个 节点容器 保存将成为总线一部分的节点
(CSMA)网络。
节点容器 csmaNodes;
csmaNodes.Add(p2pNodes.Get(1));
csmaNodes.Create(nCsma);
下一行代码 获取 第一个节点(如索引为 1)
点对点节点容器并将其添加到将获得 CSMA 的节点容器中
设备。相关节点最终将配备一个点对点设备和一个 CSMA
设备。然后我们创建一些“额外”节点来组成 CSMA 的其余部分
网络。
然后我们实例化一个 Csma助手 并设置它 Attributes 正如我们在前面的示例中所做的那样。
我们创建一个 网络设备容器 跟踪创建的 CSMA 网络设备,然后我们
安装 所选节点上的 CSMA 设备。
CsmaHelper csma;
csma.SetChannelAttribute("DataRate", StringValue("100Mbps"));
csma.SetChannelAttribute("延迟", TimeValue (NanoSeconds (6560)));
NetDeviceContainer csmaDevices;
csmaDevices = csma.Install(csmaNodes);
接下来,我们将创建成为 Wifi 网络一部分的节点。我们是
将创建命令行参数指定的多个“station”节点,并且
我们将使用点对点链路的“最左边”节点作为
切入点。
NodeContainer wifiStaNodes;
wifiStaNodes.Create(nWifi);
NodeContainer wifiApNode = p2pNodes.Get(0);
下一段代码构建了 wifi 设备以及它们之间的互连通道
这些 wifi 节点。首先,我们配置 PHY 和通道助手:
YansWifiChannelHelper 通道 = YansWifiChannelHelper::Default ();
YansWifiPhyHelper phy = YansWifiPhyHelper::Default ();
为简单起见,此代码使用默认的 PHY 层配置和通道模型
其记录在 API doxygen 文档中
YansWifiChannelHelper::默认 和 YansWifiPhyHelper::默认 方法。一旦这些物体
创建后,我们创建一个通道对象并将其关联到我们的 PHY 层对象管理器
以确保由创建的所有 PHY 层对象 YansWifi物理助手 共享
相同的底层信道,即它们共享相同的无线介质,并且可以
沟通与干扰:
phy.SetChannel(channel.Create());
配置 PHY 助手后,我们就可以专注于 MAC 层。在这里我们选择工作
对于非 Qos MAC,因此我们使用 NqosWifiMacHelper 对象来设置 MAC 参数。
WifiHelper wifi = WifiHelper::Default ();
wifi.SetRemoteStationManager("ns3::AarfWifiManager");
NqosWifiMacHelper mac = NqosWifiMacHelper::Default ();
这个 设置远程站管理器 方法告诉助手速率控制算法的类型
使用。这里,它要求助手使用 AARF 算法 --- 当然,详细信息是,
在 Doxygen 中可用。
接下来,我们配置我们想要的基础设施网络的MAC类型、SSID
设置并确保我们的站不执行主动探测:
ssid ssid=Ssid(“ns-3-ssid”);
mac.SetType("ns3::StaWifiMac",
“Ssid”,SsidValue(ssid),
“ActiveProbing”,布尔值(假));
此代码首先创建一个将使用的 802.11 服务集标识符 (SSID) 对象
设置“Ssid”的值 属性 MAC层的实现。特别是
由助手创建的 MAC 层类型由以下指定 属性 作为
“ns3::StaWifiMac”类型。指某东西的用途 NqosWifiMac助手 将确保
“支持Qos” 属性 对于创建的 MAC 对象设置为 false。这些的组合
两种配置意味着接下来创建的 MAC 实例将是非 QoS 非 AP
基础设施 BSS(即具有 AP 的 BSS)中的站 (STA)。最后,
《主动探测》 属性 设置为 false。这意味着探测请求将不会被
由该助手创建的 MAC 发送。
一旦 MAC 和 PHY 处的所有特定于站的参数均已完全配置
层,我们可以调用我们现在熟悉的 安装 创建这些wifi设备的方法
车站:
NetDeviceContainer staDevices;
staDevices = wifi.Install(phy, mac, wifiStaNodes);
我们已经为所有 STA 节点配置了 Wifi,现在我们需要配置 AP
(接入点)节点。我们通过更改默认值来开始此过程 Attributes 的
NqosWifiMac助手 以反映 AP 的要求。
mac.SetType("ns3::ApWifiMac",
“Ssid”,SsidValue(ssid));
在这种情况下, NqosWifiMac助手 将创建“ns3::ApWifiMac”的 MAC 层,
后者指定应创建配置为 AP 的 MAC 实例,其中
暗示“QosSupported”的帮助程序类型 属性 应设置为 false - 禁用
在创建的 AP 上支持 802.11e/WMM 风格的 QoS。
接下来的几行创建共享同一组 PHY 级别的单个 AP Attributes (和
频道)作为电台:
NetDeviceContainer apDevices;
apDevices = wifi.Install(phy, mac, wifiApNode);
现在,我们将添加移动模型。我们希望 STA 节点能够移动、徘徊
在边界框内,我们想让 AP 节点静止。我们使用
行动助手 让这对我们来说变得容易。首先,我们实例化一个 行动助手 对象
并设置一些 Attributes 控制“位置分配器”功能。
MobilityHelper 移动性;
mobility.SetPositionAllocator ("ns3::GridPositionAllocator",
“MinX”,双值(0.0),
“MinY”,双值(0.0),
“DeltaX”,双值(5.0),
"DeltaY", 双值 (10.0),
"GridWidth", UintegerValue (3),
"LayoutType", StringValue ("RowFirst"));
此代码告诉移动助手使用二维网格来最初放置
STA 节点。欢迎在课堂上探索 Doxygen ns3::GridPositionAllocator 看
到底正在做什么。
我们已经将节点排列在初始网格上,但现在我们需要告诉它们如何移动。
我们选择 RandomWalk2d 移动模型 其中节点以随机方向移动
边界框内的随机速度。
mobility.SetMobilityModel ("ns3::RandomWalk2dMobilityModel",
"边界", RectangleValue (矩形 (-50, 50, -50, 50)));
我们现在告诉 行动助手 在 STA 节点上安装移动模型。
移动性。安装(wifiStaNodes);
我们希望接入点在模拟过程中保持在固定位置。我们
通过将该节点的移动模型设置为
ns3::恒定位置移动模型:
mobility.SetMobilityModel ("ns3::ConstantPositionMobilityModel");
移动性。安装(wifiApNode);
我们现在已经创建了节点、设备和通道,并选择了移动模型
Wifi 节点,但我们没有协议栈。正如我们之前所做的许多
有时,我们将使用 互联网堆栈助手 安装这些堆栈。
InternetStackHelper堆栈;
stack.Install(csmaNodes);
堆栈.安装(wifiApNode);
stack.Install(wifiStaNodes);
正如在 第二抄送 示例脚本,我们将使用 ipv4地址助手 至
为我们的设备接口分配 IP 地址。首先我们使用网络10.1.1.0来创建
我们的两个点对点设备所需的两个地址。然后我们使用网络10.1.2.0
将地址分配给 CSMA 网络,然后我们从网络 10.1.3.0 分配地址
无线网络上的 STA 设备和 AP。
Ipv4AddressHelper地址;
地址.SetBase("10.1.1.0","255.255.255.0");
Ipv4InterfaceContainer p2pInterfaces;
p2pInterfaces = 地址.Assign (p2pDevices);
地址.SetBase("10.1.2.0","255.255.255.0");
Ipv4InterfaceContainer csmaInterfaces;
csmaInterfaces = 地址.Assign (csmaDevices);
地址.SetBase("10.1.3.0","255.255.255.0");
地址.分配(staDevices);
地址.分配(apDevices);
我们将 echo 服务器放在图中开头的“最右边”节点上
文件。我们以前已经这样做过。
UdpEchoServerHelper echoServer (9);
ApplicationContainer serverApps = echoServer.Install(csmaNodes.Get(nCsma));
serverApps.Start(秒(1.0));
serverApps.Stop(秒(10.0));
我们将 echo 客户端放在我们创建的最后一个 STA 节点上,将其指向服务器
CSMA网络。我们之前也见过类似的操作。
UdpEchoClientHelper echoClient(csmaInterfaces.GetAddress(nCsma), 9);
echoClient.SetAttribute("MaxPackets", UintegerValue(1));
echoClient.SetAttribute("间隔", TimeValue(秒(1.0)));
echoClient.SetAttribute("PacketSize", UintegerValue(1024));
应用程序容器 clientApps =
echoClient.Install(wifiStaNodes.Get(nWifi - 1));
clientApps.Start(秒(2.0));
clientApps.Stop(秒(10.0));
由于我们在这里建立了互联网络,因此我们需要启用互联网络路由,就像
我们在 第二抄送 示例脚本。
Ipv4GlobalRoutingHelper::PopulateRoutingTables ();
让一些用户感到惊讶的一件事是我们刚刚创建的模拟
永远不会“自然”停止。这是因为我们要求无线接入点
生成信标。它将永远生成信标,这将导致模拟器
事件被无限期地安排到未来,所以我们必须告诉模拟器停止
即使它可能安排了信标生成事件。下面这行代码
告诉模拟器停止,这样我们就不会永远模拟信标并输入什么
本质上是一个无限循环。
模拟器::停止(秒(10.0));
我们创建足够的跟踪来覆盖所有三个网络:
pointToPoint.EnablePcapAll("第三个");
phy.EnablePcap("第三个", apDevices.Get(0));
csma.EnablePcap ("第三个", csmaDevices.Get (0), true);
这三行代码将在两个点对点节点上启动 pcap 跟踪
作为我们的主干,将在 Wifi 网络上启动混杂(监控)模式跟踪,
并将在 CSMA 网络上启动混杂跟踪。这将使我们看到所有
具有最少数量跟踪文件的流量。
最后,我们实际运行模拟,清理然后退出程序。
模拟器::运行();
模拟器::销毁();
0返回;
}
为了运行这个例子,你必须复制 第三抄送 示例脚本到
scrap 目录并使用 Waf 进行构建,就像使用 第二抄送 例子。如果你
位于您要输入的存储库的顶级目录中,
$ cp 示例/教程/third.cc scrap/mythird.cc
$./waf
$ ./waf --运行 scrap/mythird
同样,由于我们已经像在中一样设置了 UDP echo 应用程序 第二抄送
脚本,您将看到类似的输出。
Waf:进入目录“/home/craigdo/repos/ns-3-allinone/ns-3-dev/build”
Waf:离开目录“/home/craigdo/repos/ns-3-allinone/ns-3-dev/build”
“构建”成功完成(0.407s)
2s时刻客户端发送1024字节到10.1.2.4端口9
在时间 2.01796s 服务器从 1024 端口 10.1.3.3 接收到 49153 字节
在时间2.01796s服务器发送1024字节到10.1.3.3端口49153
在时间 2.03364s 客户端从 1024 端口 10.1.2.4 接收到 9 字节
回想一下第一条消息, 发送次数 1024 字节 至 10.1.2.4,” 是 UDP 回显客户端
向服务器发送数据包。在这种情况下,客户端位于无线网络上
(10.1.3.0)。第二条消息“收到 1024 字节 , 10.1.3.3,”来自 UDP 回显
服务器接收到 echo 数据包时生成。最后的消息“收到 1024
字节 , 10.1.2.4,”来自echo客户端,表示已经收到它的echo
从服务器返回。
如果你现在去查看顶级目录,你会发现四个跟踪文件
在这个模拟中,两个来自节点 0,两个来自节点 1:
third-0-0.pcap third-0-1.pcap third-1-0.pcap third-1-1.pcap
文件“third-0-0.pcap”对应于节点零上的点对点设备——
“脊椎”的左侧。文件“third-1-0.pcap”对应点对点
节点一上的设备——“主干网”的右侧。文件“third-0-1.pcap”将是
来自 Wifi 网络的混杂(监控模式)跟踪和文件“third-1-1.pcap”
将是来自 CSMA 网络的混杂踪迹。您可以通过检查来验证这一点吗
代码?
由于 echo 客户端位于 Wifi 网络上,因此我们从这里开始。让我们看一下
我们在该网络上捕获的混杂(监视模式)跟踪。
$ tcpdump -nn -tt -r 第三个-0-1.pcap
您应该会看到一些您以前从未见过的类似 wifi 的内容:
从文件 Third-0-1.pcap 中读取,链接类型 IEEE802_11 (802.11)
0.000025 信标 (ns-3-ssid) [6.0* 9.0 12.0 18.0 24.0 36.0 48.0 54.0 Mbit] IBSS
0.000308 关联请求 (ns-3-ssid) [6.0 9.0 12.0 18.0 24.0 36.0 48.0 54.0 Mbit]
0.000324 Acknowledgment RA:00:00:00:00:00:08
0.000402 关联响应 AID(0):: 成功
0.000546 Acknowledgment RA:00:00:00:00:00:0a
0.000721 关联请求 (ns-3-ssid) [6.0 9.0 12.0 18.0 24.0 36.0 48.0 54.0 Mbit]
0.000737 Acknowledgment RA:00:00:00:00:00:07
0.000824 关联响应 AID(0):: 成功
0.000968 Acknowledgment RA:00:00:00:00:00:0a
0.001134 关联请求 (ns-3-ssid) [6.0 9.0 12.0 18.0 24.0 36.0 48.0 54.0 Mbit]
0.001150 Acknowledgment RA:00:00:00:00:00:09
0.001273 关联响应 AID(0):: 成功
0.001417 Acknowledgment RA:00:00:00:00:00:0a
0.102400 信标 (ns-3-ssid) [6.0* 9.0 12.0 18.0 24.0 36.0 48.0 54.0 Mbit] IBSS
0.204800 信标 (ns-3-ssid) [6.0* 9.0 12.0 18.0 24.0 36.0 48.0 54.0 Mbit] IBSS
0.307200 信标 (ns-3-ssid) [6.0* 9.0 12.0 18.0 24.0 36.0 48.0 54.0 Mbit] IBSS
您可以看到链接类型现在是 802.11,正如您所期望的那样。你或许可以
了解发生了什么并在其中找到 IP 回显请求和响应数据包
痕迹。我们将其作为完全解析跟踪转储的练习。
现在,查看点对点链接右侧的 pcap 文件,
$ tcpdump -nn -tt -r 第三个-0-0.pcap
同样,您应该会看到一些看起来熟悉的内容:
从文件 Third-0-0.pcap 中读取,链路类型 PPP (PPP)
2.008151 IP 10.1.3.3.49153 > 10.1.2.4.9:UDP,长度 1024
2.026758 IP 10.1.2.4.9 > 10.1.3.3.49153:UDP,长度 1024
这是从左到右(从 Wifi 到 CSMA)然后再次返回的回显数据包
点对点链接。
现在,查看点对点链接右侧的 pcap 文件,
$ tcpdump -nn -tt -r 第三个-1-0.pcap
同样,您应该会看到一些看起来熟悉的内容:
从文件 Third-1-0.pcap 中读取,链路类型 PPP (PPP)
2.011837 IP 10.1.3.3.49153 > 10.1.2.4.9:UDP,长度 1024
2.023072 IP 10.1.2.4.9 > 10.1.3.3.49153:UDP,长度 1024
这也是从左到右(从 Wifi 到 CSMA)然后再返回的回显数据包
正如您所期望的那样,通过点对点链路,时间略有不同。
回显服务器位于 CSMA 网络上,让我们看看那里的混杂跟踪:
$ tcpdump -nn -tt -r 第三个-1-1.pcap
您应该会看到一些熟悉的内容:
从文件third-1-1.pcap读取,链接类型EN10MB(以太网)
2.017837 ARP,请求who-has 10.1.2.4(ff:ff:ff:ff:ff:ff)告诉10.1.2.1,长度50
2.017861 ARP,回复 10.1.2.4 is-at 00:00:00:00:00:06,长度 50
2.017861 IP 10.1.3.3.49153 > 10.1.2.4.9:UDP,长度 1024
2.022966 ARP,请求who-has 10.1.2.1(ff:ff:ff:ff:ff:ff)告诉10.1.2.4,长度50
2.022966 ARP,回复 10.1.2.1 is-at 00:00:00:00:00:03,长度 50
2.023072 IP 10.1.2.4.9 > 10.1.3.3.49153:UDP,长度 1024
这应该很容易理解。如果你忘记了,请返回查看讨论
in 第二抄送。这是相同的顺序。
现在,我们花了很多时间为无线网络设置移动模型,所以它
如果不显示 STA 节点实际上正在移动就结束了,那将是一种耻辱
模拟过程中周围。让我们通过挂钩来做到这一点 移动模型 与MYP课程衔接
更改跟踪源。这只是详细跟踪部分的一瞥
即将到来,但这似乎是一个非常好的例子。
正如“调整 ns-3”部分中提到的, ns-3 追踪系统分为trace
源和跟踪接收器,我们提供连接两者的函数。我们将使用
移动模型预定义路线更改跟踪源以发起跟踪事件。我们
需要编写一个跟踪接收器来连接到该源,该源将显示一些漂亮的内容
为我们提供的信息。尽管它被认为很难,但它实际上非常简单。
就在主程序之前 暂存/mythird.cc 脚本(即,紧接在
NS_LOG_COMPONENT_DEFINE 语句),添加以下函数:
无效
CourseChange(std::字符串上下文,Ptr模型)
{
向量位置 = model->GetPosition();
NS_LOG_UNCOND(上下文 <
" x = " << 位置.x << ", y = " << 位置.y);
}
该代码只是从移动模型中无条件地提取位置信息
记录节点的 x 和 y 位置。我们将安排这个功能
每当带有回显客户端的无线节点改变其位置时调用。我们这样做
使用 配置::连接 功能。只需将以下几行代码添加到脚本中
前 模拟器::运行 呼叫。
std::ostringstream oss;
操作系统 <
"/NodeList/" << wifiStaNodes.Get (nWifi - 1)->GetId () <
“/$ns3::MobilityModel/CourseChange”;
Config::Connect(oss.str(),MakeCallback(&CourseChange));
我们在这里所做的是创建一个包含事件的跟踪命名空间路径的字符串
我们想要连接到的。首先,我们必须弄清楚我们想要使用哪个节点
这些因素包括原料奶的可用性以及达到必要粉末质量水平所需的工艺。 获取ID 方法如前所述。在CSMA默认数量的情况下
无线节点,这原来是节点 7 以及到
移动模型看起来像,
/NodeList/7/$ns3::MobilityModel/CourseChange
根据跟踪部分的讨论,您可能会推断出该跟踪路径
引用全局 NodeList 中的第七个节点。它指定了所谓的
类型的聚合对象 ns3::移动模型。美元符号前缀意味着
MobilityModel 聚合到节点七。路径的最后一个组成部分意味着我们
正在挂接到该模型的“CourseChange”事件。
我们通过调用在节点 7 中的跟踪源与跟踪接收器之间建立连接
配置::连接 并传递此名称空间路径。一旦完成,每个课程都会改变
节点 7 上的事件将被挂接到我们的跟踪接收器中,跟踪接收器将依次打印出
新的位置。
如果您现在运行模拟,您将看到显示的路线更改。
“构建”成功完成(5.989s)
/NodeList/7/$ns3::MobilityModel/CourseChange x = 10,y = 0
/NodeList/7/$ns3::MobilityModel/CourseChange x = 10.3841,y = 0.923277
/NodeList/7/$ns3::MobilityModel/CourseChange x = 10.2049,y = 1.90708
/NodeList/7/$ns3::MobilityModel/CourseChange x = 10.8136,y = 1.11368
/NodeList/7/$ns3::MobilityModel/CourseChange x = 10.8452,y = 2.11318
/NodeList/7/$ns3::MobilityModel/CourseChange x = 10.9797,y = 3.10409
2s时刻客户端发送1024字节到10.1.2.4端口9
在时间 2.01796s 服务器从 1024 端口 10.1.3.3 接收到 49153 字节
在时间2.01796s服务器发送1024字节到10.1.3.3端口49153
在时间 2.03364s 客户端从 1024 端口 10.1.2.4 接收到 9 字节
/NodeList/7/$ns3::MobilityModel/CourseChange x = 11.3273,y = 4.04175
/NodeList/7/$ns3::MobilityModel/CourseChange x = 12.013,y = 4.76955
/NodeList/7/$ns3::MobilityModel/CourseChange x = 12.4317,y = 5.67771
/NodeList/7/$ns3::MobilityModel/CourseChange x = 11.4607,y = 5.91681
/NodeList/7/$ns3::MobilityModel/CourseChange x = 12.0155,y = 6.74878
/NodeList/7/$ns3::MobilityModel/CourseChange x = 13.0076,y = 6.62336
/NodeList/7/$ns3::MobilityModel/CourseChange x = 12.6285,y = 5.698
/NodeList/7/$ns3::MobilityModel/CourseChange x = 13.32,y = 4.97559
/NodeList/7/$ns3::MobilityModel/CourseChange x = 13.1134,y = 3.99715
/NodeList/7/$ns3::MobilityModel/CourseChange x = 13.8359,y = 4.68851
/NodeList/7/$ns3::MobilityModel/CourseChange x = 13.5953,y = 3.71789
/NodeList/7/$ns3::MobilityModel/CourseChange x = 12.7595,y = 4.26688
/NodeList/7/$ns3::MobilityModel/CourseChange x = 11.7629,y = 4.34913
/NodeList/7/$ns3::MobilityModel/CourseChange x = 11.2292,y = 5.19485
/NodeList/7/$ns3::MobilityModel/CourseChange x = 10.2344,y = 5.09394
/NodeList/7/$ns3::MobilityModel/CourseChange x = 9.3601,y = 4.60846
/NodeList/7/$ns3::MobilityModel/CourseChange x = 8.40025,y = 4.32795
/NodeList/7/$ns3::MobilityModel/CourseChange x = 9.14292,y = 4.99761
/NodeList/7/$ns3::MobilityModel/CourseChange x = 9.08299,y = 5.99581
/NodeList/7/$ns3::MobilityModel/CourseChange x = 8.26068,y = 5.42677
/NodeList/7/$ns3::MobilityModel/CourseChange x = 8.35917,y = 6.42191
/NodeList/7/$ns3::MobilityModel/CourseChange x = 7.66805,y = 7.14466
/NodeList/7/$ns3::MobilityModel/CourseChange x = 6.71414,y = 6.84456
/NodeList/7/$ns3::MobilityModel/CourseChange x = 6.42489,y = 7.80181
示踪
背景
正如UsingTracingSystem中提到的,运行跟踪的全部意义在于 ns-3 模拟是为了
生成用于研究的输出。您有两种基本策略来获取输出 ns-3:
使用通用的预定义批量输出机制并解析其内容以提取
有趣的信息;或者以某种方式开发一种输出机制来准确传达
(也许只是)想要的信息。
使用预定义的批量输出机制的优点是不需要对
ns-3,但可能需要编写脚本来解析和过滤感兴趣的数据。经常,
投射式电容或 NS_LOG 输出消息在模拟运行期间收集并单独运行
通过使用的脚本 grep的, 口渴 or AWK 解析消息并减少和转换
将数据转换为可管理的形式。必须编写程序来进行转换,所以这
不是免费的。 NS_LOG 输出不被视为一部分 ns-3 API,并且可以
在版本之间进行更改而不发出警告。此外, NS_LOG 输出仅适用于
调试版本,因此依赖它会带来性能损失。当然,如果
任何预定义的输出机制中都不存在感兴趣的信息,这
方法失败。
如果您需要向预定义的批量机制添加一些信息,这可以
一定会完成;如果你使用其中之一 ns-3 机制,你可能会添加你的代码
作为贡献。
ns-3 提供了另一种机制,称为跟踪,可以避免一些固有的问题
在批量输出机制中。它有几个重要的优点。首先,你可以
仅跟踪您感兴趣的事件,减少您需要管理的数据量
(对于大型模拟,将所有内容转储到磁盘进行后处理可以创建 I/O
瓶颈)。其次,如果使用这种方法,可以控制输出的格式
直接这样你就可以避免后处理步骤 口渴, AWK, perl的 or 蟒蛇 脚本。如果
如果您愿意,您的输出可以直接格式化为 gnuplot 可接受的形式,例如
示例(另请参见 GnuplotHelper)。您可以在核心中添加挂钩,然后可以
被其他用户访问,但除非明确要求,否则不会产生任何信息
这样做。出于这些原因,我们认为 ns-3 追踪系统是获取信息的最佳途径
来自模拟的信息,因此也是最重要的机制之一
了解在 ns-3.
钝 仪器
有很多方法可以从程序中获取信息。 最直接的方法是
将信息直接打印到标准输出,如下所示:
#包括
...
无效
一些函数(空)
{
uint32_t x = SOME_INTERESTING_VALUE;
...
std::cout << "x 的值为" << x << std::endl;
...
}
没有人会阻止你深入到核心 ns-3 并添加打印
声明。这是非常容易做到的,毕竟你可以完全控制你的
己 ns-3 分支。从长远来看,这可能不会让人非常满意
不过,术语。
随着程序中打印语句数量的增加,处理打印语句的任务
大量的输出会变得越来越复杂。最终,你可能会觉得
需要以某种方式控制正在打印的信息,也许通过打开
并关闭某些类别的打印,或增加或减少数量
您想要的信息。如果你继续沿着这条路走下去,你可能会发现你已经
重新实施了 NS_LOG 机制(请参阅使用日志记录)。为了避免这种情况,其中之一
您可能首先考虑的是使用 NS_LOG 本身。
我们上面提到了一种获取信息的方法 ns-3 就是解析现有的 NS_LOG
输出有趣的信息。如果您发现一些信息
现有日志输出中不存在需要,您可以编辑以下内容的核心 ns-3 并简单地添加
将您感兴趣的信息输出到输出流。现在,这肯定比
添加您自己的打印语句,因为它如下 ns-3 编码约定并且可以
作为现有核心的补丁可能对其他人有用。
让我们随机选择一个例子。如果您想添加更多日志记录 ns-3 TCP套接字
(tcp-socket-base.cc)您可以在实现中添加一条新消息。注意
在 TcpSocketBase::ReceivedAck() 对于无 ACK 情况,没有日志消息。你
只需添加一个,更改代码即可。这是原文:
/** 处理新收到的ACK */
无效
TcpSocketBase::ReceivedAck (Ptr数据包,常量 TcpHeader& tcpHeader)
{
NS_LOG_FUNCTION(这个<< tcpHeader);
// 收到ACK。将 ACK 编号与最高未确认 seqno 进行比较
if (0 == (tcpHeader.GetFlags () & TcpHeader::ACK))
{ // 如果没有 ACK 标志则忽略
}
...
要记录无 ACK 情况,您可以添加新的 NS_LOG_LOGIC ,在 if 声明正文:
/** 处理新收到的ACK */
无效
TcpSocketBase::ReceivedAck (Ptr数据包,常量 TcpHeader& tcpHeader)
{
NS_LOG_FUNCTION(这个<< tcpHeader);
// 收到ACK。将 ACK 编号与最高未确认 seqno 进行比较
if (0 == (tcpHeader.GetFlags () & TcpHeader::ACK))
{ // 如果没有 ACK 标志则忽略
NS_LOG_LOGIC("TcpSocketBase" << this << "无ACK标志");
}
...
乍一看,这似乎相当简单且令人满意,但需要考虑的是
您将编写代码来添加 NS_LOG 陈述,你还必须写
代码(如 grep的, 口渴 or AWK 脚本)来解析日志输出以隔离您的
信息。这是因为即使您对输出的内容有一定的控制
日志系统,您只能控制到日志组件级别,这通常是
整个源代码文件。
如果您要向现有模块添加代码,您还必须接受输出
其他所有开发人员都觉得有趣。您可能会发现,为了获得
您需要的信息量很少,您可能需要费力地浏览大量信息
您不感兴趣的无关消息。您可能被迫保存大量日志
将文件写入磁盘,并在您想做任何事情时将其处理到几行。
由于没有任何保证 ns-3 关于稳定性 NS_LOG 输出,你也可以
发现您所依赖的日志输出片段消失或发生变化
发布。如果您依赖于输出的结构,您可能会发现其他消息
添加或删除可能会影响您的解析代码。
最后, NS_LOG 输出仅在调试版本中可用,您无法从中获取日志输出
优化的构建,运行速度大约是原来的两倍。依靠 NS_LOG 强加表演
罚款。
出于这些原因,我们认为打印 标准::cout 和 NS_LOG 消息要快速且
获取更多信息的肮脏方法 ns-3,但不适合严肃的工作。
最好有一个使用稳定 API 的稳定设施,使人们能够访问
核心系统并只获取所需的信息。希望能够做到
这无需更改和重新编译核心系统。更好的是
当感兴趣的项目发生变化或有趣的事件时通知用户代码的系统
发生这样用户就不必主动在系统中寻找
的东西。
这个 ns-3 跟踪系统旨在沿着这些路线工作,并与
属性和 配置 子系统允许相对简单的使用场景。
概述
这个 ns-3 溯源系统建立在独立溯源的概念之上,
跟踪接收器,以及将源连接到接收器的统一机制。
跟踪源是可以指示模拟中发生的事件并提供
访问有趣的基础数据。 例如,跟踪源可以指示何时
数据包由网络设备接收并提供对数据包内容的访问
感兴趣的跟踪汇。 跟踪源也可能指示感兴趣的状态
变化发生在模型中。 例如,TCP 模型的拥塞窗口是素数
跟踪源的候选者。每次拥塞窗口改变连接轨迹
接收器会收到旧值和新值的通知。
跟踪源本身没有用; 它们必须连接到其他代码段
这实际上对来源提供的信息做了一些有用的事情。 这
使用跟踪信息的实体称为跟踪接收器。 跟踪源是
数据和跟踪接收器的生成者是消费者。这种明确的划分允许大
分散在模型作者所在系统各处的跟踪源数量
相信可能有用。插入跟踪源会引入非常小的执行
高架。
跟踪源生成的跟踪事件可以有零个或多个消费者。 一罐
将跟踪源视为一种点对多点的信息链接。你的代码
从特定的核心代码片段中查找跟踪事件可以与以下内容愉快地共存:
其他代码执行与相同信息完全不同的操作。
除非用户将跟踪接收器连接到这些源之一,否则不会输出任何内容。通过使用
在跟踪系统中,您和其他连接到同一跟踪源的人都会得到
正是他们想要的,也只是他们想要从系统中得到的。你们都不是
通过更改系统输出的信息来影响任何其他用户。如果你
碰巧添加了一个跟踪源,你作为一个优秀的开源公民的工作可能会让其他人
用户提供新的实用程序,这些实用程序总体上可能非常有用,而无需做任何
改变了 ns-3 核心。
简易 例如:
让我们花几分钟时间来演练一个简单的跟踪示例。我们将需要
有关回调的一些背景知识,以了解示例中发生的情况,因此我们
必须马上绕一小段路。
回呼
回调系统的目标 ns-3 就是允许一段代码调用一个函数
(或 C++ 中的方法),没有任何特定的模块间依赖。这最终意味着
你需要某种间接——你将被调用函数的地址视为
多变的。该变量称为函数指针变量。关系
函数和函数指针之间的区别实际上与对象和对象之间的区别没有什么不同
指向对象的指针。
在 C 中,函数指针的典型示例是
返回整数函数的指针 (PFI)。对于 PFI 采取一 INT 参数,这个
可以这样声明,
int (*pfi)(int arg) = 0;
(但是请阅读 C++-常见问题解答 部分 33 在编写这样的代码之前!)你从中得到什么
是一个简单命名的变量 PFI 被初始化为值 0。如果你想
将这个指针初始化为有意义的东西,你必须有一个带有
匹配的签名。在这种情况下,您可以提供一个如下所示的函数:
int MyFunction (int arg) {}
如果你有这个目标,你可以初始化变量以指向你的函数:
pfi = 我的功能;
然后,您可以使用更具暗示性的调用形式间接调用 MyFunction:
int 结果 = (*pfi) (1234);
这是暗示性的,因为看起来您只是在取消引用函数指针
就像你会取消引用任何指针一样。 然而,通常情况下,人们会利用
事实上编译器知道发生了什么并且只会使用更短的形式:
int 结果 = pfi (1234);
这看起来像是您正在调用一个名为的函数 PFI,但是编译器足够聪明
知道通过变量来调用 PFI 间接到函数 我的功能.
从概念上讲,这几乎就是跟踪系统的工作原理。基本上有迹可循
水槽 is 回调。当跟踪接收器表示有兴趣接收跟踪事件时,它
将其自身作为回调添加到跟踪源内部保存的回调列表中。
当有趣的事件发生时,跟踪源会调用它的 操作员(...) 优
零个或多个参数。这 操作员(...) 最终进入系统并
做的事情与您刚刚看到的间接调用非常相似,提供零个或多个
参数,就像调用 PFI 上面向目标函数传递了一个参数
我的功能.
跟踪系统添加的重要区别是,对于每个跟踪源
是回调的内部列表。不是仅仅进行一次间接调用,而是进行跟踪
源可以调用多个回调。当跟踪接收器表示感兴趣时
来自跟踪源的通知,它基本上只是安排将自己的功能添加到
回调列表。
如果您对有关实际安排方式的更多详细信息感兴趣 ns-3,感觉
免费阅读回调部分 ns-3 手册。
演练: 第四.cc
我们提供了一些代码来实现最简单的跟踪示例
可以组装的。您可以在教程目录中找到此代码,如下所示 第四.cc.
让我们来看看:
/* -*- 模式:C++; c-文件样式:“gnu”; 缩进制表符模式:nil; -*- */
/*
* 本程序是免费软件; 您可以重新分发它和/或修改
* 根据 GNU 通用公共许可证第 2 版的条款
* 由自由软件基金会出版;
*
* 该程序发布是为了希望它有用,
* 但不提供任何保证; 甚至没有默示保证
* 适销性或特定用途的适用性。 请参阅
* GNU 通用公共许可证了解更多详细信息。
*
* 您应该已收到 GNU 通用公共许可证的副本
* 与此程序一起使用; 如果没有,请写信给自由软件
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 美国
*/
#include "ns3/object.h"
#include "ns3/uinteger.h"
#include“ns3/traced-value.h”
#include "ns3/trace-source-accessor.h"
#包括
使用命名空间 ns3;
您应该对这段代码的大部分内容非常熟悉。如上所述,跟踪系统
大量使用对象和属性系统,因此您需要包含它们。
前两个包括上面明确引入这些系统的声明。你
可以使用核心模块头一次性获取所有内容,但我们执行包含
在这里明确地说明这一切实际上是多么简单。
文件, 跟踪值.h 引入跟踪数据所需的声明
遵守值语义。一般来说,值语义只是意味着你可以传递
对象本身,而不是传递对象的地址。这一切到底是什么
意味着您将能够以真正的方式跟踪对 TracedValue 所做的所有更改
简单的方法。
由于跟踪系统与 Attributes 集成在一起,而 Attributes 与 Objects 一起工作,
必须有一个 ns-3 摆件 供跟踪源驻留在其中。下一个代码片段
声明并定义了一个我们可以使用的简单对象。
MyObject 类:公共对象
{
市民:
静态 TypeId GetTypeId (void)
{
静态 TypeId tid = TypeId ("MyObject")
.SetParent (对象::GetTypeId ())
.AddConstructor ()
.AddTraceSource ("MyInteger",
"要跟踪的整数值。",
MakeTraceSourceAccessor (&MyObject::m_myInt),
“ns3::Traced::Value::Int32Callback”)
;
返回时间;
}
我的对象(){}
追踪值m_myInt;
};
上面关于跟踪的两行重要代码是 .AddTraceSource
和 跟踪值 声明 m_myInt.
这个 .AddTraceSource 提供用于将跟踪源连接到
通过Config系统与外界接触。第一个参数是该跟踪的名称
源,这使得它在配置系统中可见。第二个参数是帮助字符串。
现在看第三个说法,其实重点在于 论点 第三个参数:
&MyObject::m_myInt。这是要添加到类中的 TracedValue;这是
始终是类数据成员。 (最后一个参数是 类型定义 等加工。为
TracedValue 类型,作为字符串。这用于生成正确的文档
回调函数签名,这对于更通用的类型特别有用
回调。)
这个 追踪值<> 声明提供了驱动回调的基础设施
过程。每当基础值发生变化时,TracedValue 机制都会提供
该变量的旧值和新值,在本例中是 int32_t 价值。踪迹
此 TracedValue 的接收器函数将需要签名
void (* TracedValueCallback)(const int32_t oldValue, const int32_t newValue);
挂钩此跟踪源的所有跟踪接收器都必须具有此签名。我们下面将讨论
在其他情况下如何确定所需的回调签名。
果然,继续通过 第四.cc 我们看:
无效
IntTrace(int32_t旧值,int32_t新值)
{
std::cout << "追踪 " << oldValue << " 到 " << newValue << std::endl;
}
这是匹配跟踪接收器的定义。它直接对应回调
函数签名。连接后,每当连接时都会调用此函数
跟踪值 的变化。
我们现在已经看到了跟踪源和跟踪接收器。剩下的就是连接的代码
源到接收器,这发生在 主:
INT
主要(int argc,char *argv[])
{
点我的对象 = 创建对象();
myObject->TraceConnectWithoutContext ("MyInteger", MakeCallback(&IntTrace));
我的对象->m_myInt = 1234;
}
在这里,我们首先创建跟踪源所在的 MyObject 实例。
下一步,该 TraceConnectWithoutContext, 形成迹线之间的连接
源和跟踪汇。第一个参数只是跟踪源名称“MyInteger”
我们在上面看到了。注意 回调 模板功能。这个函数有神奇的作用
创建底层所需的 ns-3 回调对象并将其与函数关联
内部跟踪. 跟踪连接 使您提供的功能与
超载 操作员() 在“MyInteger”属性引用的跟踪变量中。
建立此关联后,跟踪源将“触发”您提供的回调
功能。
当然,实现这一切的代码并不简单,但本质是
你正在安排一些看起来就像 pfi() 上面的例子被称为
通过追踪来源。的声明 追踪值 m_myInt; 在对象中
它本身执行提供重载赋值运算符所需的魔法,这将
使用 操作员() 使用所需参数实际调用回调。这
.AddTraceSource 执行将回调连接到配置系统的魔法,并且
TraceConnectWithoutContext 发挥魔法将您的函数连接到跟踪
源,由属性名称指定。
现在让我们忽略有关上下文的部分。
最后,该行赋值给 m_myInt:
我的对象->m_myInt = 1234;
应该被解释为调用 运算符= 在成员变量上 m_myInt -
整数 1234 作为参数传递。
建立 m_myInt 是一个 跟踪值,该运算符被定义为执行回调
返回 void 并接受两个整数值作为参数 --- 一个旧值和一个新值
对于有问题的整数。这正是回调的函数签名
我们提供的功能--- 内部跟踪.
总而言之,跟踪源本质上是一个保存回调列表的变量。 一种
trace sink 是一个用作回调目标的函数。 属性和对象类型
信息系统用于提供一种将跟踪源连接到跟踪接收器的方法。
“击中”跟踪源的行为是在跟踪源上执行一个操作符,该操作符
触发回调。这会导致跟踪接收器回调谁注册了兴趣
使用源提供的参数调用源。
如果您现在构建并运行这个示例,
$ ./waf --运行第四次
你会看到输出 内部跟踪 一旦跟踪源出现,函数就会执行
打:
追踪 0 到 1234
当我们执行代码时, myObject->m_myInt = 1234;,跟踪源被触发并且
自动向跟踪接收器提供之前和之后的值。功能
内部跟踪 然后将其打印到标准输出。
连接 - 配置
这个 TraceConnectWithoutContext 上面显示的简单示例中的调用实际上非常
系统中很少使用。 更典型的是, 配置 子系统用于选择轨迹
系统中的源使用所谓的 配置 径。我们在中看到了一个这样的例子
上一节我们在试验时挂钩了“CourseChange”事件
第三抄送.
回想一下,我们定义了一个跟踪接收器来打印来自移动性的路线变化信息
我们的模拟模型。现在你应该更清楚这个函数是什么了
这样做的:
无效
CourseChange(std::字符串上下文,Ptr模型)
{
向量位置 = model->GetPosition();
NS_LOG_UNCOND(上下文 <
" x = " << 位置.x << ", y = " << 位置.y);
}
当我们将“CourseChange”跟踪源连接到上面的跟踪接收器时,我们使用了
当我们在预定义之间安排连接时指定源的配置路径
跟踪源和新的跟踪接收器:
std::ostringstream oss;
oss <<“/NodeList/”
<< wifiStaNodes.Get (nWifi - 1)->GetId ()
<< "/$ns3::MobilityModel/CourseChange";
Config::Connect(oss.str(),MakeCallback(&CourseChange));
让我们尝试理解有时被认为相对神秘的代码。
出于讨论的目的,假设返回的节点号 获取Id() is
“7”。在这种情况下,上面的路径就是
“/NodeList/7/$ns3::MobilityModel/CourseChange”
配置路径的最后一段必须是 属性 一个 摆件。事实上,如果你有
一个指向 摆件 有“CourseChange”的 属性 方便,你可以写这个
就像我们在前面的示例中所做的那样。您现在知道我们通常存储
指向我们的 Nodes 在 NodeContainer 中。在里面 第三抄送 例如,感兴趣的节点
存储在 wifi StaNodes 节点容器。事实上,在整理路径时,
我们用这个容器得到了 点 我们以前称之为 获取Id()。我们可以有
用过这个 点 直接调用 Connect 方法:
指针theObject = wifiStaNodes.Get(nWifi - 1);
theObject->TraceConnectWithoutContext("CourseChange", MakeCallback(&CourseChange));
在 第三抄送 例如,我们实际上想要一个额外的“上下文”
与回调参数(将在下面解释),所以我们实际上可以使用
以下等效代码:
指针theObject = wifiStaNodes.Get(nWifi - 1);
theObject->TraceConnect("CourseChange", MakeCallback(&CourseChange));
事实证明,内部代码为 配置::ConnectWithoutContext 和 配置::连接
实际上找到一个 指针 并致电相应的 跟踪连接 最低的方法
水平。
这个 配置 函数采用代表链的路径 摆件 指针。各段
路径的一个对应于一个对象属性。最后一段是属性
兴趣,并且必须键入先前的段才能包含或查找对象。这 配置 码
解析并“行走”这条路径,直到到达路径的最后一段。那么它
将最后一段解释为 属性 在它行走时发现的最后一个物体上
小路。 这 配置 函数然后调用适当的 跟踪连接 or
TraceConnectWithoutContext 最终对象上的方法。让我们看看稍后会发生什么
当走上述路径时会得到更多细节。
路径中的前导“/”字符指的是所谓的命名空间。 中的一个
配置系统中预定义的命名空间是“NodeList”,它是所有
模拟中的节点。 列表中的项目由列表中的索引引用,因此
“/NodeList/7”指的是模拟过程中创建的节点列表中的第八个节点
(回忆索引开始于 0')。 本篇 参考 is 通 a ``指针` 也是如此
的子类 ns3::对象.
如对象模型部分所述 ns-3 手册,我们广泛使用
对象聚合。这允许我们在不同的对象之间形成关联
无需构建复杂的继承树或预先决定哪些对象将成为一部分
一个节点的。聚合中的每个对象都可以从其他对象访问。
在我们的示例中,正在行走的下一个路径段以“$”字符开始。这
向配置系统表明该段是对象类型的名称,因此
获取对象 应该调用寻找该类型。事实证明, 行动助手
所用 第三抄送 安排将移动模型聚合或关联到每个
无线 Nodes。当您添加“$”时,您正在要求另一个具有以下功能的对象:
大概是之前聚合的。您可以将其视为从以下位置切换指针
原始指针由“/NodeList/7”指定为其关联的移动模型 ---
这是类型 ns3::移动模型. 如果你熟悉 获取对象,我们已经问过
系统执行以下操作:
指针mobilityModel = 节点->GetObject ()
我们现在位于路径中的最后一个对象,因此我们将注意力转向
那个对象。这 移动模型 类定义了一个名为“CourseChange”的属性。你可以
通过查看源代码可以看到这一点 src/mobility/model/mobility-model.cc 和
在您最喜欢的编辑器中搜索“CourseChange”。你应该找到
.AddTraceSource(“课程更改”,
“位置和/或速度矢量的值发生了变化”,
MakeTraceSourceAccessor (&MobilityModel::m_courseChangeTrace),
“ns3::MobilityModel::CourseChangeCallback”)
此时看起来应该非常熟悉。
如果您在中查找底层跟踪变量的相应声明
移动模型.h 你会发现
追踪回调 > m_courseChangeTrace;
类型声明 追踪回调 识别 m_courseChangeTrace 作为一个特殊的清单
可以使用上述配置函数挂钩的回调。这 类型定义 HPMC胶囊
回调函数签名也在头文件中定义:
typedef void (* CourseChangeCallback)(Ptr) * 模型);
这个 移动模型 类被设计为基类,提供通用接口
所有特定的子类。如果你搜索到文件末尾,你会看到一个
定义的方法称为 通知课程更改():
无效
MobilityModel::NotifyCourseChange (void) const
{
m_courseChangeTrace(这个);
}
每当派生类进行课程更改以支持时,都会调用此方法
追踪。该方法调用 操作员() 在底层的 m_courseChangeTrace,这
反过来,将调用所有已注册的回调,调用所有跟踪接收器
通过调用 Config 函数已注册对跟踪源的兴趣。
所以,在 第三抄送 我们看到的例子是,每当其中一个课程发生变化时
RandomWalk2d 移动模型 安装实例后,会有一个 通知课程更改() 呼叫
这会调用 移动模型 基类。如上所示,这会调用 操作员()
on m_courseChangeTrace,这又调用任何已注册的跟踪接收器。在示例中,
唯一引起兴趣的代码是提供配置路径的代码。
所以, 课程变更 从节点七挂钩的函数将是
仅回调被调用。
拼图的最后一块是“上下文”。回想一下我们看到的输出看起来
类似于以下内容的 第三抄送:
/NodeList/7/$ns3::MobilityModel/CourseChange x = 7.27897,y =
2.22677
输出的第一部分是上下文。这只是一条路径,
配置代码定位了跟踪源。在我们一直在研究的情况下,可以有
系统中任意数量的跟踪源对应于任意数量的节点
移动模型。需要有某种方法来识别哪个跟踪源实际上是
触发回调的那个。最简单的方法是连接 配置::连接, 反而
of 配置::ConnectWithoutContext.
查找 来源
追踪系统的新用户不可避免地会遇到的第一个问题是, “好的,
I 知道 这 那里 必须 be 追踪 来源 in 这些因素包括原料奶的可用性以及达到必要粉末质量水平所需的工艺。 模拟 核心, 但是 形成一种 do I 发现 什么
追踪 来源 旨在 可使用 至 我?”
第二个问题是, “好的, I 发现 a 追踪 资源, 形成一种 do I 数字 这些因素包括原料奶的可用性以及达到必要粉末质量水平所需的工艺。 配置 径
至 使用 ,尤其是 I 联接 至 它?”
第三个问题是, “好的, I 发现 a 追踪 资源 和 这些因素包括原料奶的可用性以及达到必要粉末质量水平所需的工艺。 配置 路径, 形成一种 do I 数字
什么 这些因素包括原料奶的可用性以及达到必要粉末质量水平所需的工艺。 回报 类型 和 正式 参数 of my 回电话 function 需要 至 是?”
第四个问题是, “好的, I 类型 这 所有 in 和 得到了 Free Introduction 令人难以置信 奇异的 错误
信息, 什么 in 这些因素包括原料奶的可用性以及达到必要粉末质量水平所需的工艺。 世界 不 it 吝啬的?”
我们将依次解决这些问题。
可提供 来源
好吧, I 知道 这 那里 必须 be 追踪 来源 in 这些因素包括原料奶的可用性以及达到必要粉末质量水平所需的工艺。 模拟 核心, 但是 形成一种 do I 发现
什么 追踪 来源 旨在 可使用 至 我?
第一个问题的答案可以在 ns-3 API 文档。如果你去
项目网站, ns-3 项目,您将在导航中找到“文档”的链接
酒吧。如果您选择此链接,您将进入我们的文档页面。有一个
链接到“最新版本”,它将带您访问最新稳定版本的文档
释放 ns-3。如果您选择“API 文档”链接,您将进入
ns-3 API 文档页面。
在侧边栏中,您应该看到一个层次结构,从
· ns-3
· ns-3 文档
· 所有追踪来源
· 所有属性
· 所有全球价值观
我们感兴趣的列表是“All TraceSources”。继续并选择该链接。
您将看到所有可用跟踪源的列表,这或许并不令人意外
in ns-3.
例如,向下滚动到 ns3::移动模型。您将找到一个条目
CourseChange:位置和/或速度矢量的值已更改
您应该认识到这是我们在 第三抄送 例子。细读
这个列表会很有帮助。
配置 路径
好吧, I 发现 a 追踪 资源, 形成一种 do I 数字 这些因素包括原料奶的可用性以及达到必要粉末质量水平所需的工艺。 配置 径 至 使用 ,尤其是 I 联接 至
它?
如果您知道您对哪个对象感兴趣,则可以查看该对象的“详细描述”部分
类将列出所有可用的跟踪源。例如,从“全部”列表开始
TraceSources”,单击 ns3::移动模型 链接,它将带您到
的文档 移动模型 班级。几乎在页面顶部有一行
类的简短描述,以链接“更多...”结尾。点击此链接可跳过
API摘要并转到该类的“详细说明”。结束时
描述将是(最多)三个列表:
· 配置 路径:此类的典型配置路径列表。
· Attributes:该类提供的所有属性的列表。
· 追踪来源:此类中可用的所有 TraceSource 的列表。
首先我们将讨论配置路径。
假设您刚刚在“All”中找到“CourseChange”跟踪源
TraceSources”列表,并且您想弄清楚如何连接到它。您知道您是
使用(再次,从 第三抄送 示例)一个 ns3::RandomWalk2dMobilityModel。所以要么
点击“All TraceSources”列表中的类名,或者查找
ns3::RandomWalk2dMobilityModel 在“班级列表”中。不管怎样,你现在应该寻找
在“ns3::RandomWalk2dMobilityModel 类参考”页面。
如果您现在向下滚动到“详细说明”部分,在摘要列表之后
类方法和属性(或者只需单击类末尾的“更多...”链接
页面顶部的简要说明)您将看到该项目的整体文档
班级。继续向下滚动,找到“配置路径”列表:
配置 路径
ns3::RandomWalk2dMobilityModel 可以通过以下路径访问
配置::设置 和 配置::连接:
·“/NodeList/[i]/$ns3::MobilityModel/$ns3::RandomWalk2dMobilityModel”
该文档告诉您如何访问 RandomWalk2d 移动模型 目的。比较
上面的字符串与我们在示例代码中实际使用的字符串:
“/NodeList/7/$ns3::MobilityModel”
差异是由于以下事实:两个 获取对象 调用隐含在找到的字符串中
在文档中。第一个,对于 $ns3::移动模型 将查询聚合
基类。第二个暗示 获取对象 呼吁,为了 $ns3::RandomWalk2dMobilityModel,
用于将基类转换为具体实现类。文档
为您展示这两项操作。原来你的实际trace源是
寻找是在基类中找到的。
在“详细描述”部分中进一步查看跟踪源列表。
你会发现
没有为此类型定义 TraceSource。
追踪来源 定义 in 亲 程 ``ns3::移动模型``
· 课程变更:位置和/或速度矢量的值已更改。
回调签名: ns3::MobilityModel::CourseChangeCallback
这正是您需要了解的。感兴趣的跟踪源位于
ns3::移动模型 (无论如何你都知道)。有趣的是这个 API
文档告诉您,您不需要在上面的配置路径中进行额外的转换
到达具体类,因为跟踪源实际上位于基类中。
因此额外的 获取对象 不是必需的,您只需使用路径:
“/NodeList/[i]/$ns3::MobilityModel”
这与示例路径完全匹配:
“/NodeList/7/$ns3::MobilityModel”
顺便说一句,找到配置路径的另一种方法是 grep的 在附近 ns-3 代码库
对于已经弄清楚的人。你应该总是尝试复制别人的
在开始编写自己的代码之前先工作代码。尝试类似的方法:
$ 查找 . -名称 '*.cc' | xargs grep 课程更改 | xargs grep grep 连接
您可能会找到答案以及工作代码。例如,在这种情况下,
src/mobility/examples/main-random-topology.cc 有一些东西等待您使用:
Config::Connect ("/NodeList/*/$ns3::MobilityModel/CourseChange",
MakeCallback(&CourseChange));
我们稍后会回到这个例子。
打回来 签名
好吧, I 发现 a 追踪 资源 和 这些因素包括原料奶的可用性以及达到必要粉末质量水平所需的工艺。 配置 路径, 形成一种 do I 数字 什么 这些因素包括原料奶的可用性以及达到必要粉末质量水平所需的工艺。 回报 类型
和 正式 参数 of my 回电话 function 需要 至 是吗?
最简单的方法是检查回调签名 类型定义,这是在
类的“详细描述”中跟踪源的“回调签名”,如
如上所示。
重复“CourseChange”跟踪源条目 ns3::RandomWalk2dMobilityModel we
有:
· 课程变更:位置和/或速度矢量的值已更改。
回调签名: ns3::MobilityModel::CourseChangeCallback
回调签名作为相关链接的链接给出 类型定义,我们发现
类型定义 无效 (* CourseChangeCallback)(常量 标准::字符串 语境, 指针
移动模型> * 模型);
追踪回调 课程变更通知的签名。
如果使用连接回调 无上下文连接 省略 上下文 论据来自
签名。
参数:
[in] context 由跟踪源提供的上下文字符串。
[in] model 正在改变方向的 MobilityModel。
如上所述,查看它的使用情况 grep的 在附近 ns-3 代码库为例。这个例子
上面,从 src/mobility/examples/main-random-topology.cc,连接“CourseChange”
溯源至 课程变更 同一文件中的函数:
静态空隙
CourseChange(std::字符串上下文,Ptr模型)
{
...
}
请注意这个函数:
· 采用“上下文”字符串参数,我们稍后将对此进行描述。 (如果回调
使用连接 无上下文连接 功能 上下文 论点将是
省略。)
· 有 移动模型 作为最后一个参数提供(或者仅在以下情况下提供参数)
无上下文连接 用来)。
· 退货 无效.
如果碰巧回调签名尚未记录,并且没有示例
从工作开始,确定正确的回调函数签名可能是一个挑战
其实从源码中就可以看出。
在开始演练代码之前,我会很友善地告诉您一个简单的方法
弄清楚这一点:回调的返回值将始终是 无效。正式的
的参数列表 追踪回调 可以从模板参数列表中找到
宣言。回想一下,对于我们当前的示例,这是在 移动模型.h我们在哪里
之前已经发现:
追踪回调 > m_courseChangeTrace;
模板参数列表中的参数一一对应
回调函数的声明和形式参数。这里,有一个
模板参数,这是一个 指针 移动模型>。这告诉你,你需要一个
返回 void 并接受 a 的函数 指针 移动模型>。 例如:
无效
课程更改(Ptr模型)
{
...
}
如果您愿意,这就是您所需要的 配置::ConnectWithoutContext。如果你想要一个上下文,
你需要 配置::连接 并使用带有字符串上下文的回调函数,然后
模板参数:
无效
CourseChange(const std::string 上下文,Ptr模型)
{
...
}
如果您想确保您的 课程更改回调 函数仅在您的
本地文件,可以添加关键字 静止 并提出:
静态空隙
CourseChange (const std::string 路径, Ptr模型)
{
...
}
这正是我们在 第三抄送 例。
实施
此部分完全是可选的。这将是一段坎坷的旅程,尤其是对于那些
不熟悉模板的细节。然而,如果你度过了这个难关,你将会拥有
很好地处理了很多 ns-3 低级习语。
因此,我们再次弄清楚回调函数需要什么签名
“CourseChange”追踪来源。这会很痛苦,但你只需要这样做
一次。完成此操作后,您将能够查看 追踪回调 和
明白它。
我们需要查看的第一件事是跟踪源的声明。回想起那个
这是在 移动模型.h,我们之前发现:
追踪回调 > m_courseChangeTrace;
该声明适用于模板。模板参数位于尖括号内,
所以我们真的很想知道那是什么 追踪回调<> 是。如果你有
完全不知道在哪里可以找到这个, grep的 是你的朋友。
我们可能会对其中的某种声明感兴趣 ns-3 来源,所以
首先更改为 SRC 目录。然后,我们知道这个声明必须
在某种头文件中,所以就 grep的 为此,使用:
$ 查找 . -名称 '*.h' | xargs grep TracedCallback
你会看到 303 行飞过(我通过管道传递了这个 wc 看看情况有多糟糕)。虽然
这看起来可能很多,但实际上并不是很多。只需通过管道输出 更多 和
开始扫描它。在第一页,你会看到一些非常可疑的内容
看起来模板的东西。
TracedCallback<T1,T2,T3,T4,T5,T6,T7,T8>::TracedCallback ()
TracedCallback<T1,T2,T3,T4,T5,T6,T7,T8>::ConnectWithoutContext (c ...
TracedCallback<T1,T2,T3,T4,T5,T6,T7,T8>::Connect (const CallbackB ...
TracedCallback<T1,T2,T3,T4,T5,T6,T7,T8>::DisconnectWithoutContext ...
TracedCallback<T1,T2,T3,T4,T5,T6,T7,T8>::Disconnect (const Callba ...
TracedCallback<T1,T2,T3,T4,T5,T6,T7,T8>::operator() (void) const ...
TracedCallback<T1,T2,T3,T4,T5,T6,T7,T8>::operator() (T1 a1) const ...
TracedCallback<T1,T2,T3,T4,T5,T6,T7,T8>::operator() (T1 a1, T2 a2 ...
TracedCallback<T1,T2,T3,T4,T5,T6,T7,T8>::operator() (T1 a1, T2 a2 ...
TracedCallback<T1,T2,T3,T4,T5,T6,T7,T8>::operator() (T1 a1, T2 a2 ...
TracedCallback<T1,T2,T3,T4,T5,T6,T7,T8>::operator() (T1 a1, T2 a2 ...
TracedCallback<T1,T2,T3,T4,T5,T6,T7,T8>::operator() (T1 a1, T2 a2 ...
TracedCallback<T1,T2,T3,T4,T5,T6,T7,T8>::operator() (T1 a1, T2 a2 ...
原来这一切都来自头文件 跟踪回调.h 这听起来
非常有前途。然后你可以看一下 移动模型.h 看到有一条线
这证实了这个预感:
#include“ns3/traced-callback.h”
当然,您也可以从另一个方向来看待这个问题,并从查看开始
包含在 移动模型.h 并注意到包括 跟踪回调.h 和
推断这一定是你想要的文件。
无论哪种情况,下一步都是查看 src/core/model/traced-callback.h in
你最喜欢的编辑器看看发生了什么。
您将在文件顶部看到一条令人欣慰的评论:
ns3::TracedCallback 具有与普通 ns3::Callback 几乎完全相同的 API,但是
而不是将调用转发到单个函数(如 ns3::Callback 通常所做的那样),
它将调用转发到 ns3::Callback 链。
这听起来应该非常熟悉,并且让您知道您走在正确的轨道上。
看完这条评论,你会发现
模板
类型名称 T3 = 空,类型名称 T4 = 空,
类型名称 T5 = 空,类型名称 T6 = 空,
类型名称 T7 = 空,类型名称 T8 = 空>
跟踪回调类
{
...
这告诉您 TracedCallback 是一个模板类。它有八种可能的类型
具有默认值的参数。返回并与您的声明进行比较
试图理解:
追踪回调 > m_courseChangeTrace;
这个 类型名 T1 在模板化类声明中对应于 指针
移动模型> 在上面的声明中。所有其他类型参数保留为
默认值。查看构造函数实际上并不能告诉你太多信息。唯一一个地方
您已经看到回调函数和跟踪系统之间建立了连接
,在 连接 和 无上下文连接 功能。如果你向下滚动,你会看到一个
无上下文连接 方法在这里:
模板
类型名称 T3,类型名称 T4,
类型名称 T5,类型名称 T6,
类型名称 T7,类型名称 T8>
无效
TracedCallback<T1,T2,T3,T4,T5,T6,T7,T8>::ConnectWithoutContext ...
{
Callback<void,T1,T2,T3,T4,T5,T6,T7,T8> cb;
cb.分配(回调);
m_callbackList.push_back(cb);
}
你现在就在野兽的肚子里。当模板被实例化时
上面的声明,编译器将替换 T1 - 指针 移动模型>.
无效
追踪回调 ::ConnectWithoutContext ... cb
{
打回来 > CB;
cb.分配(回调);
m_callbackList.push_back(cb);
}
您现在可以看到我们一直在讨论的所有内容的实现。代码
创建正确类型的回调并将您的函数分配给它。这是
相当于 PFI = 我的功能 我们在本节开头讨论过。代码
然后将回调添加到该源的回调列表中。唯一剩下的就是
看一下Callback的定义。使用相同的 grep的 我们以前发现的把戏
追踪回调,你会发现该文件 ./core/callback.h 是我们
需要看看。
如果你往下看这个文件,你会看到很多可能几乎无法理解的内容
模板代码。您最终会看到一些回调的 API 文档
不过,模板类。幸好有一些英文:
打回来 模板类。
该类模板实现了函子设计模式。它用于声明
的类型 打回来:
· 第一个非可选模板参数表示回调的返回类型。
· 剩余的(可选)模板参数代表后续的类型
回调的参数。
· 最多支持九个参数。
我们正在试图弄清楚什么是
打回来 > CB;
声明手段。现在我们可以理解第一个(非可选)
模板参数, 无效, 表示Callback的返回类型。第二
(可选)模板参数, 指针 移动模型> 代表第一个的类型
回调的参数。
有问题的回调是您接收跟踪事件的函数。从此你可以
推断您需要一个返回的函数 无效 并采取 指针 移动模型>.
例如,
无效
CourseChangeCallback(Ptr模型)
{
...
}
如果您愿意,这就是您所需要的 配置::ConnectWithoutContext。如果你想要一个上下文,
你需要 配置::连接 并使用带有字符串上下文的回调函数。这
是因为 连接 函数将为您提供上下文。你需要:
无效
CourseChangeCallback(std::字符串上下文,Ptr模型)
{
...
}
如果您想确保您的 课程更改回调 仅在您的本地文件中可见,
你可以添加关键字 静止 并提出:
静态空隙
CourseChangeCallback(std::字符串路径,Ptr模型)
{
...
}
这正是我们在 第三抄送 例子。也许你现在应该回去
重读上一节(相信我的话)。
如果您对有关回调实现的更多详细信息感兴趣,请随时
看看 ns-3 手动的。它们是最常用的结构之一
的低级部分 ns-3。在我看来,这是一件非常优雅的事情。
追踪值
在本节前面,我们介绍了一段简单的代码,该代码使用
追踪值 演示跟踪代码的基础知识。我们只是掩饰了
TracedValue 的真正含义以及如何查找其返回类型和形式参数
回调。
正如我们提到的,该文件, 跟踪值.h 引入追踪所需的声明
遵守值语义的数据。一般来说,值语义只是意味着你可以
传递对象本身,而不是传递对象的地址。我们延长
该要求包括完整的赋值式运算符集
为普通旧数据 (POD) 类型预定义:
┌──────────────────────────────────┬──────────────┐
│运算符= (作业) │ │
├──────────────────────────────────┼──────────────┤
│运算符*= │ 运算符/= │
├──────────────────────────────────┼──────────────┤
│运算符+= │ 运算符-= │
├──────────────────────────────────┼──────────────┤
│运算符++ (前缀和 │ │
│后缀) │ │
├──────────────────────────────────┼──────────────┤
│操作员 - (前缀和 │ │
│后缀) │ │
├──────────────────────────────────┼──────────────┤
│运算符<<= │ 运算符>>= │
├──────────────────────────────────┼──────────────┤
│运算符&= │ 运算符|= │
├──────────────────────────────────┼──────────────┤
│运算符%= │ 运算符^= │
└──────────────────────────────────┴──────────────┘
这一切真正意味着您将能够跟踪使用这些内容所做的所有更改
具有值语义的 C++ 对象的运算符。
这个 追踪值<> 我们在上面看到的声明提供了重载的基础结构
上面提到的运算符并驱动回调过程。使用任何运算符时
上面有一个 跟踪值 它将提供该变量的旧值和新值,
在这种情况下 int32_t 价值。通过检查 跟踪值 声明,我们知道
跟踪接收器函数将有参数 (常量 int32_t 旧值, 常量 int32_t 新值).
a 的返回类型 跟踪值 回调函数总是 无效,所以预期的
回调签名将是:
void (* TracedValueCallback)(const int32_t oldValue, const int32_t newValue);
这个 .AddTraceSource ,在 获取类型 ID 方法提供了用于连接的“钩子”
通过Config系统向外界溯源。我们已经讨论过
前三个条件 添加跟踪源:Config系统的属性名称,帮助
字符串,以及 TracedValue 类数据成员的地址。
示例中的最后一个字符串参数“ns3::Traced::Value::Int32”是
类型定义 用于回调函数签名。我们要求定义这些签名,
并将完全限定的类型名称指定为 添加跟踪源,所以 API 文档可以
将跟踪源链接到函数签名。对于 TracedValue ,签名是
直截了当;对于 TracedCallbacks,我们已经看到 API 文档确实有帮助。
真实成功 例如:
让我们从一本最著名的 TCP 书籍中摘取一个例子。 “TCP/IP
插图,第 1 卷:协议”,作者:W. 理查德·史蒂文斯 (W. Richard Stevens),是经典之作。我刚刚翻了一下
这本书打开并看到了拥塞窗口和序列的漂亮图
第 366 页上的数字与时间。史蒂文斯称之为“图 21.10。cwnd 和
在传输数据时发送序列号。”让我们重新创建 cwnd 部分
该情节的 ns-3 使用追踪系统和 图形.
可提供 来源
首先要考虑的是我们如何获取数据。我们到底是什么
需要追踪吗?因此,让我们查阅“所有跟踪源”列表,看看我们需要做什么
和。回想一下,这是在 ns-3 API 文档。如果您滚动浏览
列表,你最终会发现:
ns3::TcpNewReno
· 拥塞窗口:TCP连接的拥塞窗口
· 慢启动阈值:TCP慢启动阈值(字节)
原来, ns-3 TCP 实现(大部分)存在于文件中
src/internet/model/tcp-socket-base.cc 而拥塞控制变体位于这样的文件中
as src/internet/model/tcp-newreno.cc。如果你不知道这一点 a 先验,你可以使用
递归 grep的 诡计:
$ 查找 . -名称 '*.cc' | xargs grep -i tcp
您会发现一页又一页的 tcp 实例将您指向该文件。
调出类文档 TCP新里诺 并跳至列表
TraceSources 你会发现
追踪来源
· 拥塞窗口:TCP 连接的拥塞窗口
回调签名: ns3::追踪::值::Uint322Callback
点击回调 类型定义 链接我们看到您现在知道期望的签名:
typedef void(* ns3::Traced::Value::Int32Callback)(const int32_t oldValue, const int32_t newValue)
您现在应该完全理解这段代码了。如果我们有一个指向 TCP新里诺,
我们可以 跟踪连接 如果我们提供适当的信息,则到“CongestionWindow”跟踪源
回调目标。这与我们在简单示例中看到的跟踪源类型相同
在本节的开头,除了我们正在讨论的 uint32_t 而不是
int32_t。我们知道我们必须提供带有该签名的回调函数。
查找 例子
最好尝试找到可以修改的工作代码,而不是
而不是从头开始。所以现在的首要任务是找到一些代码
已经挂钩“CongestionWindow”跟踪源,看看我们是否可以修改它。照常,
grep的 是你的朋友:
$ 查找 . -名称 '*.cc' | xargs grep CongestionWindow
这将指出一些有前途的候选人: 示例/tcp/tcp-large-transfer.cc
和 src/test/ns3tcp/ns3tcp-cwnd-test-suite.cc.
我们还没有访问任何测试代码,所以让我们看一下那里。你会
通常会发现测试代码相当少,因此这可能是一个非常好的选择。
可选 src/test/ns3tcp/ns3tcp-cwnd-test-suite.cc 在您最喜欢的编辑器中搜索
“拥塞窗口”。你会找到,
ns3TcpSocket->TraceConnectWithoutContext(“CongestionWindow”,
MakeCallback(&Ns3TcpCwndTestCase1::CwndChange, this));
这对你来说应该很熟悉。我们上面提到过,如果我们有一个指向
TCP新里诺, 我们可以 跟踪连接 到“CongestionWindow”跟踪源。正是如此
我们这里有什么;事实证明这行代码正是我们想要的。
让我们继续从这个函数中提取我们需要的代码(Ns3TcpCwndTestCase1::DoRun
(空白))。如果你看一下这个函数,你会发现它看起来就像一个 ns-3
脚本。事实证明确实如此。这是测试运行的脚本
框架,所以我们可以把它拉出来并把它包裹起来 主 而不是 多跑. 相当
我们已经提供了移植结果的文件
这个测试回到原生 ns-3 脚本 - 示例/教程/fifth.cc.
动态 追踪 来源
这个 第五个cc 示例演示了您必须理解的极其重要的规则
在使用任何类型的跟踪源之前:您必须确保目标
配置::连接 命令在尝试使用它之前就已存在。这和说的没什么区别
一个对象必须在尝试调用它之前被实例化。虽然这看起来似乎很明显
当这样说时,它确实让许多第一次尝试使用该系统的人陷入困境
时间。
让我们暂时回到基础知识。存在三个基本的执行阶段
任何 ns-3 脚本。第一阶段有时称为“配置时间”或“设置时间”
时间”,并存在于 主 您的脚本的功能正在运行,但是
before 模拟器::运行 叫做。第二阶段有时称为“模拟时间”
并且存在于以下时间段内: 模拟器::运行 正在积极执行其活动。
完成模拟执行后, 模拟器::运行 将把控制权返回给
这些因素包括原料奶的可用性以及达到必要粉末质量水平所需的工艺。 主 功能。当这种情况发生时,脚本会进入所谓的“Teardown
阶段”,即在设置过程中创建的结构和对象被拆开并
释放。
也许在尝试使用跟踪系统时最常见的错误是假设:
动态构造的实体 ,我们将参加 模拟 次 在配置期间可用
时间。特别是,一个 ns-3 插座 是一个动态对象,通常由 应用 至
之间交流 Nodes。 一个 ns-3 实践应用 总是有一个“开始时间”和一个“停止时间”
时间”与之相关。在绝大多数情况下, 实践应用 不会尝试
创建一个动态对象,直到它 启动应用程序 方法在某些“开始
Time”。这是为了确保在应用程序之前完成模拟配置
尝试做任何事情(如果尝试连接到不存在的节点会发生什么
还是在配置期间?)。因此,在配置阶段您无法
如果其中之一是在跟踪期间动态创建的,则将跟踪源连接到跟踪接收器
模拟。
这个难题的两个解决方案是
1. 创建一个在创建动态对象后运行的模拟器事件并挂钩
跟踪该事件何时执行;或者
2. 在配置时创建动态对象,然后挂钩它,并将该对象提供给
模拟期间使用的系统。
我们采用了第二种方法 第五个cc 例子。这个决定要求我们创建
这些因素包括原料奶的可用性以及达到必要粉末质量水平所需的工艺。 我的应用程式 实践应用,其全部目的是采取 插座 作为参数。
演练: 第五个cc
现在,让我们看一下通过剖析拥塞情况构建的示例程序
窗口测试。打开 示例/教程/fifth.cc 在您最喜欢的编辑器中。你应该看到
一些看起来很熟悉的代码:
/* -*- 模式:C++; c-文件样式:“gnu”; 缩进制表符模式:nil; -*- */
/*
* 本程序是免费软件; 您可以重新分发它和/或修改
* 根据 GNU 通用公共许可证第 2 版的条款
* 由自由软件基金会出版;
*
* 该程序发布是为了希望它有用,
* 但不提供任何保证; 甚至没有默示保证
* 适销性或特定用途的适用性。 请参阅
* GNU 通用公共许可证了解更多详细信息。
*
* 您应该已收到 GNU 通用公共许可证的副本
* 与此程序一起使用; 如果没有,请写信给自由软件
* 基金会,包括。, 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#包括
#include“ns3/core-module.h”
#include“ns3/网络模块.h”
#include "ns3/internet-module.h"
#include“ns3/点对点模块.h”
#include“ns3/applications-module.h”
使用命名空间 ns3;
NS_LOG_COMPONENT_DEFINE ("FifthScriptExample");
这些都已经讲过了,我们就不再赘述了。源代码的下一行是
网络插图和解决上述问题的评论 插座.
// ================================================== ===========================
//
// 节点0 节点1
// +----------------+ +----------------+
// | ns-3 TCP | | ns-3 TCP |
// +----------------+ +----------------+
// | 10.1.1.1 | | 10.1.1.2 | XNUMX
// +----------------+ +----------------+
// |点对点| |点对点|
// +----------------+ +----------------+
// | |
// +--------------------+
// 5 Mbps,2 毫秒
//
//
// 我们想要查看 ns-3 TCP 拥塞窗口的变化。我们需要
// 启动流量并挂钩套接字上的 CongestionWindow 属性
// 发送者的。通常,人们会使用开关应用程序来生成
// 流程,但这有几个问题。一、插座的通断
// 应用程序直到应用程序启动时间才创建,所以我们不会
// 能够在配置时挂钩套接字(现在)。第二,即使我们
// 可以在开始时间之后安排调用,套接字不是公共的,所以我们
// 无法获取它。
//
// 因此,我们可以编写一个简单版本的开关应用程序来执行以下操作
// 我们想要。从好的方面来说,我们不需要所有复杂的开关
// 应用。不利的一面是,我们没有帮手,所以我们必须得到
// 涉及更多细节,但这很简单。
//
// 首先,我们创建一个套接字并对其进行跟踪连接;然后我们通过
// 这个套接字到我们简单应用程序的构造函数中,然后我们
// 安装在源节点。
// ================================================== ===========================
//
这也应该是不言自明的。
下一部分是声明 我的应用程式 实践应用 我们把它们放在一起以允许
这些因素包括原料奶的可用性以及达到必要粉末质量水平所需的工艺。 插座 在配置时创建。
MyApp 类:公共应用程序
{
市民:
我的应用程序();
虚拟〜MyApp();
无效设置(Ptr套接字,地址地址,uint32_t packetSize,
uint32_t nPackets,数据速率(dataRate);
私人:
虚拟无效StartApplication(无效);
虚拟无效StopApplication(无效);
无效 ScheduleTx (无效);
无效发送数据包(无效);
指针m_socket;
地址m_peer;
uint32_t m_packetSize;
uint32_t m_nPackets;
数据率 m_dataRate;
EventId m_sendEvent;
布尔m_running;
uint32_t m_packetsSent;
};
可以看到这个类继承自 ns-3 实践应用 班级。看一眼
src/网络/模型/application.h 如果您对继承的内容感兴趣。这 我的应用程式
类有义务覆盖 启动应用程序 和 停止应用程序 方法。这些
方法会在以下情况下自动调用 我的应用程式 需要开始和停止发送数据
在模拟过程中。
启动/停止 应用
花一些时间来解释事件实际上是如何开始的是值得的。
系统。这是另一个相当深入的解释,如果您不了解,可以忽略
计划冒险进入系统的内部。然而,它是有用的,因为
讨论涉及一些非常重要的部分如何 ns-3 工作并暴露一些
重要的习语。如果您计划实施新模型,您可能想要
了解本节内容。
开始泵送事件的最常见方法是启动 实践应用。这是这样做的
以下(希望)熟悉的线路的结果 ns-3 脚本:
应用程序容器应用程序 = ...
apps.Start(秒(1.0));
apps.Stop(秒(10.0));
应用程序容器代码(参见 src/network/helper/application-container.h 如果你是
感兴趣)循环遍历其包含的应用程序和调用,
应用程序->SetStartTime(startTime);
作为一个结果 应用程序.启动 打电话和
应用程序->SetStopTime(stopTime);
作为一个结果 应用程序.停止 呼叫。
这些调用的最终结果是我们希望模拟器自动运行
拨打我们的电话 应用 告诉他们何时开始和停止。如果是
我的应用程式,它继承自类 实践应用 并覆盖 启动应用程序和
停止应用程序。这些是模拟器将在以下位置调用的函数
适当的时候。如果是 我的应用程式 你会发现 我的应用程序::启动应用程序 不
最初的 捆绑和 连接 在套接字上,然后通过调用开始数据流动
我的应用程序::发送数据包. 我的应用程序::停止应用程序 通过取消任何停止生成数据包
挂起的发送事件然后关闭套接字。
其中一件好事是 ns-3 是你可以完全忽略实现
详细说明您的 实践应用 模拟器在正确的位置“自动”调用
时间。但既然我们已经深入 ns-3 已经,让我们开始吧。
如果你看一下 src/网络/模型/application.cc 你会发现 设置开始时间 方法
一个 实践应用 只是设置成员变量 m_开始时间 和 设置停止时间 方法
只是设置 m_stopTime。从那里开始,如果没有任何提示,这条路可能就会结束。
再次追踪线索的关键是要知道有一个包含所有
系统中的节点。每当您在模拟中创建节点时,都会有一个指向该节点的指针
被添加到全局 节点列表.
瞅 src/network/model/node-list.cc 和搜索 节点列表::添加。公众
静态实现调用私有实现 NodeListPriv::添加。 这
是一个比较常见的惯用语 ns-3。那么,看看 NodeListPriv::添加。在那里你
会发现,
Simulator::ScheduleWithContext (index, TimeStep (0), &Node::Initialize, 节点);
这告诉您,每当在模拟中创建节点时,作为副作用,调用
到该节点的 初始化 方法已为您安排在时间零发生。不
然而,对这个名字的解读太多了。这并不意味着节点将开始做
任何东西,它都可以被解释为对节点的信息调用,告诉它
模拟已经开始,而不是要求采取行动,告诉节点开始做某事。
所以, 节点列表::添加 间接安排呼叫 节点::初始化 在零时间建议
模拟已开始的新节点。如果你看进去 src/网络/模型/node.h
但是,将找不到名为的方法 节点::初始化。事实证明,
初始化 方法是从类继承的 摆件。系统中的所有对象都可以
模拟开始时通知,Node 类的对象只是其中的一种
对象。
瞅 src/core/model/object.cc 下一步并搜索 对象::初始化。这段代码
并不像你想象的那么简单 ns-3 对象 支持
聚合。代码在 对象::初始化 然后循环遍历所有对象
已聚合在一起并调用它们 执行初始化 方法。这是另一个成语
这很常见 ns-3,有时称为“模板设计模式”。:公共
非虚拟 API 方法,该方法在实现中保持不变,并且调用
私有虚实现方法,由子类继承并实现。
这些名称通常类似于 方法名 对于公共 API 和 方法名 HPMC胶囊
私有 API。
这告诉我们应该寻找一个 节点::DoInitialize 方法中
src/网络/模型/node.cc 继续我们的探索的方法。如果您找到
代码中,你会发现一个循环遍历Node中所有设备的方法,然后
节点调用中的所有应用程序 设备->初始化 和 应用程序->初始化
。
您可能已经知道课程 设备 和 实践应用 两者都继承自类 摆件
所以下一步将看看当 应用程序::DoInitialize is
叫。看一眼 src/网络/模型/application.cc 你会发现:
无效
应用程序::DoInitialize(无效)
{
m_startEvent = Simulator::Schedule (m_startTime, &Application::StartApplication, this);
if (m_stopTime != TimeStep (0))
{
m_stopEvent = Simulator::Schedule (m_stopTime, &Application::StopApplication, this);
}
对象::DoInitialize();
}
到这里,我们终于来到了小路的尽头。如果你把一切都搞清楚了,当你
实施一个 ns-3 实践应用,您的新应用程序继承自类 实践应用。 您
覆盖 启动应用程序 和 停止应用程序 方法并提供机制
启动和停止新的数据流 实践应用。当一个节点是
在模拟中创建,它被添加到全局 节点列表。添加节点的行为
Free Introduction 节点列表 导致模拟器事件被安排在时间零,它调用
节点::初始化 模拟开始时要调用的新添加的 Node 的方法。
由于 Node 继承自 摆件,这称为 对象::初始化 节点上的方法
反过来,这又称为 执行初始化 方法对所有的 对象 聚合到
节点(想想移动模型)。自从节点 摆件 已覆盖 执行初始化,这
模拟开始时调用方法。这 节点::DoInitialize 方法调用
初始化 所有的方法 应用 在节点上。自从 应用 也
对象, 这导致 应用程序::DoInitialize 被称为。什么时候
应用程序::DoInitialize 被调用,它为 启动应用程序 和
停止应用程序 呼吁 实践应用。这些调用旨在启动和停止
数据流从 实践应用
这又是一次相当漫长的旅程,但只需要进行一次,而你现在
理解另一个非常深刻的部分 ns-3.
这个 我的应用程式 实践应用
这个 我的应用程式 实践应用 当然需要一个构造函数和一个析构函数:
我的应用程序::我的应用程序 ()
:m_socket(0),
m_peer(),
m_packetSize(0),
m_n个数据包 (0),
m_dataRate (0),
m_sendEvent(),
m_running(假),
m_packets已发送 (0)
{
}
我的应用程序::~我的应用程序()
{
m_socket = 0;
}
下一段代码的存在就是我们编写此代码的全部原因 实践应用 in
首位。
无效
MyApp::设置(Ptr套接字,地址地址,uint32_t packetSize,
uint32_t nPackets,数据速率(数据速率)
{
m_socket = 套接字;
m_peer = 地址;
m_packetSize = 数据包大小;
m_nPackets = nPackets;
m_dataRate = 数据率;
}
这段代码应该是非常不言自明的。我们只是初始化成员变量。
从追踪的角度来看,最重要的是 指针 插座 我们
需要在配置期间提供给应用程序。记得我们要去
创造 插座 作为一个 的TCPSocket (这是由 TCP新里诺)并钩住它的
“CongestionWindow”在将其传递到之前跟踪源 设置 方法。
无效
MyApp::StartApplication(无效)
{
m_running = true;
m_packetsSent = 0;
m_socket->绑定();
m_socket->连接(m_peer);
发送数据包();
}
上面的代码是重写的实现 应用程序::启动应用程序 那将是
由模拟器自动调用来启动我们的 实践应用 在适当的时候运行
时间。你可以看到它做了一个 插座 捆绑 手术。如果您熟悉
Berkeley Sockets 这不足为奇。它在本地执行所需的工作
正如您所期望的那样,连接的一侧。下列 连接 会做什么
需要与 TCP 建立连接 门店地址 m_peer。现在应该清楚了
为什么我们需要将其中大部分时间推迟到模拟时间,因为 连接 将需要
一个功能齐全的网络来完成。之后 连接, 实践应用 然后开始
通过调用创建模拟事件 发送数据包.
下一段代码解释了 实践应用 如何停止创建模拟事件。
无效
MyApp::StopApplication(无效)
{
m_running = false;
if (m_sendEvent.IsRunning())
{
模拟器::取消(m_sendEvent);
}
如果(m_socket)
{
m_socket->关闭();
}
}
每次安排模拟活动时, 创建 被建造。 如果 创建 等待中
执行或执行,其方法 在跑 将返回 true。在这段代码中,如果
在跑() 返回 true,我们 取消 将其从模拟器事件中删除的事件
队列。通过这样做,我们打破了事件链 实践应用 正在用来保留
发送其 包 和 实践应用 安静下来。当我们安静下来之后 实践应用 we
关闭 断开 TCP 连接的套接字。
当以下情况发生时,套接字实际上在析构函数中被删除: m_socket = 0 被执行。这
删除对底层 Ptr 的最后一个引用这会导致析构函数
要调用的对象。
回想起那个 启动应用程序 被称为 发送数据包 启动描述的事件链
这些因素包括原料奶的可用性以及达到必要粉末质量水平所需的工艺。 实践应用 行为。
无效
MyApp::SendPacket(无效)
{
指针数据包 = 创建(m_packetSize);
m_socket->发送(数据包);
if (++m_packetsSent < m_nPackets)
{
ScheduleTx();
}
}
在这里,你看到了 发送数据包 就是这么做的。它创建了一个 小包装 然后做了一个 发送
如果您了解 Berkeley Sockets,这可能正是您所期望看到的。
它的责任是 实践应用 继续安排事件链,所以
下一行调用 调度发送 安排另一个传输事件(a 发送数据包) 直到
实践应用 确定已发送足够的信息。
无效
MyApp::ScheduleTx(无效)
{
如果(m_运行)
{
时间 tNext(秒(m_packetSize * 8 / static_cast (m_dataRate.GetBitRate())));
m_sendEvent = Simulator::Schedule (tNext, &MyApp::SendPacket, this);
}
}
在这里,你看到了 调度发送 正是这样做的。如果 实践应用 正在运行(如果
停止应用程序 尚未被调用)它将安排一个新事件,该事件调用 发送数据包
再次。细心的读者会发现一些也会让新用户感到困惑的东西。数据速率
一个 实践应用 就是这样。它与底层的数据速率无关
频道。这是速率 实践应用 产生位。它不考虑
考虑用于传输数据的各种协议或通道的任何开销
数据。如果您设置数据速率 实践应用 与您的底层数据速率相同
频道 你最终会遇到缓冲区溢出。
追踪 水槽
本练习的重点是从 TCP 获取跟踪回调,指示
拥塞窗口已更新。接下来的一段代码实现了相应的
跟踪接收器:
静态空隙
CwndChange (uint32_t oldCwnd, uint32_t newCwnd)
{
NS_LOG_UNCOND (模拟器::Now ().GetSeconds () << "\t" << newCwnd);
}
现在您应该非常熟悉了,因此我们不再详细介绍。这个功能
只是记录当前的模拟时间和拥塞窗口的新值
时间改变了。您可能可以想象您可以加载结果输出
进入图形程序(gnuplot 或 Excel)并立即看到一个漂亮的图表
随着时间的推移,拥塞窗口行为。
我们添加了一个新的跟踪接收器来显示数据包被丢弃的位置。我们要添加一个错误
模型也适用于此代码,因此我们想演示此工作原理。
静态空隙
RxDrop(Ptr p)
{
NS_LOG_UNCOND ("RxDrop at " << Simulator::Now ().GetSeconds ());
}
该跟踪接收器将连接到点对点的“PhyRxDrop”跟踪源
网络设备。当数据包被物理层丢弃时,此跟踪源将触发
网络设备。如果你稍微绕道源头
(src/点对点/模型/点对点网络设备.cc)你会看到这个痕迹
来源指的是 PointToPointNetDevice::m_phyRxDropTrace。如果你然后看
src/点对点/模型/点对点网络设备.h 对于这个成员变量,你将
发现它被声明为 追踪回调 数据包> >。这应该告诉你
回调目标应该是一个返回 void 并接受单个
参数是一个 指针 数据包> (假设我们使用 无上下文连接) - 只是
我们上面有什么。
主要 教学计划
现在您应该非常熟悉以下代码:
INT
主要(int argc,char *argv[])
{
NodeContainer 节点;
节点.创建(2);
点对点助手点对点;
pointToPoint.SetDeviceAttribute("DataRate", StringValue("5Mbps"));
pointToPoint.SetChannelAttribute("延迟", StringValue("2ms"));
NetDeviceContainer 设备;
设备= pointToPoint.Install(节点);
这将创建两个节点,它们之间有一个点对点通道,如图所示
文件开头的插图。
接下来的几行代码显示了一些新内容。如果我们跟踪一个行为的连接
完美地,我们最终将得到一个单调增加的拥塞窗口。想要看到任何
有趣的行为,我们真的想引入会丢弃数据包的链路错误,
导致重复的 ACK 并触发拥塞窗口的更有趣的行为。
ns-3 提供 错误模型 可附加的物体 频道。我们正在使用
速率误差模型 这允许我们将错误引入 频道 在给定的 率.
指针em = 创建对象();
em->SetAttribute("ErrorRate", DoubleValue(0.00001));
devices.Get(1)->SetAttribute("ReceiveErrorModel",PointerValue(em));
上面的代码实例化了一个 速率误差模型 对象,我们设置“ErrorRate” 属性
到所需的值。然后我们设置实例化的结果 速率误差模型 作为错误
点对点使用的模型 网络设备。这会给我们一些重传和
让我们的情节更有趣一点。
InternetStackHelper堆栈;
stack.Install(节点);
Ipv4AddressHelper地址;
地址.SetBase("10.1.1.0","255.255.255.252");
Ipv4InterfaceContainer 接口 = 地址.分配(设备);
上面的代码大家应该很熟悉了。它在我们的两个节点上安装互联网堆栈
创建接口并为点对点设备分配 IP 地址。
由于我们使用的是 TCP,因此我们需要目标节点上有一些东西来接收 TCP
连接和数据。这 数据包接收器 实践应用 常用于 ns-3 为了那个原因
目的。
uint16_t 接收端口 = 8080;
地址sinkAddress(InetSocketAddress(interfaces.GetAddress(1),sinkPort));
PacketSinkHelper packetSinkHelper ("ns3::TcpSocketFactory",
InetSocketAddress(Ipv4Address::GetAny(),sinkPort));
ApplicationContainer sinkApps = packetSinkHelper.Install(nodes.Get(1));
sinkApps.Start(秒(0.));
sinkApps.Stop(秒(20。));
这些都应该很熟悉,除了,
PacketSinkHelper packetSinkHelper ("ns3::TcpSocketFactory",
InetSocketAddress(Ipv4Address::GetAny(),sinkPort));
这段代码实例化了一个 数据包接收器助手 并告诉它使用该类创建套接字
ns3::TcpSocketFactory。该类实现了一种称为“对象工厂”的设计模式
这是一种常用的机制,用于指定用于在对象中创建对象的类
抽象的方式。在这里,您不必自己创建对象,而是提供
数据包接收器助手 指定一个字符串 类型标识 用于创建一个对象的字符串
然后可以依次用于创建工厂创建的对象的实例。
剩下的参数告诉 实践应用 它应该是哪个地址和端口 捆绑 至。
接下来的两行代码将创建套接字并连接跟踪源。
指针ns3TcpSocket = Socket::CreateSocket (nodes.Get(0),
TcpSocketFactory::GetTypeId ());
ns3TcpSocket->TraceConnectWithoutContext(“CongestionWindow”,
MakeCallback(&CwndChange));
第一条语句调用静态成员函数 套接字::创建套接字 并提供了一个
节点和显式 类型标识 用于创建套接字的对象工厂。这是一个
比调用级别稍低 数据包接收器助手 上面调用,并使用显式 C++
类型而不是字符串引用的类型。否则,它在概念上是相同的
事情。
一旦 的TCPSocket 创建并附加到节点后,我们可以使用
TraceConnectWithoutContext 将 CongestionWindow 跟踪源连接到我们的跟踪接收器。
回想一下,我们编写了一个 实践应用 所以我们可以接受 插座 我们刚刚做了(期间
配置时间)并在仿真时间使用它。我们现在必须实例化它
实践应用。我们没有花任何麻烦来创建一个助手来管理 实践应用 so
我们将不得不“手动”创建和安装它。这实际上很简单:
指针应用程序 = 创建对象();
应用程序->设置(ns3TcpSocket,sinkAddress,1040,DataRate(“1000Mbps”));
节点.Get(0)->AddApplication(app);
应用程序->开始(秒(1.));
应用程序->停止(秒(20.));
第一行创建一个 摆件 类型 我的应用程式 - 我们的 实践应用。第二行告诉
这些因素包括原料奶的可用性以及达到必要粉末质量水平所需的工艺。 实践应用 什么 插座 使用、连接到什么地址、发送多少数据
每个发送事件、要生成多少个发送事件以及生成数据的速率
从这些事件中。
接下来我们手动添加 我的应用程式 实践应用 到源节点并显式调用
开始 和 Stop 停止 上的方法 实践应用 告诉它何时开始和停止做它的事情
事情。
我们实际上需要从接收器进行点对点连接 网络设备 掉落事件
我们 接收丢弃 现在回调。
devices.Get (1)->TraceConnectWithoutContext("PhyRxDrop", MakeCallback (&RxDrop));
现在应该很明显我们正在获取对接收的引用 Node 网络设备
从其容器并连接由属性“PhyRxDrop”定义的跟踪源
该设备到跟踪接收器 接收丢弃.
最后,我们告诉模拟器覆盖任何 应用 并停止处理
模拟 20 秒时发生的事件。
模拟器::停止 (秒(20));
模拟器::运行();
模拟器::销毁();
0返回;
}
回想一下,一旦 模拟器::运行 被调用,配置时间结束,仿真
时间开始。我们通过创建 实践应用 并教授它
如何连接和发送数据实际上发生在这个函数调用期间。
尽快 模拟器::运行 返回,模拟完成,我们进入拆解
阶段。在这种情况下, 模拟器::摧毁 处理好血淋淋的细节,我们就回来
完成后显示成功代码。
运行 第五个cc
既然我们已经提供了文件 第五个cc 对于你来说,如果你已经构建了你的发行版(在
调试模式,因为它使用 NS_LOG -- 回想一下,优化构建优化了 NS_LOG) 它
会等你跑。
$ ./waf --运行第五次
Waf:进入目录“/home/craigdo/repos/ns-3-allinone-dev/ns-3-dev/build”
Waf:离开目录“/home/craigdo/repos/ns-3-allinone-dev/ns-3-dev/build”
“构建”成功完成(0.684s)
1 536
1.0093 1072
1.01528 1608
1.02167 2144
...
1.11319 8040
1.12151 8576
1.12983 9112
RxDrop 为 1.13696
...
您可能会立即看到在痕迹中使用任何类型的印刷品的缺点。
我们将那些无关的 waf 消息打印在我们有趣的信息上
与那些 RxDrop 消息。我们会尽快解决这个问题,但我相信你迫不及待地想看看
所有这些工作的结果。让我们将该输出重定向到一个名为的文件 cwnd.dat:
$ ./waf --运行第五> cwnd.dat 2>&1
现在在您最喜欢的编辑器中编辑“cwnd.dat”并删除 waf 构建状态并删除
行,只留下跟踪数据(您也可以注释掉
TraceConnectWithoutContext("PhyRxDrop", 回调 (&RxDrop)); 在脚本中摆脱
液滴打印同样容易。
你现在可以运行 gnuplot (如果你安装了它)并告诉它生成一些漂亮的
图片:
$ gnuplot
gnuplot> 设置终端 png 大小 640,480
gnuplot> 设置输出“cwnd.png”
gnuplot> 使用 1:2 标题“拥塞窗口”和线点绘制“cwnd.dat”
gnuplot> 退出
现在,您应该在文件中看到拥塞窗口与时间的关系图
“cwnd.png”loading=“lazy”,看起来像:
[图片]
运用 中半山 助手
在上一节中,我们展示了如何挂钩跟踪源并希望获得
模拟中的有趣信息。也许你会记得我们打电话给
使用记录到标准输出 标准::cout 早在这之前的“钝器”
章节。我们还写了如何按顺序解析日志输出是一个问题
隔离有趣的信息。你可能想到我们刚刚花了很多钱
花时间实现一个展示我们想要解决的所有问题的示例
这些因素包括原料奶的可用性以及达到必要粉末质量水平所需的工艺。 ns-3 追踪系统!你是对的。但是,请耐心等待。我们还没有完成。
我们想做的最重要的事情之一就是能够轻松地
控制模拟的输出量;我们也想拯救那些
将数据保存到文件中,以便我们稍后可以参考它。我们可以使用中级跟踪助手
提供于 ns-3 做到这一点并完成图片。
我们提供了一个脚本,用于编写示例中开发的cwnd更改和删除事件
第五个cc 到磁盘上的单独文件中。 cwnd 更改存储为制表符分隔的 ASCII
文件和放置事件存储在 PCAP 文件中。实现这一目标的改变是
相当小。
演练: 第六抄送
让我们看一下需要进行的更改 第五个cc 至 第六抄送。 打开
示例/教程/sixth.cc 在您最喜欢的编辑器中。您可以通过以下方式看到第一个更改
搜索 CwndChange。您会发现我们已经更改了跟踪的签名
接收器,并向每个接收器添加一行,将跟踪信息写入
代表文件的流。
静态空隙
CwndChange(Ptr流,uint32_t oldCwnd,uint32_t newCwnd)
{
NS_LOG_UNCOND (模拟器::Now ().GetSeconds () << "\t" << newCwnd);
*stream->GetStream () << Simulator::Now ().GetSeconds () << "\t" << oldCwnd << "\t" << newCwnd << std::endl;
}
静态空隙
RxDrop(Ptr文件、指针p)
{
NS_LOG_UNCOND ("RxDrop at " << Simulator::Now ().GetSeconds ());
文件->写入(模拟器::Now(), p);
}
我们添加了一个“stream”参数 改变命令 痕迹汇。这是一个对象
保存(保持安全活动)C++ 输出流。事实证明这是一个非常简单的
对象,但它管理流的生命周期问题并解决甚至无法解决的问题
有经验的 C++ 用户会遇到。事实证明,复制构造函数 std::ostream
被标记为私有。这意味着 std::ostreams 不遵守值语义并且不能
可用于任何需要复制流的机制。这包括 ns-3
您可能还记得,回调系统需要遵守值语义的对象。
进一步注意,我们在 改变命令 跟踪汇
执行:
*stream->GetStream () << Simulator::Now ().GetSeconds () << "\t" << oldCwnd << "\t" << newCwnd << std::endl;
如果您替换的话,这将是非常熟悉的代码 *流->获取流 () - 标准::cout作为
在:
std::cout << Simulator::Now ().GetSeconds () << "\t" << oldCwnd << "\t" << newCwnd << std::endl;
这说明了 指针 实际上只是随身携带一个
std::ofstream 为您服务,您可以像任何其他输出流一样在此处使用它。
类似的情况发生在 接收丢弃 除了被传递的对象(a
指针) 代表 PCAP 文件。跟踪水槽中有一个单衬管,用于
将时间戳和要丢弃的数据包的内容写入 PCAP 文件:
文件->写入(模拟器::Now(), p);
当然,如果我们有代表两个文件的对象,我们需要在某个地方创建它们
并导致它们被传递到跟踪接收器。如果您查看 主 功能,
你会发现新的代码可以做到这一点:
AsciiTraceHelper asciiTraceHelper;
指针Stream = asciiTraceHelper.CreateFileStream("第六个.cwnd");
ns3TcpSocket->TraceConnectWithoutContext("CongestionWindow", MakeBoundCallback(&CwndChange,stream));
...
pcapHelper pcapHelper;
指针file = pcapHelper.CreateFile ("第六.pcap", std::ios::out, PcapHelper::DLT_PPP);
devices.Get (1)->TraceConnectWithoutContext("PhyRxDrop", MakeBoundCallback (&RxDrop, file));
在上面代码片段的第一部分中,我们正在创建 ASCII 跟踪文件,
创建一个负责管理它的对象并使用回调的变体
创建函数来安排将对象传递到接收器。我们的 ASCII 跟踪
帮助程序提供了一组丰富的函数,使文本 (ASCII) 文件的使用变得容易。我们是
这里只是举例说明文件流创建函数的使用。
这个 创建文件流 函数基本上是要实例化一个 std::ofstream 对象和
创建一个新文件(或截断现有文件)。这 std::ofstream 被封装在一个
ns-3 用于生命周期管理和复制构造函数问题解决的对象。
然后我们把这个 ns-3 代表文件的对象并将其传递给 MakeBoundCallback().
该函数创建一个回调,就像 回调(),但它“绑定”了一个新值
回调。该值在调用之前作为第一个参数添加到回调中
叫。
从本质上讲, MakeBoundCallback(&CwndChange, 溪流) 导致跟踪源添加
在调用之前将附加“stream”参数添加到形式参数列表的前面
回调。这会更改所需的签名 改变命令 水槽与之匹配
如上所示,其中包括“额外”参数 指针 流.
在上面代码片段的第二部分中,我们实例化了一个 Pcap助手 做
我们的 PCAP 跟踪文件与我们对 Ascii跟踪助手。的线
码,
指针file = pcapHelper.CreateFile("第六个.pcap",
“w”,PcapHelper::DLT_PPP);
创建一个名为“sixth.pcap”、文件模式为“w”的 PCAP 文件。这意味着新文件
如果找到具有该名称的现有文件,则会被截断(内容被删除)。决赛
参数是新 PCAP 文件的“数据链路类型”。这些与 PCAP 相同
库数据链接类型定义在 bpf.h 如果您熟悉 PCAP。在这种情况下,
分布式账本协议 表示 PCAP 文件将包含前缀为 point to 的数据包
点标题。这是真的,因为数据包来自我们的点对点设备
司机。其他常见数据链路类型有适用于 csma 的 DLT_EN10MB(10 MB 以太网)
设备和适用于 wifi 设备的 DLT_IEEE802_11 (IEEE 802.11)。这些被定义
in src/network/helper/trace-helper.h 如果您有兴趣查看该列表。这
列表中的条目与 bpf.h 但我们复制它们以避免 PCAP 源
依赖。
A ns-3 表示 PCAP 文件的对象从返回 的CreateFile 并在一定范围内使用
回调与 ASCII 情况完全相同。
一个重要的绕道:值得注意的是,即使这两个对象都是
以非常相似的方式声明,
指针文件 ...
指针溪流 ...
底层对象完全不同。例如, 指针 是一个
智能指针指向 ns-3 对象是一个相当重量级的东西,支持
属性并集成到Config系统中。这 指针,在
另一方面,是一个指向引用计数对象的智能指针,它是一个非常轻量级的对象
事物。请记住在做出任何假设之前先查看您引用的对象
关于该对象可能拥有的“权力”。
例如看一下 src/network/utils/pcap-file-wrapper.h 在分布和
注意,
类 PcapFileWrapper :公共对象
那个班级 Pcap文件包装器 是一个 ns-3 对象凭借其继承性。然后看看
src/network/model/output-stream-wrapper.h 并注意,
类 OutputStreamWrapper :公共
简单引用计数
该对象不是 ns-3 根本就是对象,它“仅仅”是一个碰巧的 C++ 对象
支持侵入式引用计数。
这里的重点是,仅仅因为你读了 指针 这并不一定意味着
这 东西 是一个 ns-3 可以悬挂的物体 ns-3 例如,属性。
现在,回到这个例子。如果您构建并运行这个示例,
$ ./waf --运行第六次
您将看到与运行“fifth”时相同的消息,但会出现两个新文件
出现在你的顶级目录中 ns-3 分布。
第六.cwnd 第六.pcap
由于“sixth.cwnd”是一个 ASCII 文本文件,您可以使用以下命令查看它 猫 或您最喜爱的文件
观众。
1 0 536
1.0093 536 1072
1.01528 1072 1608
1.02167 1608 2144
...
9.69256 5149 5204
9.89311 5204 5259
您有一个制表符分隔的文件,其中包含时间戳、旧的拥塞窗口和新的拥塞窗口。
拥塞窗口适合直接导入到绘图程序中。没有
文件中的无关打印,无需解析或编辑。
由于“sixth.pcap”是一个 PCAP 文件,您可以使用以下命令查看它 转储.
从文件 Six.pcap 中读取,链路类型 PPP (PPP)
1.136956 IP 10.1.1.1.49153 > 10.1.1.2.8080:标志 [.],seq 17177:17681,ack 1,win 32768,选项 [TS val 1133 ecr 1127,eol],长度 504
1.403196 IP 10.1.1.1.49153 > 10.1.1.2.8080:标志 [.],seq 33280:33784,ack 1,win 32768,选项 [TS val 1399 ecr 1394,eol],长度 504
...
7.426220 IP 10.1.1.1.49153 > 10.1.1.2.8080:标志 [.],seq 785704:786240,ack 1,win 32768,选项 [TS val 7423 ecr 7421,eol],长度 536
9.630693 IP 10.1.1.1.49153 > 10.1.1.2.8080:标志 [.],seq 882688:883224,ack 1,win 32768,选项 [TS val 9620 ecr 9618,eol],长度 536
您有一个 PCAP 文件,其中包含模拟中丢弃的数据包。没有
文件中存在其他数据包,并且没有其他任何东西可以维持生命
难。
这是一段漫长的旅程,但我们现在正处于一个可以欣赏的时刻 ns-3
追踪系统。我们已经从 TCP 实现中提取了重要事件
和一个设备驱动程序。我们将这些事件直接存储在可与众所周知的文件一起使用的文件中
工具。我们在没有修改任何涉及的核心代码的情况下做到了这一点,并且我们在
仅18行代码:
静态空隙
CwndChange(Ptr流,uint32_t oldCwnd,uint32_t newCwnd)
{
NS_LOG_UNCOND (模拟器::Now ().GetSeconds () << "\t" << newCwnd);
*stream->GetStream () << Simulator::Now ().GetSeconds () << "\t" << oldCwnd << "\t" << newCwnd << std::endl;
}
...
AsciiTraceHelper asciiTraceHelper;
指针Stream = asciiTraceHelper.CreateFileStream("第六个.cwnd");
ns3TcpSocket->TraceConnectWithoutContext("CongestionWindow", MakeBoundCallback(&CwndChange,stream));
...
静态空隙
RxDrop(Ptr文件、指针p)
{
NS_LOG_UNCOND ("RxDrop at " << Simulator::Now ().GetSeconds ());
文件->写入(模拟器::Now(), p);
}
...
pcapHelper pcapHelper;
指针file = pcapHelper.CreateFile("sixth.pcap", "w", PcapHelper::DLT_PPP);
devices.Get (1)->TraceConnectWithoutContext("PhyRxDrop", MakeBoundCallback (&RxDrop, file));
追踪 助手
这个 ns-3 跟踪助手提供了一个丰富的环境来配置和选择不同的
跟踪事件并将其写入文件。在前面的章节中,主要是
BuildingTopologies,我们已经看到了设计的几种跟踪辅助方法
用于其他(设备)助手内部。
也许您会记得看到其中的一些变化:
pointToPoint.EnablePcapAll ("第二");
pointToPoint.EnablePcap("秒", p2pNodes.Get(0)->GetId(), 0);
csma.EnablePcap ("第三个", csmaDevices.Get (0), true);
pointToPoint.EnableAsciiAll (ascii.CreateFileStream ("myfirst.tr"));
然而,可能并不明显的是,所有的模型都有一个一致的模型。
在系统中找到与跟踪相关的方法。 我们现在将花一点时间来看看
在“大图”。
目前有两个主要的跟踪助手用例 ns-3:设备助手
和协议助手。设备助手着眼于指定哪些跟踪的问题
应通过(节点、设备)对启用。例如,您可能想要指定
应在特定节点上的特定设备上启用 PCAP 跟踪。这
从 ns-3 设备概念模型,以及设备的概念模型
各种设备助手。自然地,创建的文件遵循
- - 命名约定。
协议助手查看指定应启用哪些跟踪的问题
协议和接口对。 这是从 ns-3 协议栈概念
模型,以及互联网堆栈助手的概念模型。自然,痕迹
文件应遵循 - - 命名约定。
因此,跟踪助手自然而然地落入二维分类法中。 有
阻止所有四个类行为相同的微妙之处,但我们确实努力
使它们尽可能相似地工作; 并且尽可能有类似物
所有类中的所有方法。
┌────────────────┬──────┬────────┐
│ │ PCAP │ ASCII │
└────────────────┴──────┴────────┘
│设备助手│ │ │
├────────────────┼──────┼────────┤
│协议助手│ │ │
└────────────────┴──────┴────────┘
我们使用一种称为 混入 为我们的助手类添加跟踪功能。 一种
混入 是一个类,当它被子类继承时提供功能。
从 mixin 继承不被认为是一种专业化形式,但实际上是一种实现
收集功能。
让我们快速浏览一下所有这四个案例及其各自的 混合蛋白.
设备 助手
PCAP
这些助手的目标是轻松地将一致的 PCAP 跟踪工具添加到
ns-3 设备。我们希望所有不同类型的 PCAP 追踪都能在不同的环境中以相同的方式工作。
所有设备,因此这些助手的方法由设备助手继承。 看一看
at src/network/helper/trace-helper.h 如果您想在查看时关注讨论
真实的代码。
班级 Pcap设备助手 是一个 混入 提供了使用的高级功能
PCAP 追踪 ns-3 设备。 每个设备都必须实现一个虚拟方法
从这个类继承。
virtual void EnablePcapInternal(std::string 前缀,Ptr nd,布尔混杂,布尔显式文件名)= 0;
该方法的签名反映了此时情况的以设备为中心的观点
等级。 从类继承的所有公共方法 PcapUserHelperForDevice 减少到
调用这个依赖于单个设备的实现方法。 例如,最低级别
投射电容法,
void EnablePcap (std::string 前缀, Ptr nd,布尔混杂=假,布尔显式文件名=假);
将调用设备实现 启用 Pcap 内部 直接地。所有其他公共 PCAP
跟踪方法建立在此实现之上,以提供额外的用户级别
功能。 这对用户来说意味着系统中的所有设备助手都将
拥有所有可用的 PCAP 跟踪方法;这些方法都将在同一个中工作
如果设备实现了跨设备的方式 启用 Pcap 内部 正确。
方法
void EnablePcap (std::string 前缀, Ptr nd,布尔混杂=假,布尔显式文件名=假);
void EnablePcap (std::string 前缀, std::string ndName, bool promiscuous = false, bool explicitFilename = false);
void EnablePcap (std::string 前缀, NetDeviceContainer d, bool promiscously = false);
void EnablePcap (std::string 前缀, NodeContainer n, bool promiscously = false);
void EnablePcap (std::string 前缀、uint32_t nodeid、uint32_t deviceid、bool promiscously = false);
void EnablePcapAll (std::string 前缀, bool promiscuous = false);
在上面显示的每个方法中,都有一个名为的默认参数 淫乱 这
默认为 false。该参数表明跟踪不应被收集在
混杂模式。 如果您确实希望您的跟踪包括设备看到的所有流量
(并且如果设备支持混杂模式)只需将 true 参数添加到任何
上面调用。例如,
点nd;
...
helper.EnablePcap ("前缀", nd, true);
将启用混杂模式捕获 网络设备 由 nd.
前两个方法还包括一个默认参数,称为 显式文件名 那会的
下面讨论。
我们鼓励您仔细阅读课程的 API 文档 Pcap设备助手 找
这些方法的细节;但总结一下...
· 您可以通过提供一个特定的节点/网络设备对来启用 PCAP 跟踪
点 到 启用Pcap 方法。 的 点 是隐含的,因为网络设备
必须恰好属于一个节点。例如,
点nd;
...
helper.EnablePcap ("前缀", nd);
· 您可以通过提供一个特定的节点/网络设备对来启用 PCAP 跟踪
标准::字符串 将对象名称服务字符串表示为 启用Pcap 方法。 的
点 从名称字符串中查找。 再次, 是隐含的,因为
指定的网络设备必须属于一个节点。例如,
名称::Add ("server" ...);
名称::Add ("server/eth0" ...);
...
helper.EnablePcap ("前缀", "server/ath0");
· 您可以通过提供一个节点/网络设备对的集合来启用 PCAP 跟踪
网络设备容器。 对于每个 网络设备 在容器中检查类型。 对于每个
正确类型的设备(与设备助手管理的类型相同),跟踪是
启用。 再次, 是隐式的,因为找到的网络设备必须属于
恰好是一个节点。例如,
NetDeviceContainer d = ...;
...
helper.EnablePcap ("前缀", d);
· 您可以通过提供一个节点/网络设备对的集合来启用 PCAP 跟踪
节点容器。对于每个节点 节点容器 它的附件 网络设备 被迭代。
对于每一个 网络设备 连接到容器中的每个节点,该设备的类型是
检查。 对于正确类型的每个设备(与设备管理的类型相同)
helper),跟踪已启用。
节点容器 n;
...
helper.EnablePcap ("前缀", n);
· 您可以根据节点 ID 和设备 ID 以及使用
明确的 PTR。系统中的每个节点都有一个整数节点ID和连接的每个设备
节点具有整数设备 ID。
helper.EnablePcap ("前缀", 21, 1);
· 最后,您可以为系统中的所有设备启用PCAP跟踪,具有相同的类型
由设备助手管理。
helper.EnablePcapAll ("前缀");
档名
上述方法描述中隐含的是通过以下方式构建完整的文件名
实施方法。按照惯例,PCAP 跟踪在 ns-3 系统的形式
- 身份证>- id>.pcap
如前所述,系统中的每个 Node 都会有一个系统分配的 Node id;和
每个设备都有一个相对于其节点的接口索引(也称为设备 ID)。
默认情况下,由于在第一个上启用跟踪而创建了一个 PCAP 跟踪文件
使用前缀“prefix”的节点 21 的设备将是 前缀 21-1.pcap.
您可以随时使用 ns-3 对象名称服务使这一点更加清晰。 例如,如果
您使用对象名称服务将名称“服务器”分配给节点 21,即生成的 PCAP
跟踪文件名会自动变成, 前缀服务器 1.pcap 如果您还分配
将“eth0”命名为设备,您的 PCAP 文件名将自动选取该名称并命名为
被称为 前缀服务器-eth0.pcap.
最后,上面显示的两种方法,
void EnablePcap (std::string 前缀, Ptr nd,布尔混杂=假,布尔显式文件名=假);
void EnablePcap (std::string 前缀, std::string ndName, bool promiscuous = false, bool explicitFilename = false);
有一个名为的默认参数 显式文件名. 当设置为 true 时,此参数
禁用自动文件名完成机制并允许您创建显式
文件名。此选项仅在启用 PCAP 跟踪的方法中可用
单个设备。
例如,为了安排设备助手创建单个混杂的 PCAP
捕获特定名称的文件 我的-pcap-file.pcap 在给定设备上,人们可以:
点nd;
...
helper.EnablePcap ("my-pcap-file.pcap", nd, true, true);
最快的 true 参数启用混杂模式跟踪,第二个告诉助手
来解释 字首 参数作为完整的文件名。
ASCII码
ASCII 跟踪助手的行为 混入 与 PCAP 版本基本相似。
瞅 src/network/helper/trace-helper.h 如果你想关注讨论
在查看真实代码时。
班级 AsciiTraceHelperForDevice 添加使用 ASCII 的高级功能
跟踪到设备帮助程序类。与 PCAP 情况一样,每个设备都必须实现
从 ASCII 跟踪继承的单个虚拟方法 混入.
虚拟无效EnableAsciiInternal(Ptr溪流,
std::string 前缀,
指针nd,
布尔显式文件名) = 0;
该方法的签名反映了此时情况的以设备为中心的观点
等级; 以及助手可能正在写入共享输出流的事实。 所有的
从类继承的与 ASCII 跟踪相关的公共方法 AsciiTraceHelperForDevice
减少到调用这个单一的设备相关的实现方法。 例如,
最低级别的 ascii 跟踪方法,
void EnableAscii(std::字符串前缀,Ptr nd, 布尔型显式文件名 = false);
无效 EnableAscii (Ptr 流,Ptr nd);
将调用设备实现 启用AsciiInternal 直接,提供任一
有效的前缀或流。所有其他公共 ASCII 跟踪方法都将基于这些方法
低级函数提供额外的用户级功能。这意味着什么
用户认为系统中的所有设备助手都将具有所有 ASCII 跟踪方法
可用的; 如果设备
实施 启用AsciiInternal 正确。
方法
void EnableAscii(std::字符串前缀,Ptr nd, 布尔型显式文件名 = false);
无效 EnableAscii (Ptr 流,Ptr nd);
void EnableAscii (std::string 前缀, std::string ndName, boolexplicitFilename = false);
无效 EnableAscii (Ptr 流,std::string ndName);
void EnableAscii (std::string 前缀, NetDeviceContainer d);
无效 EnableAscii (Ptr 流,NetDeviceContainer d);
void EnableAscii (std::string 前缀, NodeContainer n);
无效 EnableAscii (Ptr 流,节点容器 n);
无效 EnableAsciiAll (std::string 前缀);
无效 EnableAsciiAll (Ptr 溪流);
void EnableAscii (std::string 前缀, uint32_t nodeid, uint32_t deviceid, boolexplicitFilename);
无效 EnableAscii (Ptr 流,uint32_t nodeid,uint32_t deviceid);
我们鼓励您仔细阅读课程的 API 文档 AsciiTraceHelperForDevice 至
查找这些方法的详细信息;但总结一下...
· 可用于 ASCII 跟踪的方法是 PCAP 的两倍
追踪。这是因为,除了 PCAP 式模型之外,每个
唯一的节点/设备对被写入一个唯一的文件,我们支持一个模型,其中跟踪
许多节点/设备对的信息被写入一个公共文件。 这意味着
- - 文件名生成机制被一种机制取代
引用一个通用文件; 并且API方法的数量增加了一倍以允许所有
组合。
· 就像 PCAP 跟踪一样,您可以在特定(节点、网络设备)上启用 ASCII 跟踪
通过提供一个配对 点 到 启用ASCII 方法。 的 点 是隐式的
因为网络设备必须恰好属于一个节点。例如,
点nd;
...
helper.EnableAscii ("前缀", nd);
· 前四个方法还包含一个默认参数,称为 显式文件名 这
与 PCAP 情况下的等效参数类似。
在这种情况下,不会将任何跟踪上下文写入 ASCII 跟踪文件,因为它们将被写入
多余的。 系统将使用相同的规则选择要创建的文件名
PCAP 部分中描述,但该文件具有后缀 .tr 而不是
.pcap.
· 如果您想在多个网络设备上启用 ASCII 跟踪并发送所有跟踪
对于单个文件,您也可以通过使用对象来引用单个文件来做到这一点。
我们已经在上面的“cwnd”示例中看到了这一点:
点nd1;
点nd2;
...
点流 = asciiTraceHelper.CreateFileStream ("trace-file-name.tr");
...
helper.EnableAscii(流,nd1);
helper.EnableAscii(流,nd2);
在这种情况下,跟踪上下文 旨在 写入 ASCII 跟踪文件,因为它们是必需的
消除两个设备的痕迹。 请注意,由于用户完全
指定文件名,字符串应包含 ,tr 后缀以保持一致性。
· 您可以通过提供一个特定的(节点、网络设备)对来启用 ASCII 跟踪
标准::字符串 将对象名称服务字符串表示为 启用Pcap 方法。 的
点 从名称字符串中查找。 再次, 是隐含的,因为
指定的网络设备必须属于一个节点。例如,
Names::Add ("client" ...);
名称::Add ("client/eth0" ...);
名称::Add ("server" ...);
名称::Add ("server/eth0" ...);
...
helper.EnableAscii ("prefix", "client/eth0");
helper.EnableAscii ("prefix", "server/eth0");
这将产生两个名为“prefix-client-eth0.tr”的文件和
“prefix-server-eth0.tr”,其中包含每个设备的跟踪信息
各自的跟踪文件。由于所有“EnableAscii”函数
被重载以采用流包装器,您可以将该形式用作
出色地::
Names::Add ("client" ...);
名称::Add ("client/eth0" ...);
名称::Add ("server" ...);
名称::Add ("server/eth0" ...);
...
点流 = asciiTraceHelper.CreateFileStream ("trace-file-name.tr");
...
helper.EnableAscii (stream, "client/eth0");
helper.EnableAscii (stream, "server/eth0");
这将产生一个名为 跟踪文件名.tr 包含所有
两个设备的跟踪事件。 这些事件将通过跟踪上下文消除歧义
字符串。
· 您可以通过提供一个(节点、网络设备)对的集合来启用 ASCII 跟踪
网络设备容器。 对于每个 网络设备 在容器中检查类型。 对于每个
正确类型的设备(与设备助手管理的类型相同),跟踪是
启用。 再次, 是隐式的,因为找到的网络设备必须属于
恰好是一个节点。例如,
NetDeviceContainer d = ...;
...
helper.EnableAscii ("前缀", d);
这将导致创建许多 ASCII 跟踪文件,
其中每一个都遵循`` - - .tr``
惯例。
将所有跟踪合并到一个文件中的完成方式与示例类似
以上:
NetDeviceContainer d = ...;
...
点流 = asciiTraceHelper.CreateFileStream ("trace-file-name.tr");
...
helper.EnableAscii (流, d);
· 您可以通过提供一个(节点、网络设备)对的集合来启用 ASCII 跟踪
节点容器。对于每个节点 节点容器 它的附件 网络设备 被迭代。
对于每一个 网络设备 连接到容器中的每个节点,该设备的类型是
检查。 对于正确类型的每个设备(与设备管理的类型相同)
helper),跟踪已启用。
节点容器 n;
...
helper.EnableAscii ("前缀", n);
这将导致创建许多 ASCII 跟踪文件,每个文件都遵循
这些因素包括原料奶的可用性以及达到必要粉末质量水平所需的工艺。 - 身份证>- id>.tr 习俗。将所有痕迹组合成一个
单个文件的完成方式与上面的示例类似。
· 您可以根据节点 ID 和设备 ID 以及使用
明确的 PTR。系统中的每个节点都有一个整数节点ID和连接的每个设备
节点具有整数设备 ID。
helper.EnableAscii ("前缀", 21, 1);
当然,跟踪可以合并到一个文件中,如上所示。
· 最后,您可以为系统中的所有设备启用PCAP跟踪,具有相同的类型
由设备助手管理。
helper.EnableAsciiAll ("前缀");
这将导致创建许多 ASCII 跟踪文件,每个设备一个
在由助手管理的类型的系统中。所有这些文件都将遵循
- 身份证>- id>.tr 习俗。将所有痕迹组合成一个
文件的完成与上面的示例类似。
档名
上述前缀式方法描述中隐含的是完整的构造
文件名由实现方法决定。按照惯例,ASCII 跟踪在 ns-3 系统
是形式 - 身份证>- id>.tr
如前所述,系统中的每个 Node 都会有一个系统分配的 Node id;和
每个设备都有一个相对于其节点的接口索引(也称为设备 ID)。
默认情况下,由于在第一个上启用跟踪而创建一个 ASCII 跟踪文件
节点 21 的设备,使用前缀“prefix”,将是 前缀 21-1.tr.
您可以随时使用 ns-3 对象名称服务使这一点更加清晰。 例如,如果
您使用对象名称服务将名称“server”分配给节点 21,结果
ASCII 跟踪文件名将自动变为, 前缀服务器 1.tr 如果你也分配
设备名称“eth0”,您的 ASCII 跟踪文件名将自动选取该名称
并被称为 前缀服务器-eth0.tr.
其中一些方法有一个默认参数,称为 显式文件名。 设置为
true,此参数禁用自动文件名完成机制并允许您
创建一个明确的文件名。此选项仅在采用
前缀并在单个设备上启用跟踪。
协议 助手
PCAP
这些的目标 混合蛋白 是为了轻松添加一致的 PCAP 跟踪工具
协议。我们希望 PCAP 追踪的所有不同风格在所有领域都能够以相同的方式工作。
协议,因此这些助手的方法由堆栈助手继承。 看一眼
src/network/helper/trace-helper.h 如果您想在查看时关注讨论
真实的代码。
在本节中,我们将说明应用于协议的方法 Ipv4。 至
在类似的协议中指定跟踪,只需替换适当的类型。 例如,
用一个 点 代替 点 并打电话 启用PcapIpv6 而不是 启用PcapIpv4.
班级 PcapHelperForIpv4 提供使用 PCAP 跟踪的高级功能
,在 Ipv4 协议。 启用这些方法的每个协议助手都必须实现一个
继承自该类的虚方法。 将有一个单独的实现
Ipv6,例如,但唯一的区别在于方法名称和签名。
需要不同的方法名称来消除类的歧义 Ipv4 , Ipv6 两者都是
从类派生 摆件,以及共享相同签名的方法。
virtual void EnablePcapIpv4Internal(std::string 前缀,
指针ipv4,
uint32_t接口,
布尔显式文件名) = 0;
该方法的签名反映了以协议和接口为中心的视图
这个级别的情况。 从类继承的所有公共方法 PcapHelperForIpv4
减少到调用这个依赖于单个设备的实现方法。 例如,
最低级别的 PCAP 方法,
void EnablePcapIpv4(std::string 前缀,Ptr ipv4,uint4_t 接口,布尔explicitFilename = false);
将调用设备实现 启用PcapIpv4内部 直接地。 所有其他公众
PCAP 跟踪方法构建在此实现之上,以提供额外的用户级别
功能。这对用户来说意味着系统中的所有协议助手
将拥有所有可用的 PCAP 跟踪方法;这些方法都将在
如果助手实现,跨协议的方式相同 启用PcapIpv4内部 正确。
方法
这些方法被设计为与 Node 和 Node 一对一对应。
网络设备- 设备版本的中心版本。而不是节点和 网络设备 对
约束,我们使用协议和接口约束。
请注意,就像在设备版本中一样,有六种方法:
void EnablePcapIpv4(std::string 前缀,Ptr ipv4,uint4_t 接口,布尔explicitFilename = false);
void EnablePcapIpv4(std::string 前缀,std::string ipv4Name,uint32_t 接口,bool ExplicitFilename = false);
void EnablePcapIpv4 (std::string 前缀, Ipv4InterfaceContainer c);
void EnablePcapIpv4(std::string 前缀,NodeContainer n);
void EnablePcapIpv4(std::string 前缀、uint32_t 节点 ID、uint32_t 接口、bool 显式文件名);
无效 EnablePcapIpv4All (std::string 前缀);
我们鼓励您仔细阅读课程的 API 文档 PcapHelperForIpv4 找到了
这些方法的细节; 但总结...
· 您可以通过提供一个特定的协议/接口对来启用 PCAP 跟踪
点 和 接口 到 启用Pcap 方法。例如,
点ipv4 = 节点->GetObject ();
...
helper.EnablePcapIpv4 ("前缀", ipv4, 0);
· 您可以通过提供一个特定的节点/网络设备对来启用 PCAP 跟踪
标准::字符串 将对象名称服务字符串表示为 启用Pcap 方法。 的
点 从名称字符串中查找。例如,
名称::Add ("serverIPv4" ...);
...
helper.EnablePcapIpv4 ("prefix", "serverIpv4", 1);
· 您可以通过提供一个协议/接口对的集合来启用 PCAP 跟踪
IPv4接口容器。 对于每个 Ipv4 / 容器中的接口对协议
类型已检查。对于适当类型的每个协议(与所管理的类型相同)
设备助手),为相应的接口启用跟踪。例如,
NodeContainer 节点;
...
NetDeviceContainer 设备 = deviceHelper.Install(节点);
...
ipv4AddressHelper ipv4;
ipv4.SetBase("10.1.1.0", "255.255.255.0");
Ipv4InterfaceContainer 接口 = ipv4.Assign (设备);
...
helper.EnablePcapIpv4(“前缀”,接口);
· 您可以通过提供一个协议/接口对的集合来启用 PCAP 跟踪
节点容器。对于每个节点 节点容器 找到合适的协议。
对于每个协议,都会枚举其接口并在结果上启用跟踪
对。例如,
节点容器 n;
...
helper.EnablePcapIpv4 ("前缀", n);
· 您还可以根据节点 ID 和接口启用 PCAP 跟踪。在这个
在这种情况下,节点 ID 被转换为 点 并查找适当的协议
在节点中。生成的协议和接口用于指定生成的结果
跟踪源。
helper.EnablePcapIpv4 ("前缀", 21, 1);
· 最后,您可以为系统中的所有接口启用 PCAP 跟踪,并关联
协议与设备助手管理的协议类型相同。
helper.EnablePcapIpv4All ("前缀");
档名
上述所有方法描述中隐含的是完整的构造
文件名由实现方法决定。按照惯例,对设备进行的 PCAP 跟踪
这些因素包括原料奶的可用性以及达到必要粉末质量水平所需的工艺。 ns-3 系统的形式为“ - - .pcap”。在这种情况下
协议跟踪,协议和协议之间存在一一对应关系 Nodes。 这
是因为协议 对象 被汇总到 Node 对象。由于没有全局
系统中的协议id,我们在文件命名时使用对应的Node id。所以
自动选择的跟踪文件名可能会发生文件名冲突。
因此,协议跟踪的文件名约定发生了变化。
如前所述,系统中的每个节点都会有一个系统分配的节点 ID。
由于协议实例和Node实例之间存在一一对应的关系
我们使用节点 ID。每个接口都有一个与其协议相关的接口 ID。我们用
公约》 -n -一世.pcap”用于跟踪文件命名
协议助手。
因此,默认情况下,由于启用跟踪而创建的 PCAP 跟踪文件
使用前缀“prefix”的节点 1 的 Ipv4 协议的接口 21 将是
“前缀-n21-i1.pcap”。
您可以随时使用 ns-3 对象名称服务使这一点更加清晰。 例如,如果
您使用对象名称服务将名称“serverIpv4”分配给 Ptr在节点上
21、生成的PCAP跟踪文件名会自动变成,
“前缀-nserverIpv4-i1.pcap”。
其中一些方法有一个默认参数,称为 显式文件名。 设置为
true,此参数禁用自动文件名完成机制并允许您
创建一个明确的文件名。此选项仅在采用
前缀并在单个设备上启用跟踪。
ASCII码
ASCII 跟踪助手的行为与 PCAP 情况基本相似。采取一个
看着 src/network/helper/trace-helper.h 如果你想在讨论的同时
看真实的代码。
在本节中,我们将说明应用于协议的方法 Ipv4。 至
在类似的协议中指定跟踪,只需替换适当的类型。 例如,
用一个 点 代替 点 并打电话 启用AsciiIpv6 而不是
启用AsciiIpv4.
班级 AsciiTraceHelperForIpv4 添加使用 ASCII 的高级功能
跟踪到协议助手。 启用这些方法的每个协议都必须实现一个
从此类继承的单个虚拟方法。
虚拟无效EnableAsciiIpv4Internal(Ptr溪流,
std::string 前缀,
指针ipv4,
uint32_t接口,
布尔显式文件名) = 0;
此方法的签名反映了以协议和接口为中心的视图
这个级别的情况; 以及助手可能正在写入共享的事实
输出流。 从类继承的所有公共方法
Pcap和AsciiTraceHelperForIpv4 减少到调用这个单一的设备依赖
实施方法。例如,最低级别的 ASCII 跟踪方法,
void EnableAsciiIpv4(std::string 前缀,Ptr ipv4,uint4_t 接口,布尔explicitFilename = false);
无效 EnableAsciiIpv4 (Ptr 流,Ptr ipv4,uint4_t 接口);
将调用设备实现 启用AsciiIpv4Internal 直接提供
前缀或流。所有其他公共 ASCII 跟踪方法都将基于这些方法
低级函数提供额外的用户级功能。这意味着什么
用户认为系统中的所有设备助手都将具有所有 ASCII 跟踪方法
可用的; 如果
协议实现 启用AsciiIpv4内部 正确。
方法
void EnableAsciiIpv4(std::string 前缀,Ptr ipv4,uint4_t 接口,布尔explicitFilename = false);
无效 EnableAsciiIpv4 (Ptr 流,Ptr ipv4,uint4_t 接口);
void EnableAsciiIpv4 (std::string 前缀,std::string ipv4Name,uint32_t 接口,bool ExplicitFilename = false);
无效 EnableAsciiIpv4 (Ptr 流,std::string ipv4Name,uint32_t 接口);
void EnableAsciiIpv4 (std::string 前缀, Ipv4InterfaceContainer c);
无效 EnableAsciiIpv4 (Ptr 流,Ipv4InterfaceContainer c);
void EnableAsciiIpv4(std::string 前缀,NodeContainer n);
无效 EnableAsciiIpv4 (Ptr 流,节点容器 n);
无效 EnableAsciiIpv4All (std::string 前缀);
无效 EnableAsciiIpv4All (Ptr 溪流);
void EnableAsciiIpv4 (std::string 前缀、uint32_t 节点 ID、uint32_t 设备 ID、bool 显式文件名);
无效 EnableAsciiIpv4 (Ptr 流,uint32_t nodeid,uint32_t 接口);
我们鼓励您仔细阅读课程的 API 文档 Pcap和AsciiHelperForIpv4 至
查找这些方法的详细信息;但总结一下...
· 可用于 ASCII 跟踪的方法是 PCAP 的两倍
追踪。这是因为,除了 PCAP 式模型之外,每个
唯一的协议/接口对被写入一个唯一的文件,我们支持一个模型,其中
许多协议/接口对的跟踪信息被写入一个公共文件。 这
意味着-n -文件名生成机制是
被引用公共文件的机制所取代; API 方法的数量为
加倍以允许所有组合。
· 正如 PCAP 跟踪一样,您可以在特定协议/接口上启用 ASCII 跟踪
通过提供一个配对 点 和 接口 到 启用ASCII 方法。例如,
点IPv4;
...
helper.EnableAsciiIpv4 ("前缀", ipv4, 1);
在这种情况下,不会将任何跟踪上下文写入 ASCII 跟踪文件,因为它们将被写入
多余的。 系统将使用相同的规则选择要创建的文件名
PCAP 部分中描述,但该文件将具有后缀“.tr”
“.pcap”。
· 如果您想在多个接口上启用 ASCII 跟踪并发送所有跟踪
对于单个文件,您也可以通过使用对象来引用单个文件来做到这一点。
我们在上面的“cwnd”示例中已经有类似的内容:
点协议4 = node1->GetObject ();
点协议4 = node2->GetObject ();
...
点流 = asciiTraceHelper.CreateFileStream ("trace-file-name.tr");
...
helper.EnableAsciiIpv4(流,协议1,1);
helper.EnableAsciiIpv4(流,协议2,1);
在这种情况下,跟踪上下文将写入 ASCII 跟踪文件,因为它们是必需的
消除来自两个接口的痕迹。 请注意,由于用户完全
指定文件名时,字符串应包含“,tr”以保持一致性。
· 您可以通过提供一个特定协议来启用 ASCII 跟踪 标准::字符串
将对象名称服务字符串表示为 启用Pcap 方法。 的 点 is
从名称字符串中查找。 这 在生成的文件名中是隐含的,因为
协议实例和节点之间存在一一对应的关系,例如:
名称::Add ("node1Ipv4" ...);
名称::Add ("node2Ipv4" ...);
...
helper.EnableAsciiIpv4 ("prefix", "node1Ipv4", 1);
helper.EnableAsciiIpv4 ("prefix", "node2Ipv4", 1);
这将导致两个名为“prefix-nnode1Ipv4-i1.tr”的文件和
“prefix-nnode2Ipv4-i1.tr”在相应的跟踪文件中带有每个接口的跟踪。
由于所有 EnableAscii 函数都被重载以采用流包装器,因此您可以
也使用该表格:
名称::Add ("node1Ipv4" ...);
名称::Add ("node2Ipv4" ...);
...
点流 = asciiTraceHelper.CreateFileStream ("trace-file-name.tr");
...
helper.EnableAsciiIpv4 (stream, "node1Ipv4", 1);
helper.EnableAsciiIpv4 (stream, "node2Ipv4", 1);
这将产生一个名为“trace-file-name.tr”的跟踪文件,其中包含所有
两个接口的跟踪事件。事件将通过跟踪消除歧义
上下文字符串。
· 您可以通过提供一个协议/接口对的集合来启用 ASCII 跟踪
IPv4接口容器。对于适当类型的每个协议(与
由设备助手管理),为相应的接口启用跟踪。
再次,在 是隐含的,因为每个之间存在一一对应关系
协议及其节点。例如,
NodeContainer 节点;
...
NetDeviceContainer 设备 = deviceHelper.Install(节点);
...
ipv4AddressHelper ipv4;
ipv4.SetBase("10.1.1.0", "255.255.255.0");
Ipv4InterfaceContainer 接口 = ipv4.Assign (设备);
...
...
helper.EnableAsciiIpv4(“前缀”,接口);
这将导致创建许多 ASCII 跟踪文件,每个文件都遵循
这-n -一世.tr 约定。 将所有轨迹组合成一个
单个文件的完成方式与上面的示例类似:
NodeContainer 节点;
...
NetDeviceContainer 设备 = deviceHelper.Install(节点);
...
ipv4AddressHelper ipv4;
ipv4.SetBase("10.1.1.0", "255.255.255.0");
Ipv4InterfaceContainer 接口 = ipv4.Assign (设备);
...
点流 = asciiTraceHelper.CreateFileStream ("trace-file-name.tr");
...
helper.EnableAsciiIpv4(流,接口);
· 您可以通过提供一个协议/接口对的集合来启用 ASCII 跟踪
节点容器。对于每个节点 节点容器 找到合适的协议。
对于每个协议,都会枚举其接口并在结果上启用跟踪
对。例如,
节点容器 n;
...
helper.EnableAsciiIpv4 ("前缀", n);
这将导致创建许多 ASCII 跟踪文件,每个文件都遵循
这- - .tr 约定。 将所有轨迹组合成一个
单个文件的完成方式与上面的示例类似。
· 您还可以根据节点 ID 和设备 ID 启用 PCAP 跟踪。在这个
在这种情况下,节点 ID 被转换为 点 并查找适当的协议
在节点中。生成的协议和接口用于指定生成的结果
跟踪源。
helper.EnableAsciiIpv4 ("前缀", 21, 1);
当然,跟踪可以合并到一个文件中,如上所示。
· 最后,您可以为系统中的所有接口启用 ASCII 跟踪,并关联
协议与设备助手管理的协议类型相同。
helper.EnableAsciiIpv4All ("前缀");
这将导致创建许多 ASCII 跟踪文件,每个文件对应一个
系统中与助手管理的类型的协议相关的接口。所有的
这些文件将遵循-n -我
将所有跟踪记录到单个文件中的完成方式与上面的示例类似。
档名
上述前缀式方法描述中隐含的是完整的构造
文件名由实现方法决定。按照惯例,ASCII 跟踪在 ns-3 系统
的形式为“ - - .tr"
如前所述,系统中的每个节点都会有一个系统分配的节点 ID。
由于协议和节点之间存在一一对应关系,因此我们使用 node-id
识别协议身份。 给定协议上的每个接口都会有一个
相对于其协议的接口索引(也简称为接口)。 默认情况下,
然后,由于在第一个设备上启用跟踪而创建一个 ASCII 跟踪文件
节点 21 使用前缀“prefix”,将是“prefix-n21-i1.tr”。使用前缀
消除每个节点的多个协议的歧义。
您可以随时使用 ns-3 对象名称服务使这一点更加清晰。 例如,如果
您使用对象名称服务将名称“serverIpv4”分配给节点上的协议
21,并指定接口一,生成的 ASCII 跟踪文件名将自动
成为“前缀-nserverIpv4-1.tr”。
其中一些方法有一个默认参数,称为 显式文件名。 设置为
true,此参数禁用自动文件名完成机制并允许您
创建一个明确的文件名。此选项仅在采用
前缀并在单个设备上启用跟踪。
结语
ns-3 包括极其丰富的环境,允许多个级别的用户进行自定义
可以从模拟中提取的信息类型。
有高级帮助函数,允许用户简单地控制集合
预定义的输出到细粒度。有中级帮助函数可以允许
更成熟的用户可以自定义信息的提取和保存方式;在那里
是低级核心功能,允许专家用户更改系统以呈现新的和
用户可以立即访问以前未导出的信息
更高的水平。
这是一个非常全面的系统,我们意识到它需要消化很多东西,尤其是
适合新用户或不太熟悉 C++ 及其习惯用法的用户。我们确实考虑
追踪系统非常重要的一部分 ns-3 因此建议熟悉
有了它就可以了。理解其余部分的情况可能就是这样 ns-3 系统
一旦你掌握了追踪系统,就会变得非常简单
数据 类别
我们的最后一章教程介绍了一些添加到的组件 ns-3 在版本中
3.18,仍在开发中。本教程部分也是
工作正在进行。
激励卡片
运行模拟的要点之一是生成输出数据,无论是
研究目的或只是为了了解系统。在上一章中,我们
介绍了跟踪子系统和示例 第六抄送。来自 PCAP 或 ASCII 跟踪
文件已生成。这些痕迹对于使用各种方法进行数据分析非常有价值
外部工具,对于许多用户来说,此类输出数据是收集数据的首选方式
数据(用于外部工具分析)。
然而,除了跟踪文件生成之外,还有一些用例,包括
在以下:
· 生成的数据不能很好地映射到 PCAP 或 ASCII 跟踪,例如非数据包
数据(例如协议状态机转换),
· 生成跟踪文件的磁盘 I/O 要求较高的大型仿真
令人望而却步的或麻烦的,以及
· 需要 在线 在模拟过程中进行数据缩减或计算。
一个很好的例子是定义模拟的终止条件,告诉
当它收到足够的数据以形成足够窄的置信度时何时停止
某些参数估计值周围的区间。
这个 ns-3 数据收集框架旨在提供这些附加功能
超越基于跟踪的输出。我们建议对此主题感兴趣的读者查阅
这些因素包括原料奶的可用性以及达到必要粉末质量水平所需的工艺。 ns-3 对该框架进行更详细处理的手册;在这里,我们总结一下
一些开发功能的示例程序。
例如: 代码
教程示例 示例/教程/seventh.cc 类似于 第六抄送 例如我们
之前已审查过,但有一些更改。首先,已启用IPv6
支持命令行选项:
命令行 cmd;
cmd.AddValue("useIpv6","使用Ipv6",useV6);
cmd.Parse(argc, argv);
如果用户指定 使用Ipv6,选项,程序将使用 IPv6 而不是 IPv4 运行。
这个 帮助 选项,适用于所有 ns-3 支持 CommandLine 对象的程序为
如上所示,可以按如下方式调用(请注意使用双引号):
./waf--运行“第七--帮助”
它产生:
ns3-dev-seventh-debug [程序参数] [一般参数]
程序参数:
--useIpv6: 使用 Ipv6 [false]
一般论点:
--PrintGlobals:打印全局列表。
--PrintGroups:打印组列表。
--PrintGroup=[group]: 打印group的所有TypeId。
--PrintTypeIds:打印所有TypeId。
--PrintAttributes=[typeid]:打印typeid的所有属性。
--PrintHelp:打印此帮助消息。
可以通过切换布尔值来更改此默认值(使用 IPv4,因为 useIpv6 为 false)
值如下:
./waf --run "第七 --useIpv6=1"
并查看生成的 pcap,例如 转储:
tcpdump -r Seventh.pcap -nn -tt
这是对 IPv6 支持和命令行的一个简短的题外话,这也是
本教程前面介绍过。有关命令行使用的专用示例,
请参阅 src/core/examples/命令行-example.cc.
现在回到数据收集。在里面 示例/教程/ 目录,输入以下内容
命令: 差异 -u 第六抄送 第七个.cc,并检查此差异的一些新行:
+ std::string 探针类型;
+ std::string 跟踪路径;
+ if (useV6 == false)
+ {
...
+probeType =“ns3::Ipv4PacketProbe”;
+ tracePath = "/NodeList/*/$ns3::Ipv4L3Protocol/Tx";
+ }
+ 其他
+ {
...
+probeType =“ns3::Ipv6PacketProbe”;
+ tracePath = "/NodeList/*/$ns3::Ipv6L3Protocol/Tx";
+ }
...
+ // 使用 GnuplotHelper 绘制随时间变化的数据包字节数
+ GnuplotHelper 绘图助手;
+
+ // 配置绘图。第一个参数是文件名前缀
+ // 用于生成的输出文件。第二个、第三个、第四个
+ // 参数分别是绘图标题、x 轴和 y 轴标签
+plotHelper.ConfigurePlot(“第七个数据包字节数”,
+“数据包字节数与时间”,
+“时间(秒)”,
+“数据包字节数”);
+
+ // 指定探针类型、跟踪源路径(在配置命名空间中)以及
+ // 探测要绘制的输出跟踪源(“OutputBytes”)。第四个论点
+ // 指定绘图上数据系列标签的名称。最后
+ // 参数通过指定键的放置位置来格式化绘图。
+plotHelper.PlotProbe(探针类型,
+ 跟踪路径,
+“输出字节”,
+“数据包字节数”,
+ GnuplotAggregator::KEY_BELOW);
+
+ // 使用 FileHelper 随时间写出数据包字节数
+ 文件助手 文件助手;
+
+ // 配置要写入的文件,以及输出数据的格式。
+ fileHelper.ConfigureFile ("第七个数据包字节数",
+ 文件聚合器::格式化);
+
+ // 设置此格式化输出文件的标签。
+ fileHelper.Set2dFormat("时间(秒) = %.3e\t数据包字节计数 = %.0f");
+
+ // 指定探测类型、探测路径(在配置命名空间中)和
+ // 要写入的探针输出跟踪源(“OutputBytes”)。
+ fileHelper.WriteProbe (probeType,
+ 跟踪路径,
+“输出字节”);
+
模拟器::停止(秒(20));
模拟器::运行();
模拟器::销毁();
细心的读者会注意到,在测试上面的 IPv6 命令行属性时,
这 第七个.cc 创建了许多新的输出文件:
第七包字节计数0.txt
第七包字节计数1.txt
第七包字节计数.dat
第七包字节计数.plt
第七个数据包字节计数.png
第七包字节计数.sh
这些是由上面介绍的附加语句创建的;特别是,由
GnuplotHelper 和 FileHelper。该数据是通过挂钩数据收集产生的
组件 ns-3 跟踪来源,并将数据编组为格式化的 图形 和
到格式化的文本文件中。在接下来的部分中,我们将逐一回顾这些内容。
Gnuplot助手
GnuplotHelper 是一个 ns-3 辅助对象旨在生成 图形 情节与
对于常见情况,尽可能少的语句。它钩住 ns-3 用数据溯源
数据收集系统支持的类型。不是全部 ns-3 跟踪源数据类型是
支持,但许多常见的跟踪类型都支持,包括带有普通旧版本的 TracedValues
数据 (POD) 类型。
让我们看看这个助手产生的输出:
第七包字节计数.dat
第七包字节计数.plt
第七包字节计数.sh
第一个是一个 gnuplot 数据文件,其中包含一系列以空格分隔的时间戳和数据包
字节数。我们将在下面介绍如何配置这个特定的数据输出,但让我们
继续输出文件。文件 第七包字节计数.plt 是一个 gnuplot 图
文件,可以从 gnuplot 中打开。了解 gnuplot 语法的读者可以
看到这将生成一个名为的格式化输出 PNG 文件
第七个数据包字节计数.png。最后是一个小shell脚本
第七包字节计数.sh 通过 gnuplot 运行该绘图文件以生成所需的结果
PNG(可以在图像编辑器中查看);即命令:
sh 第七个数据包字节计数.sh
会屈服 第七个数据包字节计数.png。为什么这个PNG不先制作出来
地方?答案是,通过提供 plt 文件,用户可以手动配置
如果需要,在生成 PNG 之前生成结果。
PNG 图像标题指出该图是“数据包字节数与时间”的图,并且
它正在绘制与跟踪源路径相对应的探测数据:
/NodeList/*/$ns3::Ipv6L3Protocol/Tx
请注意跟踪路径中的通配符。总而言之,这个情节所捕捉的是情节
在 Ipv6L3Protocol 对象的传输跟踪源处观察到的数据包字节数;
一个方向上主要是 596 字节的 TCP 段,另一个方向上是 60 字节的 TCP 确认(两个
节点跟踪源与此跟踪源匹配)。
这是如何配置的?需要提供一些声明。一、GnuplotHelper
必须声明和配置对象:
+ // 使用 GnuplotHelper 绘制随时间变化的数据包字节数
+ GnuplotHelper 绘图助手;
+
+ // 配置绘图。第一个参数是文件名前缀
+ // 用于生成的输出文件。第二个、第三个、第四个
+ // 参数分别是绘图标题、x 轴和 y 轴标签
+plotHelper.ConfigurePlot(“第七个数据包字节数”,
+“数据包字节数与时间”,
+“时间(秒)”,
+“数据包字节数”);
至此,一个空图已经配置完毕。文件名前缀是第一个
参数,绘图标题是第二个,x 轴标签是第三个,y 轴标签是
第四个论点。
下一步是配置数据,这里是挂钩跟踪源的地方。
首先,注意上面我们在程序中声明了几个变量供以后使用:
+ std::string 探针类型;
+ std::string 跟踪路径;
+probeType =“ns3::Ipv6PacketProbe”;
+ tracePath = "/NodeList/*/$ns3::Ipv6L3Protocol/Tx";
我们在这里使用它们:
+ // 指定探针类型、跟踪源路径(在配置命名空间中)以及
+ // 探测要绘制的输出跟踪源(“OutputBytes”)。第四个论点
+ // 指定绘图上数据系列标签的名称。最后
+ // 参数通过指定键的放置位置来格式化绘图。
+plotHelper.PlotProbe(探针类型,
+ 跟踪路径,
+“输出字节”,
+“数据包字节数”,
+ GnuplotAggregator::KEY_BELOW);
前两个参数是探测类型的名称和跟踪源路径。这些
当您尝试使用此框架绘制其他框架时,两个可能是最难确定的
痕迹。这里的探测轨迹是 Tx 追踪类的来源 IPv6L3协议。 什么时候我们
检查这个类的实现(src/internet/model/ipv6-l3-protocol.cc)我们可以观察到:
.AddTraceSource("Tx", "将 IPv6 数据包发送到传出接口。",
MakeTraceSourceAccessor (&Ipv6L3Protocol::m_txTrace))
这说的是 Tx 是变量的名称 m_txTrace,其中有一个声明:
/ **
* \brief 跟踪 TX(传输)数据包的回调。
*/
追踪回调 , 指针, uint6_t> m_txTrace;
事实证明,这个特定的跟踪源签名是由 Probe 类支持的(什么
我们在这里需要 Ipv6PacketProbe 类的)。查看文件
src/internet/model/ipv6-packet-probe.{h,cc}.
因此,在上面的 PlotProbe 语句中,我们看到该语句正在挂钩跟踪
源(由路径字符串标识)与匹配 ns-3 探头类型 Ipv6数据包探测。 如果
我们不支持这种探测类型(匹配跟踪源签名),我们本来可以不支持
使用了这个语句(尽管一些更复杂的较低级别的语句可能是
使用,如手册中所述)。
Ipv6PacketProbe 本身导出一些跟踪源,这些跟踪源从
探测到的数据包对象:
类型标识
Ipv6PacketProbe::GetTypeId ()
{
静态 TypeId tid = TypeId ("ns3::Ipv6PacketProbe")
.SetParent ()
.AddConstructor ()
.AddTraceSource ( "输出",
"作为此探测输出的数据包及其 IPv6 对象和接口",
MakeTraceSourceAccessor (&Ipv6PacketProbe::m_output))
.AddTraceSource ( "OutputBytes",
"数据包中的字节数",
MakeTraceSourceAccessor (&Ipv6PacketProbe::m_outputBytes))
;
返回时间;
}
PlotProbe 语句的第三个参数指定我们感兴趣的是
该数据包中的字节数;具体来说,“OutputBytes”跟踪源
Ipv6PacketProbe。最后,该语句的最后两个参数提供了情节图例
对于此数据系列(“数据包字节计数”),以及可选的 gnuplot 格式化语句
(GnuplotAggregator::KEY_BELOW) 我们希望将绘图键插入到绘图下方。
其他选项包括 NO_KEY、KEY_INSIDE 和 KEY_ABOVE。
支持 追踪 类型
截至撰写本文时,探针支持以下跟踪值:
┌──────────────────┬──────────────────┬────────────── ──────────────────┐
│TracedValue 类型 │ 探针类型 │ 文件 │
├──────────────────┼──────────────────┼────────────── ──────────────────┤
│double │ DoubleProbe │ stats/model/double-probe.h │
├──────────────────┼──────────────────┼────────────── ──────────────────┤
│uint8_t │ Uinteger8Probe │ stats/model/uinteger-8-probe.h │
├──────────────────┼──────────────────┼────────────── ──────────────────┤
│uint16_t │ Uinteger16Probe │ stats/model/uinteger-16-probe.h │
├──────────────────┼──────────────────┼────────────── ──────────────────┤
│uint32_t │ Uinteger32Probe │ stats/model/uinteger-32-probe.h │
├──────────────────┼──────────────────┼────────────── ──────────────────┤
│bool │ BooleanProbe │ stats/model/uinteger-16-probe.h │
├──────────────────┼──────────────────┼────────────── ──────────────────┤
│ns3::时间 │ TimeProbe │ stats/model/time-probe.h │
└──────────────────┴──────────────────┴────────────── ──────────────────┘
截至撰写本文时,探针支持以下 TraceSource 类型:
┌────────────────────┬────────────────────────┬──── ────────────┬────────────────────────────────────── ──────────┐
├────────────────────┼────────────────────────┼──── ────────────┼──────────────────────────────────── ──────────┤
├────────────────────┼────────────────────────┼──── ────────────┼──────────────────────────────────── ──────────┤
├────────────────────┼────────────────────────┼──── ────────────┼──────────────────────────────────── ──────────┤
├────────────────────┼────────────────────────┼──── ────────────┼──────────────────────────────────── ──────────┤
├────────────────────┼────────────────────────┼──── ────────────┼──────────────────────────────────── ──────────┤
└────────────────────┴────────────────────────┴──── ────────────┴────────────────────────────────────── ──────────┘
可以看到,只支持少数trace源,而且都是面向
输出数据包大小(以字节为单位)。然而,大多数基本数据类型
这些帮助程序可以支持 TracedValues。
文件助手
FileHelper 类只是前面的 GnuplotHelper 示例的变体。这
示例程序提供相同时间戳数据的格式化输出,如下所示:
时间(秒)= 9.312e+00 数据包字节数 = 596
时间(秒)= 9.312e+00 数据包字节数 = 564
提供了两个文件,一个用于节点“0”,一个用于节点“1”,如以下所示
文件名。让我们一块一块地看一下代码:
+ // 使用 FileHelper 随时间写出数据包字节数
+ 文件助手 文件助手;
+
+ // 配置要写入的文件,以及输出数据的格式。
+ fileHelper.ConfigureFile ("第七个数据包字节数",
+ 文件聚合器::格式化);
文件帮助程序文件前缀是第一个参数,接下来是格式说明符。一些
其他格式化选项包括 SPACE_SEPARATED、COMMA_SEPARATED 和 TAB_SEPARATED。
用户可以使用格式字符串更改格式(如果指定了 FORMATTED)
比如如下:
+
+ // 设置此格式化输出文件的标签。
+ fileHelper.Set2dFormat("时间(秒) = %.3e\t数据包字节计数 = %.0f");
最后,必须钩住感兴趣的跟踪源。同样,probeType 和tracePath
使用本例中的变量,探测器的输出跟踪源“OutputBytes”为
上瘾:
+
+ // 指定探针类型、跟踪源路径(在配置命名空间中)以及
+ // 要写入的探针输出跟踪源(“OutputBytes”)。
+ fileHelper.WriteProbe (probeType,
+ 跟踪路径,
+“输出字节”);
+
此跟踪源说明符中的通配符字段与两个跟踪源匹配。不像
GnuplotHelper 示例,其中两个数据系列叠加在同一个图上,这里是两个
单独的文件被写入磁盘。
结语
自 ns-3.18 起新增数据收集支持,以及提供时间序列的基本支持
输出已添加。上述基本模式可以在
现有探测器和跟踪源的支持范围。更多功能包括
统计处理将在未来版本中添加。
结论
期货
本文档旨在作为一份动态文档。我们希望并期望它随着时间的推移而增长
涵盖越来越多的细节 ns-3.
编写手册和教程章节并不是我们都感到兴奋的事情,但它是
对项目非常重要。如果您是这些领域之一的专家,请
考虑为 ns-3 通过提供这些章节之一;或您的任何其他章节
可能认为很重要。
关闭
ns-3 是一个庞大而复杂的系统。不可能涵盖所有你想要的东西
将需要在一个小教程中了解。鼓励想要了解更多信息的读者
阅读以下附加文档:
· ns-3 手册
· ns-3 模型库文档
· ns-3 Doxygen(API 文档)
· ns-3 维基
- 这 ns-3 开发团队。
使用 onworks.net 服务在线使用 ns-3-tutorial