需求
用ns3复现一篇论文,在ns3-tutorial中,新建模拟脚本的方式是都是在scratch文件夹下创建新的cc文件,而且由于样例较为简单都是一个cc文件就可以完成模拟需求。而我所要复现的论文的算法相当复杂,不适合用单个cc文件保存所有代码。
问题
最初,尝试直接在scratch文件中创建多个.cc文件和.h文件相互引用,并以其中一个作为放置main函数的模拟入口文件。经测试发现,ns3的waf编译工具完全无法识别其中的引用关系,报未定义错误。
因为,我们的目标为:
找到一个方法,能够用多个相互引用的文件完成一个复杂模拟,其中仅有一个含有main函数的文件作为模拟入口。使得ns3的waf工具可以正确编译和运行这些文件。
思路
在网上查找资料,首先通过此网页得到了新建模块的思路。
How to run ns3 files out side the scratch or in sub-folder in side the scratch
之后,通过查阅ns3-manual得到了新建一个模块的具体方法和更详细的介绍
Adding a New Module to ns-3
最后,又找到了两篇csdn上的相关博客,印证了该方法的可行性。
在NS3中建立新的库函数手动添加依赖
在ns-3中添加自己编写的模块
需要注意的是,这两篇csdn博客在我看来都有些不妥之处。这里只将其作为思路印证来看。而google-group和ns3-manual那两个网址则相对显得专业和直击要害一些。
解决方法
接下来的解决方法基于ns-3.30.1,相比于前面网页中的内容,由于版本更新所以一些步骤产生了区别。
- 进入src文件夹,通过
./create-module.py [YourModelName]
来创建新Model,其中[YourModelName]为你想要创建的模块的名称。 - 系统会自动创建一个以[YourModelName]命名的子目录,其内部结构如下。
本文暂不涉及helper、test、examples、doc中的内容和相关我认为较为深入的内容。
在model中,会有自动生成的[YourModelName].h和[YouModelName].cc。我的模块名为qtcp,所以生成了qtcp.h和qtcp.cc
- 观察qtcp.h,我们可以自动生成的代码中出现了一个命名空间qtcp.
我认为这意味着ns3希望我们的新模块能尽可能包含在一个命名空间中(待验证)。因此我们按照相关的写法编写和组织自己的文件。我在model文件夹中又写了一些类和函数。
- 修改qtcp文件夹中的wscript!
# def options(opt):
# pass
# def configure(conf):
# conf.check_nonfatal(header_name='stdint.h', define_name='HAVE_STDINT_H')
def build(bld):
module = bld.create_ns3_module('qtcp', ['core','network','internet','point-to-point','applications','stats'])
module.source = [
'model/qtcp.cc',
'model/TerminalApplication.cc',
'model/QTCPSimulator.cc',
'helper/qtcp-helper.cc',
]
module_test = bld.create_ns3_module_test_library('qtcp')
module_test.source = [
'test/qtcp-test-suite.cc',
]
headers = bld(features='ns3header')
headers.module = 'qtcp'
headers.source = [
'model/qtcp.h',
'model/TerminalApplication.h',
'model/QTCPSimulator.h',
'helper/qtcp-helper.h',
]
if bld.env.ENABLE_EXAMPLES:
bld.recurse('examples')
# bld.ns3_python_bindings()
不同人的具体wscript文件需求不同。在这里特别解释一下其中需要特别注意的三个地方。
对其他模块的引用
module = bld.create_ns3_module('qtcp', ['core','network','internet','point-to-point','applications','stats'])
module = bld.create_ns3_module这行代码意思为此模块需要引用的模块,需要将自己写的代码所有引用的模块在此处声明。具体引用了哪些模块可以根据自己写的文件中引用的头文件来帮助判断。
比如我在TerminalApplication.h中include了上面那些xx.h,就要将对应的xx都写到wscript的bld.create_ns3_module中
cc文件的声明
module.source = [
'model/qtcp.cc',
'model/TerminalApplication.cc',
'model/QTCPSimulator.cc',
'helper/qtcp-helper.cc',
]
在module.source中,我们需要声明我们所有自己写的cc文件。
h文件的声明
headers.source = [
'model/qtcp.h',
'model/TerminalApplication.h',
'model/QTCPSimulator.h',
'helper/qtcp-helper.h',
]
在header.source文件中,我们需要声明所有自己写的h文件。
- 回到ns3的根目录,通过
./waf build
重新编译ns3项目 - 在scratch中创建我们的入口文件,其中包含main函数。此时,我们创建的qtcp模块已经被整合到了系统中(在build目录下生成了相关的引用文件)。我们可以调用里面qtcp模块中我们写的各个类和函数完成我们想要的功能。
结果
我在scratch目录下新建了一个名为MyQTCPSimulator.cc的文件,具体代码如下
#include "ns3/qtcp-module.h"
#include "ns3/core-module.h"
#include "ns3/simulator.h"
NS_LOG_COMPONENT_DEFINE("QTCPSimulator");
int main(int argc, char *argv[])
{
qtcp::TerminalApplication app;
cout<<"Hello QTCP!";
qtcp::simulate(argc,argv);
return 0;
}
通过#inclue "ns3/qtcp-module.h
添加了对qtcp模块的引用。
TerminalApplication
是qtcp
目录中由TerminalApplication.h
和TerminalApplication.cc
定义的类,定义在qtcp命名空间内。
simulate
是qtcp
目录中由QTCPSimulator.h
和QTCPSimulator.cpp
在qtcp命名空间中直接定义的方法。
在终端中运行./waf --run MyQTCPSimulator
,程序顺利运行。
其他
-
C++语言最让我讨厌的地方之一就是其复杂的头文件、引用、命名空间等等机制很容易导致的各种重定义、未声明错误。我在尝试这个过程中曾出现过的导致问题的原因有:
- 1 . 命名空间使用不规范
- 2 . 自己写的类和函数同Simulator,Application等ns3本身含有的命名崇明
- 3 . 没有在每一个.h文件中添加#ifndef … #define … #endif
-
关于wscript,这种写了文件后还需要手动配置的方式实在是太不友好了。我非常希望能有一个自动通过相关目录信息来修改这个wscript文件的脚本。也许我对ns3熟悉之后又能抽出时间的话会试着去写一个。
-
关于如何创建一个比较好的ns3编辑环境,可以参考我的上一篇博客使用vscode开发ns3项目(代码高亮、自动补全支持)
参考文献
在思路章已全部列出,此处不再赘述。